Merge "Allow FragmentTransactions in onConfigurationChanged" into oc-mr1-jetpack-dev
diff --git a/.gitignore b/.gitignore
index 162af55..7e44717 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,4 +16,8 @@
 **/out
 buildSrc/build
 lifecycle/common/build
-jacoco.exec
\ No newline at end of file
+jacoco.exec
+
+# When Gradle configuration fails with the --profile option enabled it creates this folder.
+/reports
+/app-toolkit/reports
diff --git a/app-toolkit/core-testing/api/current.txt b/app-toolkit/core-testing/api/current.txt
new file mode 100644
index 0000000..f1d206c
--- /dev/null
+++ b/app-toolkit/core-testing/api/current.txt
@@ -0,0 +1,15 @@
+package android.arch.core.executor.testing {
+
+  public class CountingTaskExecutorRule extends org.junit.rules.TestWatcher {
+    ctor public CountingTaskExecutorRule();
+    method public void drainTasks(int, java.util.concurrent.TimeUnit) throws java.lang.InterruptedException, java.util.concurrent.TimeoutException;
+    method public boolean isIdle();
+    method protected void onIdle();
+  }
+
+  public class InstantTaskExecutorRule extends org.junit.rules.TestWatcher {
+    ctor public InstantTaskExecutorRule();
+  }
+
+}
+
diff --git a/app-toolkit/init.gradle b/app-toolkit/init.gradle
index 393b2f9..654f65c 100644
--- a/app-toolkit/init.gradle
+++ b/app-toolkit/init.gradle
@@ -15,7 +15,6 @@
  */
 
 import android.support.DacOptions
-import org.gradle.internal.os.OperatingSystem
 
 apply from: "${ext.supportRootFolder}/buildSrc/init.gradle"
 init.setSdkInLocalPropertiesFile()
diff --git a/app-toolkit/runtime/api/current.txt b/app-toolkit/runtime/api/current.txt
new file mode 100644
index 0000000..af5b253
--- /dev/null
+++ b/app-toolkit/runtime/api/current.txt
@@ -0,0 +1,8 @@
+package android.arch.core.util {
+
+  public abstract interface Function<I, O> {
+    method public abstract O apply(I);
+  }
+
+}
+
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 718110f..4d663af 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -11,6 +11,14 @@
     dependencies {
         classpath build_libs.kotlin.gradle_plugin
     }
+
+    configurations.classpath.resolutionStrategy {
+        eachDependency { details ->
+            if (details.requested.group == 'org.jetbrains.kotlin') {
+                details.useVersion build_versions.kotlin
+            }
+        }
+    }
 }
 def runningInBuildServer = System.env.DIST_DIR != null && System.env.OUT_DIR != null
 if (runningInBuildServer) {
@@ -24,10 +32,11 @@
 
 repos.addMavenRepositories(repositories)
 
-apply plugin: 'groovy'
 apply plugin: 'java'
 apply plugin: 'kotlin'
 
+apply from: "kotlin-dsl-dependency.gradle.kts"
+
 compileGroovy {
     dependsOn tasks.getByPath('compileKotlin')
     classpath += files(compileKotlin.destinationDir)
@@ -38,4 +47,5 @@
     compile build_libs.error_prone
     compile build_libs.jarjar_gradle
     compile gradleApi()
+    testCompile "junit:junit:4.12"
 }
diff --git a/buildSrc/build_dependencies.gradle b/buildSrc/build_dependencies.gradle
index 313c4e3..f693884 100644
--- a/buildSrc/build_dependencies.gradle
+++ b/buildSrc/build_dependencies.gradle
@@ -14,6 +14,13 @@
  * limitations under the License.
  */
 
+def build_versions = [:]
+
+build_versions.kotlin = '1.2.0'
+
+rootProject.ext['build_versions'] = build_versions
+
+
 def build_libs = [:]
 
 def androidPluginVersionOverride = System.getenv("GRADLE_PLUGIN_VERSION")
@@ -31,7 +38,9 @@
 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 = 'androidx.tools.jetifier:gradle-plugin:0.1'
-build_libs.kotlin = [gradle_plugin: "org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.0"]
+build_libs.kotlin = [
+        gradle_plugin: "org.jetbrains.kotlin:kotlin-gradle-plugin:${build_versions.kotlin}"
+]
 // jdiff dependencies
 build_libs.jdiff = 'com.android:jdiff:1.1.0'
 build_libs.xml_parser_apis = 'xerces:xmlParserAPIs:2.6.2'
diff --git a/buildSrc/init.gradle b/buildSrc/init.gradle
index 08faa27..30841b6 100644
--- a/buildSrc/init.gradle
+++ b/buildSrc/init.gradle
@@ -30,7 +30,7 @@
 }
 def init = new Properties()
 ext.init = init
-rootProject.ext.versionChecker = new GMavenVersionChecker(rootProject)
+rootProject.ext.versionChecker = new GMavenVersionChecker(rootProject.logger)
 ext.runningInBuildServer = System.env.DIST_DIR != null && System.env.OUT_DIR != null
 
 apply from: "${supportRoot}/buildSrc/dependencies.gradle"
@@ -63,27 +63,15 @@
 }
 
 def setSdkInLocalPropertiesFile() {
-    ext.buildToolsVersion = '27.0.1'
-    final String fullSdkPath = getFullSdkPath();
-    if (file(fullSdkPath).exists()) {
-        project.ext.currentSdk = 26
-        project.ext.androidJar =
-                files("${fullSdkPath}/platforms/android-${project.currentSdk}/android.jar")
-        project.ext.androidSrcJar =
-                file("${fullSdkPath}/platforms/android-${project.currentSdk}/android-stubs-src.jar")
-        project.ext.androidApiTxt = null
+    final File fullSdkPath = file(getFullSdkPath())
+    if (fullSdkPath.exists()) {
+        project.ext.fullSdkPath = fullSdkPath
         File props = file("local.properties")
-        props.write "sdk.dir=${fullSdkPath}"
+        props.write "sdk.dir=${fullSdkPath.getAbsolutePath()}"
         ext.usingFullSdk = true
     } else {
-        gradle.ext.currentSdk = 'current'
-        project.ext.androidJar = files("${repos.prebuiltsRoot}/sdk/current/android.jar")
-        project.ext.androidSrcJar = null
-        project.ext.androidApiTxt = file("${repos.prebuiltsRoot}/sdk/api/26.txt")
-        System.setProperty('android.dir', "${supportRootFolder}/../../")
-        File props = file("local.properties")
-        props.write "android.dir=../../"
-        ext.usingFullSdk = false
+        throw Exception("You are using non ub-supportlib-* checkout. You need to check out "
+                + "ub-supportlib-* to work on support library. See go/supportlib for details.")
     }
 }
 
@@ -164,8 +152,6 @@
 }
 
 def configureSubProjects() {
-    // lint every library
-    def lintTask = project.tasks.create("lint")
     subprojects {
         repos.addMavenRepositories(repositories)
 
@@ -180,13 +166,10 @@
             return
         }
 
-        project.ext.currentSdk = rootProject.ext.currentSdk
-
         project.plugins.whenPluginAdded { plugin ->
             def isAndroidLibrary = "com.android.build.gradle.LibraryPlugin"
                     .equals(plugin.class.name)
             def isAndroidApp = "com.android.build.gradle.AppPlugin".equals(plugin.class.name)
-            def isJavaLibrary = "org.gradle.api.plugins.JavaPlugin".equals(plugin.class.name)
 
             if (isAndroidLibrary || isAndroidApp) {
                 // Enable code coverage for debug builds only if we are not running inside the IDE,
@@ -216,29 +199,6 @@
                         v.assemble.dependsOn jarifyTask
                     }
                 }
-
-                // Enforce NewApi lint check as fatal.
-                project.android.lintOptions.fatal 'NewApi'
-                lintTask.dependsOn {project.lint}
-            }
-
-            if (isAndroidLibrary || isJavaLibrary) {
-                // Add library to the aggregate dependency report.
-                task allDeps(type: DependencyReportTask) {}
-
-                project.afterEvaluate {
-                    Upload uploadTask = (Upload) project.tasks.uploadArchives;
-                    uploadTask.repositories.mavenDeployer {
-                        repository(url: uri("$rootProject.ext.supportRepoOut"))
-                        setUniqueVersion(true)
-                    }
-
-                    // Before the upload, make sure the repo is ready.
-                    uploadTask.dependsOn rootProject.tasks.prepareRepo
-
-                    // Make the mainupload depend on this one.
-                    mainUpload.dependsOn uploadTask
-                }
             }
         }
 
diff --git a/v13/tests/java/android/support/v13/app/FragmentCompatTestActivity.java b/buildSrc/kotlin-dsl-dependency.gradle.kts
similarity index 75%
rename from v13/tests/java/android/support/v13/app/FragmentCompatTestActivity.java
rename to buildSrc/kotlin-dsl-dependency.gradle.kts
index b3ab763..64085ab 100644
--- a/v13/tests/java/android/support/v13/app/FragmentCompatTestActivity.java
+++ b/buildSrc/kotlin-dsl-dependency.gradle.kts
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-package android.support.v13.app;
-
-import android.app.Activity;
-
-public class FragmentCompatTestActivity extends Activity {
-}
+dependencies {
+    "compileOnly"(gradleKotlinDsl())
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/android/support/gmaven/GMavenVersionChecker.groovy b/buildSrc/src/main/groovy/android/support/gmaven/GMavenVersionChecker.groovy
deleted file mode 100644
index 0a71fbc..0000000
--- a/buildSrc/src/main/groovy/android/support/gmaven/GMavenVersionChecker.groovy
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR 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.gmaven
-
-import android.support.Version
-import com.android.annotations.Nullable
-import groovy.util.slurpersupport.NodeChild
-import org.gradle.api.GradleException
-import org.gradle.api.Project
-import org.gradle.api.logging.Logger
-
-/**
- * Queries maven.google.com to get the version numbers for each artifact.
- * Due to the structure of maven.google.com, a new query is necessary for each group.
- */
-@SuppressWarnings("GroovyUnusedDeclaration")
-class GMavenVersionChecker {
-    // wait 2 seconds before retrying if fetch fails
-    private static final int RETRY_DELAY = 2000 // ms
-    // number of times we'll try to reach maven.google.com before failing
-    private static final int DEFAULT_RETRY_LIMIT = 20
-    private static final String BASE = "https://dl.google.com/dl/android/maven2/"
-    private static final String GROUP_FILE = "group-index.xml"
-
-    // cache versions by group to avoid re-querying for each artifact
-    private final Map<String, GroupVersionData> versionCache = new HashMap<>()
-    // the logger from the project
-    private final Logger logger
-
-    /**
-     * Creates a new instance using the given project's logger
-     *
-     * @param project This should be the root project. No reason to create multiple instances of
-     *                this
-     */
-    GMavenVersionChecker(Project project) {
-        this.logger = project.logger
-    }
-
-    /**
-     * Creates the URL which has the XML file that describes the available versions for each
-     * artifact in that group
-     *
-     * @param group Maven group name
-     * @return The URL of the XML file
-     */
-    private static String buildGroupUrl(String group) {
-        return BASE + group.replace(".", "/") + "/" + GROUP_FILE
-    }
-
-    /**
-     * Returns the version data for each artifact in a given group.
-     * <p>
-     * If data is not cached, this will make a web request to get it.
-     *
-     * @param group The group to query
-     * @return A data class which has the versions for each artifact
-     */
-    private GroupVersionData getVersionData(String group) {
-        def versionData = versionCache.get(group)
-        if (versionData == null) {
-            versionData = fetchGroup(group)
-            if (versionData != null) {
-                versionCache.put(versionData.name, versionData)
-            }
-        }
-        return versionData
-    }
-
-    /**
-     * Fetches the group version information from maven.google.com
-     *
-     * @param group The group name to fetch
-     * @param retryCount Number of times we'll retry before failing
-     * @return GroupVersionData that has the data or null if it is a new item.
-     */
-    @Nullable
-    private GroupVersionData fetchGroup(String group, int retryCount) {
-        def url = buildGroupUrl(group)
-        for (int run = 0; run < retryCount; run++) {
-            logger.info "fetching maven XML from $url"
-            try {
-                def parsedXml = new XmlSlurper(false, false).parse(url)
-                return new GroupVersionData(parsedXml)
-            } catch (FileNotFoundException ignored) {
-                logger.info "could not find version data for $group, seems like a new file"
-                return null
-            } catch (IOException ioException) {
-                logger.warning "failed to fetch the maven info, retrying in 2 seconds. " +
-                        "Run $run of $retryCount"
-                Thread.sleep(RETRY_DELAY)
-            }
-        }
-        throw new GradleException("Could not access maven.google.com")
-    }
-
-    /**
-     * Fetches the group version information from maven.google.com
-     *
-     * @param group The group name to fetch
-     * @return GroupVersionData that has the data or null if it is a new item.
-     */
-    @Nullable
-    private GroupVersionData fetchGroup(String group) {
-        return fetchGroup(group, DEFAULT_RETRY_LIMIT)
-    }
-
-    /**
-     * Return the available versions on maven.google.com for a given artifact
-     *
-     * @param group The group id of the artifact
-     * @param artifactName The name of the artifact
-     * @return The set of versions that are available on maven.google.com. Null if artifact is not
-     *         available.
-     */
-    @Nullable
-    Set<Version> getVersions(String group, String artifactName) {
-        def groupData = getVersionData(group)
-        return groupData?.artifacts?.get(artifactName)?.versions
-    }
-
-    /**
-     * Checks whether the given artifact is already on maven.google.com.
-     *
-     * @param group The project group on maven
-     * @param artifactName The artifact name on maven
-     * @param version The version on maven
-     * @return true if the artifact is already on maven.google.com
-     */
-    boolean isReleased(String group, String artifactName, String version) {
-        return getVersions(group, artifactName)?.contains(new Version(version))
-    }
-
-    /**
-     * Data class that holds the artifacts of a single maven group
-     */
-    private static class GroupVersionData {
-        /**
-         * The group name
-         */
-        String name
-        /**
-         * Map of artifact versions keyed by artifact name
-         */
-        Map<String, ArtifactVersionData> artifacts = new HashMap<>()
-
-        /**
-         * Constructs an instance from the given node.
-         *
-         * @param xml The information node fetched from {@code GROUP_FILE}
-         */
-        GroupVersionData(NodeChild xml) {
-            /**
-             * sample input:
-             * <android.arch.core>
-             *   <runtime versions="1.0.0-alpha4,1.0.0-alpha5,1.0.0-alpha6,1.0.0-alpha7"/>
-             *   <common versions="1.0.0-alpha4,1.0.0-alpha5,1.0.0-alpha6,1.0.0-alpha7"/>
-             * </android.arch.core>
-             */
-            this.name = xml.name()
-            xml.childNodes().each {
-                def versions = it.attributes['versions'].split(",").collect { version ->
-                    new Version(version)
-                }.toSet()
-                artifacts[it.name()] = new ArtifactVersionData(it.name(), versions)
-            }
-        }
-    }
-
-    /**
-     * Data class that holds the version information about a single artifact
-     */
-    private static class ArtifactVersionData {
-        /**
-         * name of the artifact
-         */
-        final String name
-        /**
-         * set of version codes that are already on maven.google.com
-         */
-        final Set<Version> versions
-
-        ArtifactVersionData(String name, Set<Version> versions) {
-            this.name = name
-            this.versions = versions
-        }
-    }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/java/android/support/LibraryVersions.java b/buildSrc/src/main/java/android/support/LibraryVersions.java
index baf5007..7b7a37e 100644
--- a/buildSrc/src/main/java/android/support/LibraryVersions.java
+++ b/buildSrc/src/main/java/android/support/LibraryVersions.java
@@ -28,7 +28,7 @@
     /**
      * Version code for flatfoot 1.0 projects (room, lifecycles)
      */
-    private static final Version FLATFOOT_1_0_BATCH = new Version("1.0.0");
+    private static final Version FLATFOOT_1_0_BATCH = new Version("1.1.0-SNAPSHOT");
 
     /**
      * Version code for Room
diff --git a/buildSrc/src/main/java/android/support/Version.java b/buildSrc/src/main/java/android/support/Version.java
deleted file mode 100644
index 36c7728..0000000
--- a/buildSrc/src/main/java/android/support/Version.java
+++ /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.support;
-
-import java.io.File;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Utility class which represents a version
- */
-public class Version implements Comparable<Version> {
-    private static final Pattern VERSION_FILE_REGEX = Pattern.compile("^(\\d+\\.\\d+\\.\\d+).txt$");
-    private static final Pattern VERSION_REGEX = Pattern
-            .compile("^(\\d+)\\.(\\d+)\\.(\\d+)(-.+)?$");
-
-    private final int mMajor;
-    private final int mMinor;
-    private final int mPatch;
-    private final String mExtra;
-
-    public Version(String versionString) {
-        this(checkedMatcher(versionString));
-    }
-
-    private static Matcher checkedMatcher(String versionString) {
-        Matcher matcher = VERSION_REGEX.matcher(versionString);
-        if (!matcher.matches()) {
-            throw new IllegalArgumentException("Can not parse version: " + versionString);
-        }
-        return matcher;
-    }
-
-    private Version(Matcher matcher) {
-        mMajor = Integer.parseInt(matcher.group(1));
-        mMinor = Integer.parseInt(matcher.group(2));
-        mPatch = Integer.parseInt(matcher.group(3));
-        mExtra = matcher.groupCount() == 4 ? matcher.group(4) : null;
-    }
-
-    @Override
-    public int compareTo(Version version) {
-        if (mMajor != version.mMajor) {
-            return mMajor - version.mMajor;
-        }
-        if (mMinor != version.mMinor) {
-            return mMinor - version.mMinor;
-        }
-        if (mPatch != version.mPatch) {
-            return mPatch - version.mPatch;
-        }
-        if (mExtra == null) {
-            if (version.mExtra == null) {
-                return 0;
-            }
-            // not having any extra is always a later version
-            return 1;
-        } else {
-            if (version.mExtra == null) {
-                // not having any extra is always a later version
-                return -1;
-            }
-            // gradle uses lexicographic ordering
-            return mExtra.compareTo(version.mExtra);
-        }
-    }
-
-    public boolean isPatch() {
-        return mPatch != 0;
-    }
-
-    public boolean isSnapshot() {
-        return "-SNAPSHOT".equals(mExtra);
-    }
-
-    public int getMajor() {
-        return mMajor;
-    }
-
-    public int getMinor() {
-        return mMinor;
-    }
-
-    public int getPatch() {
-        return mPatch;
-    }
-
-    public String getExtra() {
-        return mExtra;
-    }
-
-    @Override
-    public String toString() {
-        return mMajor + "." + mMinor + "." + mPatch + (mExtra != null ? mExtra : "");
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        Version version = (Version) o;
-
-        if (mMajor != version.mMajor) return false;
-        if (mMinor != version.mMinor) return false;
-        if (mPatch != version.mPatch) return false;
-        return mExtra != null ? mExtra.equals(version.mExtra) : version.mExtra == null;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mMajor;
-        result = 31 * result + mMinor;
-        result = 31 * result + mPatch;
-        result = 31 * result + (mExtra != null ? mExtra.hashCode() : 0);
-        return result;
-    }
-
-    /**
-     * @return Version or null, if a name of the given file doesn't match
-     */
-    public static Version from(File file) {
-        if (!file.isFile()) {
-            return null;
-        }
-        Matcher matcher = VERSION_FILE_REGEX.matcher(file.getName());
-        if (!matcher.matches()) {
-            return null;
-        }
-        return new Version(matcher.group(1));
-    }
-
-    /**
-     * @return Version or null, if the given string doesn't match
-     */
-    public static Version from(String versionString) {
-        Matcher matcher = VERSION_REGEX.matcher(versionString);
-        if (!matcher.matches()) {
-            return null;
-        }
-        return new Version(matcher);
-    }
-}
diff --git a/buildSrc/src/main/java/android/support/VersionFileWriterTask.java b/buildSrc/src/main/java/android/support/VersionFileWriterTask.java
deleted file mode 100644
index aafa023..0000000
--- a/buildSrc/src/main/java/android/support/VersionFileWriterTask.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.support;
-
-import com.android.build.gradle.LibraryExtension;
-
-import org.gradle.api.Action;
-import org.gradle.api.DefaultTask;
-import org.gradle.api.Project;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.TaskAction;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintWriter;
-
-/**
- * Task that allows to write a version to a given output file.
- */
-public class VersionFileWriterTask extends DefaultTask {
-    public static final String RESOURCE_DIRECTORY = "generatedResources";
-    public static final String VERSION_FILE_PATH =
-            RESOURCE_DIRECTORY + "/META-INF/%s_%s.version";
-
-    private String mVersion;
-    private File mOutputFile;
-
-    /**
-     * Sets up Android Library project to have a task that generates a version file.
-     *
-     * @param project an Android Library project.
-     */
-    public static void setUpAndroidLibrary(Project project) {
-        project.afterEvaluate(new Action<Project>() {
-            @Override
-            public void execute(Project project) {
-                LibraryExtension library =
-                        project.getExtensions().findByType(LibraryExtension.class);
-
-                String group = (String) project.getProperties().get("group");
-                String artifactId = (String) project.getProperties().get("name");
-                String version = (String) project.getProperties().get("version");
-
-                // Add a java resource file to the library jar for version tracking purposes.
-                File artifactName = new File(project.getBuildDir(),
-                        String.format(VersionFileWriterTask.VERSION_FILE_PATH,
-                                group, artifactId));
-
-                VersionFileWriterTask writeVersionFile =
-                        project.getTasks().create("writeVersionFile", VersionFileWriterTask.class);
-                writeVersionFile.setVersion(version);
-                writeVersionFile.setOutputFile(artifactName);
-
-                library.getLibraryVariants().all(
-                        libraryVariant -> libraryVariant.getProcessJavaResources().dependsOn(
-                                writeVersionFile));
-
-                library.getSourceSets().getByName("main").getResources().srcDir(
-                        new File(project.getBuildDir(), VersionFileWriterTask.RESOURCE_DIRECTORY)
-                );
-            }
-        });
-    }
-
-    @Input
-    public String getVersion() {
-        return mVersion;
-    }
-
-    public void setVersion(String version) {
-        mVersion = version;
-    }
-
-    @OutputFile
-    public File getOutputFile() {
-        return mOutputFile;
-    }
-
-    public void setOutputFile(File outputFile) {
-        mOutputFile = outputFile;
-    }
-
-    /**
-     * The main method for actually writing out the file.
-     *
-     * @throws IOException
-     */
-    @TaskAction
-    public void run() throws IOException {
-        PrintWriter writer = new PrintWriter(mOutputFile);
-        writer.println(mVersion);
-        writer.close();
-    }
-}
diff --git a/buildSrc/src/main/kotlin/android/support/DiffAndDocs.kt b/buildSrc/src/main/kotlin/android/support/DiffAndDocs.kt
index fefc3ed..b96260c 100644
--- a/buildSrc/src/main/kotlin/android/support/DiffAndDocs.kt
+++ b/buildSrc/src/main/kotlin/android/support/DiffAndDocs.kt
@@ -30,7 +30,6 @@
 import org.gradle.api.artifacts.Configuration
 import org.gradle.api.file.FileCollection
 import org.gradle.api.plugins.JavaBasePlugin
-import org.gradle.api.tasks.Copy
 import org.gradle.api.tasks.TaskContainer
 import org.gradle.api.tasks.bundling.Zip
 import org.gradle.api.tasks.compile.JavaCompile
@@ -106,7 +105,7 @@
     var lastFile: File? = null
     var lastVersion: Version? = null
     apiDir.listFiles().forEach { file ->
-        Version.from(file)?.let { version ->
+        Version.parseOrNull(file)?.let { version ->
             if ((lastFile == null || lastVersion!! < version) && version < refVersion) {
                 lastFile = file
                 lastVersion = version
@@ -129,7 +128,7 @@
 private fun getApiFile(rootDir: File, refVersion: Version, forceRelease: Boolean = false): File {
     val apiDir = File(rootDir, "api")
 
-    if (!refVersion.isSnapshot || forceRelease) {
+    if (!refVersion.isSnapshot() || forceRelease) {
         // Release API file is always X.Y.0.txt.
         return File(apiDir, "${refVersion.major}.${refVersion.minor}.0.txt")
     }
@@ -147,13 +146,13 @@
                 val rootFolder = project.projectDir
                 val version = Version(project.version as String)
 
-                if (version.isPatch) {
+                if (version.isPatch()) {
                     throw GradleException("Public APIs may not be modified in patch releases.")
-                } else if (version.isSnapshot && getApiFile(rootFolder,
+                } else if (version.isSnapshot() && getApiFile(rootFolder,
                         version,
                         true).exists()) {
                     throw GradleException("Inconsistent version. Public API file already exists.")
-                } else if (!version.isSnapshot && getApiFile(rootFolder, version).exists()
+                } else if (!version.isSnapshot() && getApiFile(rootFolder, version).exists()
                         && !project.hasProperty("force")) {
                     throw GradleException("Public APIs may not be modified in finalized releases.")
                 }
@@ -166,7 +165,7 @@
             setDocletpath(docletpathParam)
             destinationDir = project.docsDir()
             // Base classpath is Android SDK, sub-projects add their own.
-            classpath = project.androidJar()
+            classpath = androidJarFile(project)
             apiFile = File(project.docsDir(), "release/${project.name}/current.txt")
             generateDocs = false
 
@@ -247,7 +246,7 @@
  */
 private fun createOldApiXml(project: Project, doclavaConfig: Configuration) =
         project.tasks.createWithConfig("oldApiXml", ApiXmlConversionTask::class.java) {
-            val toApi = project.processProperty("toApi")?.let(Version::from)
+            val toApi = project.processProperty("toApi")?.let { Version.parseOrNull(it) }
             val fromApi = project.processProperty("fromApi")
             classpath = project.files(doclavaConfig.resolve())
             val rootFolder = project.projectDir
@@ -329,7 +328,7 @@
         jdiffConfig: Configuration) =
         project.tasks.createWithConfig("generateDiffs", JDiffTask::class.java) {
             // Base classpath is Android SDK, sub-projects add their own.
-            classpath = project.androidJar()
+            classpath = androidJarFile(project)
 
             // JDiff properties.
             oldApiXmlFile = oldApiTask.outputApiXmlFile
@@ -358,7 +357,7 @@
             from(generateDocs.destinationDir)
             baseName = "android-support-docs"
             version = project.buildNumber()
-
+            destinationDir = project.distDir()
             doLast {
                 logger.lifecycle("'Wrote API reference to $archivePath")
             }
@@ -366,33 +365,18 @@
 
 // Set up platform API files for federation.
 private fun createGenerateSdkApiTask(project: Project, doclavaConfig: Configuration): Task =
-        if (project.androidApiTxt() != null) {
-            project.tasks.createWithConfig("generateSdkApi", Copy::class.java) {
-                description = "Copies the API files for the current SDK."
-                // Export the API files so this looks like a DoclavaTask.
-                from(project.androidApiTxt()!!.absolutePath)
-                val apiFile = sdkApiFile(project)
-                into(apiFile.parent)
-                rename { apiFile.name }
-                // Register the fake removed file as an output.
-                val removedApiFile = removedSdkApiFile(project)
-                outputs.file(removedApiFile)
-                doLast { removedApiFile.createNewFile() }
-            }
-        } else {
-            project.tasks.createWithConfig("generateSdkApi", DoclavaTask::class.java) {
-                dependsOn(doclavaConfig)
-                description = "Generates API files for the current SDK."
-                setDocletpath(doclavaConfig.resolve())
-                destinationDir = project.docsDir()
-                classpath = project.androidJar()
-                source(project.zipTree(project.androidSrcJar()))
-                apiFile = sdkApiFile(project)
-                removedApiFile = removedSdkApiFile(project)
-                generateDocs = false
-                coreJavadocOptions {
-                    addStringOption("stubpackages", "android.*")
-                }
+        project.tasks.createWithConfig("generateSdkApi", DoclavaTask::class.java) {
+            dependsOn(doclavaConfig)
+            description = "Generates API files for the current SDK."
+            setDocletpath(doclavaConfig.resolve())
+            destinationDir = project.docsDir()
+            classpath = androidJarFile(project)
+            source(project.zipTree(androidSrcJarFile(project)))
+            apiFile = sdkApiFile(project)
+            removedApiFile = removedSdkApiFile(project)
+            generateDocs = false
+            coreJavadocOptions {
+                addStringOption("stubpackages", "android.*")
             }
         }
 
@@ -411,7 +395,7 @@
             setDocletpath(doclavaConfig.resolve())
             val offline = project.processProperty("offlineDocs") != null
             destinationDir = File(project.docsDir(), if (offline) "offline" else "online")
-            classpath = project.androidJar()
+            classpath = androidJarFile(project)
             val hidden = listOf<Int>(105, 106, 107, 111, 112, 113, 115, 116, 121)
             doclavaErrors = ((101..122) - hidden).toSet()
             doclavaWarnings = emptySet()
@@ -490,7 +474,7 @@
     }
 
     // Check whether the development API surface has changed.
-    val verifyConfig = if (version.isPatch) CHECK_API_CONFIG_PATCH else CHECK_API_CONFIG_DEVELOP
+    val verifyConfig = if (version.isPatch()) CHECK_API_CONFIG_PATCH else CHECK_API_CONFIG_DEVELOP
     val currentApiFile = getApiFile(workingDir, version)
     val checkApi = createCheckApiTask(project,
             "checkApi",
@@ -602,18 +586,23 @@
         config: T.() -> Unit) =
         create(name, taskClass) { task -> task.config() }
 
+private fun androidJarFile(project: Project): FileCollection =
+        project.files(arrayOf(File(project.fullSdkPath(),
+                "platforms/android-${SupportConfig.CURRENT_SDK_VERSION}/android.jar")))
+
+private fun androidSrcJarFile(project: Project): File = File(project.fullSdkPath(),
+        "platforms/android-${SupportConfig.CURRENT_SDK_VERSION}/android-stubs-src.jar")
+
 // Nasty part. Get rid of that eventually!
 private fun Project.docsDir(): File = properties["docsDir"] as File
 
-private fun Project.androidJar() = rootProject.properties["androidJar"] as FileCollection
-
-private fun Project.androidSrcJar() = rootProject.properties["androidSrcJar"] as File
+private fun Project.fullSdkPath(): File = rootProject.properties["fullSdkPath"] as File
 
 private fun Project.version() = Version(project.version as String)
 
 private fun Project.buildNumber() = properties["buildNumber"] as String
 
-private fun Project.androidApiTxt() = properties["androidApiTxt"] as? File
+private fun Project.distDir(): File = rootProject.properties["distDir"] as File
 
 private fun Project.processProperty(name: String) =
         if (hasProperty(name)) {
diff --git a/buildSrc/src/main/kotlin/android/support/GroovyInteroperability.kt b/buildSrc/src/main/kotlin/android/support/GroovyInteroperability.kt
deleted file mode 100644
index ec2d2f6..0000000
--- a/buildSrc/src/main/kotlin/android/support/GroovyInteroperability.kt
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright 2016 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR 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
-
-import groovy.lang.Closure
-import groovy.lang.GroovyObject
-import groovy.lang.MetaClass
-
-import org.codehaus.groovy.runtime.InvokerHelper.getMetaClass
-
-operator fun <T> Closure<T>.invoke(): T = call()
-
-operator fun <T> Closure<T>.invoke(x: Any?): T = call(x)
-
-operator fun <T> Closure<T>.invoke(vararg xs: Any?): T = call(*xs)
-
-
-/**
- * Executes the given [builder] against this object's [GroovyBuilderScope].
- *
- * @see [GroovyBuilderScope]
- */
-inline
-fun <T> Any.withGroovyBuilder(builder: GroovyBuilderScope.() -> T): T =
-        GroovyBuilderScope.of(this).builder()
-
-
-/**
- * Provides a dynamic dispatching DSL with Groovy semantics for better integration with
- * plugins that rely on Groovy builders such as the core `maven` plugin.
- *
- * It supports Groovy keyword arguments and arbitrary nesting, for instance, the following Groovy code:
- *
- * ```Groovy
- * repository(url: "scp://repos.mycompany.com/releases") {
- *   authentication(userName: "me", password: "myPassword")
- * }
- * ```
- *
- * Can be mechanically translated to the following Kotlin with the aid of `withGroovyBuilder`:
- *
- * ```Kotlin
- * withGroovyBuilder {
- *   "repository"("url" to "scp://repos.mycompany.com/releases") {
- *     "authentication"("userName" to "me", "password" to "myPassword")
- *   }
- * }
- * ```
- *
- * @see [withGroovyBuilder]
- */
-interface GroovyBuilderScope : GroovyObject {
-
-    companion object {
-
-        fun of(value: Any): GroovyBuilderScope =
-                when (value) {
-                    is GroovyObject -> GroovyBuilderScopeForGroovyObject(value)
-                    else            -> GroovyBuilderScopeForRegularObject(value)
-                }
-    }
-
-    val delegate: Any
-
-    operator fun String.invoke(vararg arguments: Any?): Any?
-
-    operator fun String.invoke(): Any? =
-            invoke(*emptyArray<Any>())
-
-    operator fun <T> String.invoke(vararg arguments: Any?, builder: GroovyBuilderScope.() -> T): Any? =
-            invoke(*arguments, closureFor(builder))
-
-    operator fun <T> String.invoke(builder: GroovyBuilderScope.() -> T): Any? =
-            invoke(closureFor(builder))
-
-    operator fun <T> String.invoke(vararg keywordArguments: Pair<String, Any?>, builder: GroovyBuilderScope.() -> T): Any? =
-            invoke(keywordArguments.toMap(), closureFor(builder))
-
-    operator fun String.invoke(vararg keywordArguments: Pair<String, Any?>): Any? =
-            invoke(keywordArguments.toMap())
-
-    private
-    fun <T> closureFor(builder: GroovyBuilderScope.() -> T): Closure<Any?> =
-            object : Closure<Any?>(this, this) {
-                @Suppress("unused")
-                fun doCall() = delegate.withGroovyBuilder(builder)
-            }
-}
-
-
-private
-class GroovyBuilderScopeForGroovyObject(override val delegate: GroovyObject) : GroovyBuilderScope, GroovyObject by delegate {
-
-    override fun String.invoke(vararg arguments: Any?): Any? =
-            delegate.invokeMethod(this, arguments)
-}
-
-
-private
-class GroovyBuilderScopeForRegularObject(override val delegate: Any) : GroovyBuilderScope {
-
-    private
-    val groovyMetaClass: MetaClass by lazy {
-        getMetaClass(delegate)
-    }
-
-    override fun invokeMethod(name: String, args: Any?): Any? =
-            groovyMetaClass.invokeMethod(delegate, name, args)
-
-    override fun setProperty(propertyName: String, newValue: Any?) =
-            groovyMetaClass.setProperty(delegate, propertyName, newValue)
-
-    override fun getProperty(propertyName: String): Any =
-            groovyMetaClass.getProperty(delegate, propertyName)
-
-    override fun setMetaClass(metaClass: MetaClass?) =
-            throw IllegalStateException()
-
-    override fun getMetaClass(): MetaClass =
-            groovyMetaClass
-
-    override fun String.invoke(vararg arguments: Any?): Any? =
-            groovyMetaClass.invokeMethod(delegate, this, arguments)
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/android/support/MavenUploadHelper.kt b/buildSrc/src/main/kotlin/android/support/MavenUploadHelper.kt
index 1698bf9..c8be47a 100644
--- a/buildSrc/src/main/kotlin/android/support/MavenUploadHelper.kt
+++ b/buildSrc/src/main/kotlin/android/support/MavenUploadHelper.kt
@@ -21,15 +21,17 @@
 import org.gradle.api.artifacts.ProjectDependency
 import org.gradle.api.artifacts.maven.MavenDeployer
 import org.gradle.api.tasks.Upload
+import org.gradle.kotlin.dsl.withGroovyBuilder
+import java.io.File
 
 fun apply(project: Project, extension: SupportLibraryExtension) {
     project.afterEvaluate {
         if (extension.publish) {
             if (extension.mavenGroup == null) {
-                throw Exception("You must specify mavenGroup for " + project.name + " project");
+                throw Exception("You must specify mavenGroup for ${project.name}  project")
             }
             if (extension.mavenVersion == null) {
-                throw Exception("You must specify mavenVersion for " + project.name + " project");
+                throw Exception("You must specify mavenVersion for ${project.name}  project")
             }
             project.group = extension.mavenGroup!!
             project.version = extension.mavenVersion.toString()
@@ -40,9 +42,22 @@
 
     // Set uploadArchives options.
     val uploadTask = project.tasks.getByName("uploadArchives") as Upload
+
+    val repo = project.uri(project.rootProject.property("supportRepoOut") as File)
+            ?: throw Exception("supportRepoOut not set")
+
+    uploadTask.repositories {
+        it.withGroovyBuilder {
+            "mavenDeployer" {
+                "repository"(mapOf("url" to repo))
+            }
+        }
+    }
+
     project.afterEvaluate {
         if (extension.publish) {
             uploadTask.repositories.withType(MavenDeployer::class.java) { mavenDeployer ->
+                mavenDeployer.isUniqueVersion = true
 
                 mavenDeployer.getPom().project {
                     it.withGroovyBuilder {
@@ -68,7 +83,7 @@
 
                         "scm" {
                             "url"("http://source.android.com")
-                            "connection"("scm:git:https://android.googlesource.com/platform/frameworks/support")
+                            "connection"(ANDROID_GIT_URL)
                         }
 
                         "developers" {
@@ -83,9 +98,9 @@
                 // https://github.com/gradle/gradle/issues/3170
                 uploadTask.doFirst {
                     val allDeps = HashSet<ProjectDependency>()
-                    collectDependenciesForConfiguration(allDeps, project, "api");
-                    collectDependenciesForConfiguration(allDeps, project, "implementation");
-                    collectDependenciesForConfiguration(allDeps, project, "compile");
+                    collectDependenciesForConfiguration(allDeps, project, "api")
+                    collectDependenciesForConfiguration(allDeps, project, "implementation")
+                    collectDependenciesForConfiguration(allDeps, project, "compile")
 
                     mavenDeployer.getPom().whenConfigured {
                         it.dependencies.forEach { dep ->
@@ -95,10 +110,10 @@
 
                             val getGroupIdMethod =
                                     dep::class.java.getDeclaredMethod("getGroupId")
-                            val groupId : String = getGroupIdMethod.invoke(dep) as String
+                            val groupId: String = getGroupIdMethod.invoke(dep) as String
                             val getArtifactIdMethod =
                                     dep::class.java.getDeclaredMethod("getArtifactId")
-                            val artifactId : String = getArtifactIdMethod.invoke(dep) as String
+                            val artifactId: String = getArtifactIdMethod.invoke(dep) as String
 
                             if (isAndroidProject(groupId, artifactId, allDeps)) {
                                 val setTypeMethod = dep::class.java.getDeclaredMethod("setType",
@@ -109,14 +124,23 @@
                     }
                 }
             }
+
+            // Before the upload, make sure the repo is ready.
+            uploadTask.dependsOn(project.rootProject.tasks.getByName("prepareRepo"))
+
+            // Make the mainUpload depend on this uploadTask one.
+            project.rootProject.tasks.getByName("mainUpload").dependsOn(uploadTask)
         } else {
             uploadTask.enabled = false
         }
     }
 }
 
-private fun collectDependenciesForConfiguration(projectDependencies : MutableSet<ProjectDependency>,
-                                                project : Project, name : String) {
+private fun collectDependenciesForConfiguration(
+        projectDependencies: MutableSet<ProjectDependency>,
+        project: Project,
+        name: String
+) {
     val config = project.configurations.findByName(name)
     if (config != null) {
         config.dependencies.withType(ProjectDependency::class.java).forEach {
@@ -125,12 +149,18 @@
     }
 }
 
-private fun isAndroidProject(groupId : String, artifactId : String,
-                             deps : Set<ProjectDependency>) : Boolean {
+private fun isAndroidProject(
+        groupId: String,
+        artifactId: String,
+        deps: Set<ProjectDependency>
+): Boolean {
     for (dep in deps) {
         if (dep.group == groupId && dep.name == artifactId) {
             return dep.getDependencyProject().plugins.hasPlugin(LibraryPlugin::class.java)
         }
     }
     return false
-}
\ No newline at end of file
+}
+
+private const val ANDROID_GIT_URL =
+        "scm:git:https://android.googlesource.com/platform/frameworks/support"
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/android/support/SupportAndroidLibraryPlugin.kt b/buildSrc/src/main/kotlin/android/support/SupportAndroidLibraryPlugin.kt
index e55f383..c660604 100644
--- a/buildSrc/src/main/kotlin/android/support/SupportAndroidLibraryPlugin.kt
+++ b/buildSrc/src/main/kotlin/android/support/SupportAndroidLibraryPlugin.kt
@@ -19,6 +19,7 @@
 import android.support.SupportConfig.INSTRUMENTATION_RUNNER
 import com.android.build.gradle.LibraryExtension
 import com.android.build.gradle.internal.dsl.LintOptions
+import com.android.build.gradle.tasks.GenerateBuildConfig
 import net.ltgt.gradle.errorprone.ErrorProneBasePlugin
 import net.ltgt.gradle.errorprone.ErrorProneToolChain
 import org.gradle.api.JavaVersion
@@ -72,13 +73,22 @@
 
             library.compileOptions.setSourceCompatibility(javaVersion)
             library.compileOptions.setTargetCompatibility(javaVersion)
-        }
 
-        VersionFileWriterTask.setUpAndroidLibrary(project)
+            VersionFileWriterTask.setUpAndroidLibrary(project, library)
+        }
 
         project.apply(mapOf("plugin" to "com.android.library"))
         project.apply(mapOf("plugin" to ErrorProneBasePlugin::class.java))
 
+        project.afterEvaluate {
+            project.tasks.all({
+                if (it is GenerateBuildConfig) {
+                    // Disable generating BuildConfig.java
+                    it.enabled = false
+                }
+            })
+        }
+
         project.configurations.all { configuration ->
             if (isCoreSupportLibrary && project.name != "support-annotations") {
                 // While this usually happens naturally due to normal project dependencies, force
@@ -102,16 +112,13 @@
         val library = project.extensions.findByType(LibraryExtension::class.java)
                 ?: throw Exception("Failed to find Android extension")
 
-        val currentSdk = project.property("currentSdk")
-        when (currentSdk) {
-            is Int -> library.compileSdkVersion(currentSdk)
-            is String -> library.compileSdkVersion(currentSdk)
-        }
+        library.compileSdkVersion(SupportConfig.CURRENT_SDK_VERSION)
 
-        library.buildToolsVersion = SupportConfig.getBuildTools(project)
+        library.buildToolsVersion = SupportConfig.BUILD_TOOLS_VERSION
 
         // Update the version meta-data in each Manifest.
-        library.defaultConfig.addManifestPlaceholders(mapOf("target-sdk-version" to currentSdk))
+        library.defaultConfig.addManifestPlaceholders(
+                mapOf("target-sdk-version" to SupportConfig.CURRENT_SDK_VERSION))
 
         // Set test runner.
         library.defaultConfig.testInstrumentationRunner = INSTRUMENTATION_RUNNER
@@ -122,13 +129,13 @@
         library.signingConfigs.findByName("debug")?.storeFile =
                 SupportConfig.getKeystore(project)
 
-        setUpLint(library.lintOptions, SupportConfig.getLintBaseline(project))
-
-        if (SupportConfig.isUsingFullSdk(project)) {
-            // Library projects don't run lint by default, so set up dependency.
-            project.tasks.getByName("uploadArchives").dependsOn("lintRelease")
+        project.afterEvaluate {
+            setUpLint(library.lintOptions, SupportConfig.getLintBaseline(project),
+                    (supportLibraryExtension.mavenVersion?.isSnapshot()) ?: true)
         }
 
+        project.tasks.getByName("uploadArchives").dependsOn("lintRelease")
+
         SourceJarTaskHelper.setUpAndroidProject(project, library)
 
         val toolChain = ErrorProneToolChain.create(project)
@@ -160,7 +167,7 @@
     }
 }
 
-private fun setUpLint(lintOptions: LintOptions, baseline: File) {
+private fun setUpLint(lintOptions: LintOptions, baseline: File, snapshotVersion: Boolean) {
     // Always lint check NewApi as fatal.
     lintOptions.isAbortOnError = true
     lintOptions.isIgnoreWarnings = true
@@ -178,12 +185,21 @@
     lintOptions.isNoLines = false
     lintOptions.isQuiet = true
 
-    lintOptions.error("NewApi")
+    lintOptions.fatal("NewApi")
 
-    // Set baseline file for all legacy lint warnings.
+    if (snapshotVersion) {
+        // Do not run missing translations checks on snapshot versions of the library.
+        lintOptions.disable("MissingTranslation")
+    } else {
+        lintOptions.fatal("MissingTranslation")
+    }
+
     if (System.getenv("GRADLE_PLUGIN_VERSION") != null) {
         lintOptions.check("NewApi")
-    } else if (baseline.exists()) {
+    }
+
+    // Set baseline file for all legacy lint warnings.
+    if (baseline.exists()) {
         lintOptions.baseline(baseline)
     }
 }
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/android/support/SupportAndroidTestAppPlugin.kt b/buildSrc/src/main/kotlin/android/support/SupportAndroidTestAppPlugin.kt
index 6d73c99..89e8177 100644
--- a/buildSrc/src/main/kotlin/android/support/SupportAndroidTestAppPlugin.kt
+++ b/buildSrc/src/main/kotlin/android/support/SupportAndroidTestAppPlugin.kt
@@ -42,19 +42,10 @@
         val application = project.extensions.findByType(AppExtension::class.java)
                 ?: throw Exception("Failed to find Android extension")
 
-        val currentSdk = project.property("currentSdk")
-        when (currentSdk) {
-            is Int -> {
-                application.compileSdkVersion(currentSdk)
-                application.defaultConfig.targetSdkVersion(currentSdk)
-            }
-            is String -> {
-                application.compileSdkVersion(currentSdk)
-                application.defaultConfig.targetSdkVersion(currentSdk)
-            }
-        }
+        application.compileSdkVersion(SupportConfig.CURRENT_SDK_VERSION)
+        application.defaultConfig.targetSdkVersion(SupportConfig.CURRENT_SDK_VERSION)
 
-        application.buildToolsVersion = SupportConfig.getBuildTools(project)
+        application.buildToolsVersion = SupportConfig.BUILD_TOOLS_VERSION
 
         application.defaultConfig.versionCode = 1
         application.defaultConfig.versionName = "1.0"
diff --git a/buildSrc/src/main/kotlin/android/support/SupportConfig.kt b/buildSrc/src/main/kotlin/android/support/SupportConfig.kt
index 26c3bf3..94573c6 100644
--- a/buildSrc/src/main/kotlin/android/support/SupportConfig.kt
+++ b/buildSrc/src/main/kotlin/android/support/SupportConfig.kt
@@ -23,10 +23,8 @@
 object SupportConfig {
     const val DEFAULT_MIN_SDK_VERSION = 14
     const val INSTRUMENTATION_RUNNER = "android.support.test.runner.AndroidJUnitRunner"
-
-    fun getBuildTools(project: Project): String {
-        return project.rootProject.property("buildToolsVersion") as String
-    }
+    const val BUILD_TOOLS_VERSION = "27.0.1"
+    const val CURRENT_SDK_VERSION = 27
 
     fun getKeystore(project: Project): File {
         val supportRoot = (project.rootProject.property("ext") as ExtraPropertiesExtension)
@@ -34,10 +32,6 @@
         return File(supportRoot, "development/keystore/debug.keystore")
     }
 
-    fun isUsingFullSdk(project: Project): Boolean {
-        return project.rootProject.property("usingFullSdk") as Boolean
-    }
-
     fun getLintBaseline(project: Project): File {
         return File(project.projectDir, "/lint-baseline.xml")
     }
diff --git a/buildSrc/src/main/kotlin/android/support/Version.kt b/buildSrc/src/main/kotlin/android/support/Version.kt
new file mode 100644
index 0000000..0a9d945
--- /dev/null
+++ b/buildSrc/src/main/kotlin/android/support/Version.kt
@@ -0,0 +1,85 @@
+/*
+ * 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
+
+import java.io.File
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
+/**
+ * Utility class which represents a version
+ */
+data class Version(
+        val major: Int,
+        val minor: Int,
+        val patch: Int,
+        val extra: String? = null
+) : Comparable<Version> {
+
+    constructor(versionString: String) : this(
+            Integer.parseInt(checkedMatcher(versionString).group(1)),
+            Integer.parseInt(checkedMatcher(versionString).group(2)),
+            Integer.parseInt(checkedMatcher(versionString).group(3)),
+            if (checkedMatcher(versionString).groupCount() == 4) checkedMatcher(
+                    versionString).group(4) else null)
+
+    fun isPatch(): Boolean = patch != 0
+
+    fun isSnapshot(): Boolean = "-SNAPSHOT" == extra
+
+    override fun compareTo(other: Version) = compareValuesBy(this, other,
+            { it.major },
+            { it.minor },
+            { it.patch },
+            { it.extra == null }, // False (no extra) sorts above true (has extra)
+            { it.extra } // gradle uses lexicographic ordering
+    )
+
+    override fun toString(): String {
+        return "$major.$minor.$patch${extra ?: ""}"
+    }
+
+    companion object {
+        private val VERSION_FILE_REGEX = Pattern.compile("^(\\d+\\.\\d+\\.\\d+).txt$")
+        private val VERSION_REGEX = Pattern.compile("^(\\d+)\\.(\\d+)\\.(\\d+)(-.+)?$")
+
+        private fun checkedMatcher(versionString: String): Matcher {
+            val matcher = VERSION_REGEX.matcher(versionString)
+            if (!matcher.matches()) {
+                throw IllegalArgumentException("Can not parse version: " + versionString)
+            }
+            return matcher
+        }
+
+        /**
+         * @return Version or null, if a name of the given file doesn't match
+         */
+        fun parseOrNull(file: File): Version? {
+            if (!file.isFile) return null
+            val matcher = VERSION_FILE_REGEX.matcher(file.name)
+            return if (matcher.matches()) Version(matcher.group(1)) else null
+        }
+
+        /**
+         * @return Version or null, if the given string doesn't match
+         */
+        fun parseOrNull(versionString: String): Version? {
+            val matcher = VERSION_REGEX.matcher(versionString)
+            return if (matcher.matches()) Version(versionString) else null
+        }
+    }
+}
diff --git a/buildSrc/src/main/kotlin/android/support/VersionFileWriterTask.kt b/buildSrc/src/main/kotlin/android/support/VersionFileWriterTask.kt
new file mode 100644
index 0000000..cde11e6
--- /dev/null
+++ b/buildSrc/src/main/kotlin/android/support/VersionFileWriterTask.kt
@@ -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 android.support
+
+import com.android.build.gradle.LibraryExtension
+import org.gradle.api.DefaultTask
+import org.gradle.api.Project
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import java.io.File
+import java.io.PrintWriter
+
+/**
+ * Task that allows to write a version to a given output file.
+ */
+open class VersionFileWriterTask : DefaultTask() {
+    @get:Input
+    lateinit var version: String
+    @get:OutputFile
+    lateinit var outputFile: File
+
+    /**
+     * The main method for actually writing out the file.
+     */
+    @TaskAction
+    fun run() {
+        val writer = PrintWriter(outputFile)
+        writer.println(version)
+        writer.close()
+    }
+
+    companion object {
+        val RESOURCE_DIRECTORY = "generatedResources"
+        val VERSION_FILE_PATH = RESOURCE_DIRECTORY + "/META-INF/%s_%s.version"
+
+        /**
+         * Sets up Android Library project to have a task that generates a version file.
+         * It must be called after [LibraryExtension] has been resolved.
+         *
+         * @param project an Android Library project.
+         */
+        fun setUpAndroidLibrary(project: Project, library: LibraryExtension) {
+            val group = project.properties["group"] as String
+            val artifactId = project.properties["name"] as String
+            val version = project.properties["version"] as String
+
+            // Add a java resource file to the library jar for version tracking purposes.
+            val artifactName = File(
+                    project.buildDir,
+                    String.format(VERSION_FILE_PATH, group, artifactId))
+
+            val writeVersionFile = project.tasks.create("writeVersionFile",
+                    VersionFileWriterTask::class.java)
+            writeVersionFile.version = version
+            writeVersionFile.outputFile = artifactName
+
+            library.libraryVariants.all {
+                it.processJavaResources.dependsOn(writeVersionFile)
+            }
+
+            library.sourceSets.getByName("main").resources.srcDir(
+                    File(project.buildDir, RESOURCE_DIRECTORY)
+            )
+        }
+    }
+}
diff --git a/buildSrc/src/main/kotlin/android/support/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/android/support/dependencies/Dependencies.kt
index 48103d2..e370801 100644
--- a/buildSrc/src/main/kotlin/android/support/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/android/support/dependencies/Dependencies.kt
@@ -57,4 +57,5 @@
 const val SUPPORT_V4 = "com.android.support:support-v4:$SUPPORT_VERSION"
 
 // Arch libraries
-const val ARCH_LIFECYCLE_RUNTIME = "android.arch.lifecycle:runtime:1.0.3@aar"
\ No newline at end of file
+const val ARCH_LIFECYCLE_RUNTIME = "android.arch.lifecycle:runtime:1.0.3@aar"
+const val ARCH_LIFECYCLE_EXTENSIONS = "android.arch.lifecycle:extensions:1.0.0@aar"
diff --git a/buildSrc/src/main/kotlin/android/support/docs/GenerateDocsTask.kt b/buildSrc/src/main/kotlin/android/support/docs/GenerateDocsTask.kt
index 2183b88..e889a54 100644
--- a/buildSrc/src/main/kotlin/android/support/docs/GenerateDocsTask.kt
+++ b/buildSrc/src/main/kotlin/android/support/docs/GenerateDocsTask.kt
@@ -48,7 +48,7 @@
 
     fun addSinceFilesFrom(dir: File) {
         File(dir, "api").listFiles().forEach { file ->
-            Version.from(file)?.let { version ->
+            Version.parseOrNull(file)?.let { version ->
                 sinces.add(Since(file.absolutePath, version.toString()))
             }
         }
diff --git a/buildSrc/src/main/kotlin/android/support/gmaven/GMavenVersionChecker.kt b/buildSrc/src/main/kotlin/android/support/gmaven/GMavenVersionChecker.kt
new file mode 100644
index 0000000..7fc4836
--- /dev/null
+++ b/buildSrc/src/main/kotlin/android/support/gmaven/GMavenVersionChecker.kt
@@ -0,0 +1,180 @@
+/*
+ * 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.gmaven
+
+import android.support.Version
+import groovy.util.XmlSlurper
+import groovy.util.slurpersupport.Node
+import groovy.util.slurpersupport.NodeChild
+import org.gradle.api.GradleException
+import org.gradle.api.logging.Logger
+import java.io.FileNotFoundException
+import java.io.IOException
+
+/**
+ * Queries maven.google.com to get the version numbers for each artifact.
+ * Due to the structure of maven.google.com, a new query is necessary for each group.
+ *
+ * @param logger Logger of the root project. No reason to create multiple instances of this.
+ */
+class GMavenVersionChecker(private val logger: Logger) {
+    private val versionCache: MutableMap<String, GroupVersionData> = HashMap()
+
+    /**
+     * Checks whether the given artifact is already on maven.google.com.
+     *
+     * @param group The project group on maven
+     * @param artifactName The artifact name on maven
+     * @param version The version on maven
+     * @return true if the artifact is already on maven.google.com
+     */
+    fun isReleased(group: String, artifactName: String, version: String): Boolean {
+        return getVersions(group, artifactName)?.contains(Version(version)) ?: false
+    }
+
+    /**
+     * Return the available versions on maven.google.com for a given artifact
+     *
+     * @param group The group id of the artifact
+     * @param artifactName The name of the artifact
+     * @return The set of versions that are available on maven.google.com. Null if artifact is not
+     *         available.
+     */
+    private fun getVersions(group: String, artifactName: String): Set<Version>? {
+        val groupData = getVersionData(group)
+        return groupData?.artifacts?.get(artifactName)?.versions
+    }
+
+    /**
+     * Returns the version data for each artifact in a given group.
+     * <p>
+     * If data is not cached, this will make a web request to get it.
+     *
+     * @param group The group to query
+     * @return A data class which has the versions for each artifact
+     */
+    private fun getVersionData(group: String): GroupVersionData? {
+        return versionCache.getOrMaybePut(group) {
+            fetchGroup(group, DEFAULT_RETRY_LIMIT)
+        }
+    }
+
+    /**
+     * Fetches the group version information from maven.google.com
+     *
+     * @param group The group name to fetch
+     * @param retryCount Number of times we'll retry before failing
+     * @return GroupVersionData that has the data or null if it is a new item.
+     */
+    private fun fetchGroup(group: String, retryCount: Int): GroupVersionData? {
+        val url = buildGroupUrl(group)
+        for (run in 0..retryCount) {
+            logger.info("fetching maven XML from $url")
+            try {
+                val parsedXml = XmlSlurper(false, false).parse(url) as NodeChild
+                return GroupVersionData.from(parsedXml)
+            } catch (ignored: FileNotFoundException) {
+                logger.info("could not find version data for $group, seems like a new file")
+                return null
+            } catch (ioException: IOException) {
+                logger.warn("failed to fetch the maven info, retrying in 2 seconds. " +
+                        "Run $run of $retryCount")
+                Thread.sleep(RETRY_DELAY)
+            }
+        }
+        throw GradleException("Could not access maven.google.com")
+    }
+
+    companion object {
+        /**
+         * Creates the URL which has the XML file that describes the available versions for each
+         * artifact in that group
+         *
+         * @param group Maven group name
+         * @return The URL of the XML file
+         */
+        private fun buildGroupUrl(group: String) =
+                "$BASE${group.replace(".","/")}/$GROUP_FILE"
+    }
+}
+
+private fun <K, V> MutableMap<K, V>.getOrMaybePut(key: K, defaultValue: () -> V?): V? {
+    val value = get(key)
+    return if (value == null) {
+        val answer = defaultValue()
+        if (answer != null) put(key, answer)
+        answer
+    } else {
+        value
+    }
+}
+
+/**
+ * Data class that holds the artifacts of a single maven group.
+ *
+ * @param name Maven group name
+ * @param artifacts Map of artifact versions keyed by artifact name
+ */
+private data class GroupVersionData(
+        val name: String,
+        val artifacts: Map<String, ArtifactVersionData>
+) {
+    companion object {
+        /**
+         * Constructs an instance from the given node.
+         *
+         * @param xml The information node fetched from {@code GROUP_FILE}
+         */
+        fun from(xml: NodeChild): GroupVersionData {
+            /*
+             * sample input:
+             * <android.arch.core>
+             *   <runtime versions="1.0.0-alpha4,1.0.0-alpha5,1.0.0-alpha6,1.0.0-alpha7"/>
+             *   <common versions="1.0.0-alpha4,1.0.0-alpha5,1.0.0-alpha6,1.0.0-alpha7"/>
+             * </android.arch.core>
+             */
+            val name = xml.name()
+            val artifacts: MutableMap<String, ArtifactVersionData> = HashMap()
+
+            xml.childNodes().forEach {
+                val node = it as Node
+                val versions = (node.attributes()["versions"] as String).split(",").map {
+                    Version(it)
+                }.toSet()
+                artifacts.put(it.name(), ArtifactVersionData(it.name(), versions))
+            }
+            return GroupVersionData(name, artifacts)
+        }
+    }
+}
+
+/**
+ * Data class that holds the version information about a single artifact
+ *
+ * @param name Name of the maven artifact
+ * @param versions set of version codes that are already on maven.google.com
+ */
+private data class ArtifactVersionData(val name: String, val versions: Set<Version>)
+
+// wait 2 seconds before retrying if fetch fails
+private const val RETRY_DELAY: Long = 2000 // ms
+
+// number of times we'll try to reach maven.google.com before failing
+private const val DEFAULT_RETRY_LIMIT = 20
+
+private const val BASE = "https://dl.google.com/dl/android/maven2/"
+private const val GROUP_FILE = "group-index.xml"
\ No newline at end of file
diff --git a/buildSrc/src/test/kotlin/android/support/VersionTest.kt b/buildSrc/src/test/kotlin/android/support/VersionTest.kt
new file mode 100644
index 0000000..339acab
--- /dev/null
+++ b/buildSrc/src/test/kotlin/android/support/VersionTest.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.support
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class VersionTest {
+    @Test
+    fun testComparisons() {
+        assert(true > false)
+
+        val version2600 = Version("26.0.0")
+        val version2610 = Version("26.1.0")
+        val version2611 = Version("26.1.1")
+        val version2620 = Version("26.2.0")
+        val version2621 = Version("26.2.1")
+        val version2700 = Version("27.0.0")
+        val version2700SNAPSHOT = Version("27.0.0-SNAPSHOT")
+        val version2700TNAPSHOT = Version("27.0.0-TNAPSHOT")
+
+        assertEquals(version2600, version2600)
+
+        assert(version2600 < version2700)
+
+        assert(version2600 < version2700)
+
+        assert(version2610 < version2611)
+        assert(version2610 < version2620)
+        assert(version2610 < version2621)
+        assert(version2610 < version2700)
+
+        assert(version2611 < version2620)
+        assert(version2611 < version2621)
+        assert(version2611 < version2700)
+
+        assert(version2700 > version2600)
+        assert(version2700 > version2700SNAPSHOT)
+        assert(version2700SNAPSHOT < version2700)
+
+        assert(version2700TNAPSHOT > version2700SNAPSHOT)
+        assert(version2700SNAPSHOT < version2700TNAPSHOT)
+    }
+}
\ No newline at end of file
diff --git a/car/OWNERS b/car/OWNERS
index d226975..eac51b8 100644
--- a/car/OWNERS
+++ b/car/OWNERS
@@ -1 +1,3 @@
-ajchen@google.com
\ No newline at end of file
+ajchen@google.com
+deanh@google.com
+yaoyx@google.com
diff --git a/car/res/drawable/car_borderless_button_text_color.xml b/car/res/drawable/car_borderless_button_text_color.xml
index ff27db5..27f79f0 100644
--- a/car/res/drawable/car_borderless_button_text_color.xml
+++ b/car/res/drawable/car_borderless_button_text_color.xml
@@ -17,5 +17,5 @@
 <!-- Default text colors for car buttons when enabled/disabled. -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:color="@color/car_grey_700" android:state_enabled="false"/>
-    <item android:color="?android:attr/colorPrimary"/>
+    <item android:color="?android:attr/colorButtonNormal"/>
 </selector>
diff --git a/car/res/drawable/car_button_background.xml b/car/res/drawable/car_button_background.xml
index 1a8995c..58aa739 100644
--- a/car/res/drawable/car_button_background.xml
+++ b/car/res/drawable/car_button_background.xml
@@ -25,7 +25,7 @@
     <item>
         <shape android:shape="rectangle">
             <corners android:radius="@dimen/car_button_radius"/>
-            <solid android:color="?android:attr/colorPrimary"/>
+            <solid android:color="?android:attr/colorButtonNormal"/>
         </shape>
     </item>
 </selector>
diff --git a/car/res/layout/car_paged_list_item_content.xml b/car/res/layout/car_paged_list_item_content.xml
index fd6a8a4..943c489 100644
--- a/car/res/layout/car_paged_list_item_content.xml
+++ b/car/res/layout/car_paged_list_item_content.xml
@@ -24,8 +24,7 @@
     <ImageView
         android:id="@+id/primary_icon"
         android:layout_width="@dimen/car_single_line_list_item_height"
-        android:layout_height="@dimen/car_single_line_list_item_height"
-        android:layout_centerVertical="true"/>
+        android:layout_height="@dimen/car_single_line_list_item_height"/>
 
     <!-- Text. -->
     <TextView
@@ -55,10 +54,7 @@
         <!-- End icon with divider. -->
         <View
             android:id="@+id/supplemental_icon_divider"
-            android:layout_width="@dimen/car_vertical_line_divider_width"
-            android:layout_height="@dimen/car_vertical_line_divider_height"
-            android:layout_marginStart="@dimen/car_padding_4"
-            android:background="@color/car_list_divider"/>
+            style="@style/CarListVerticalDivider"/>
         <ImageView
             android:id="@+id/supplemental_icon"
             android:layout_width="@dimen/car_primary_icon_size"
@@ -66,13 +62,22 @@
             android:layout_marginStart="@dimen/car_padding_4"
             android:scaleType="fitCenter"/>
 
+        <!-- Switch with divider. -->
+        <View
+            android:id="@+id/switch_divider"
+            style="@style/CarListVerticalDivider"/>
+        <Switch
+            android:id="@+id/switch_widget"
+            android:layout_width="@dimen/car_primary_icon_size"
+            android:layout_height="@dimen/car_primary_icon_size"
+            android:layout_marginStart="@dimen/car_padding_4"
+            style="@android:style/Widget.Material.CompoundButton.Switch"
+        />
+
         <!-- Up to 2 action buttons with dividers. -->
         <View
             android:id="@+id/action2_divider"
-            android:layout_width="@dimen/car_vertical_line_divider_width"
-            android:layout_height="@dimen/car_vertical_line_divider_height"
-            android:layout_marginStart="@dimen/car_padding_4"
-            android:background="@color/car_list_divider"/>
+            style="@style/CarListVerticalDivider"/>
         <Button
             android:id="@+id/action2"
             android:layout_width="wrap_content"
@@ -83,13 +88,10 @@
             android:maxLines="1"
             android:background="@color/car_card"
             android:foreground="@drawable/car_card_ripple_background"
-            style="@style/CarButton.Borderless"/>
+            style="?android:attr/borderlessButtonStyle"/>
         <View
             android:id="@+id/action1_divider"
-            android:layout_width="@dimen/car_vertical_line_divider_width"
-            android:layout_height="@dimen/car_vertical_line_divider_height"
-            android:layout_marginStart="@dimen/car_padding_4"
-            android:background="@color/car_list_divider"/>
+            style="@style/CarListVerticalDivider"/>
         <Button
             android:id="@+id/action1"
             android:layout_width="wrap_content"
@@ -100,6 +102,6 @@
             android:maxLines="1"
             android:background="@color/car_card"
             android:foreground="@drawable/car_card_ripple_background"
-            style="@style/CarButton.Borderless"/>
+            style="?android:attr/borderlessButtonStyle"/>
     </LinearLayout>
 </RelativeLayout>
diff --git a/car/res/values/colors.xml b/car/res/values/colors.xml
index 8f82c01..88af182 100644
--- a/car/res/values/colors.xml
+++ b/car/res/values/colors.xml
@@ -99,8 +99,8 @@
     <color name="car_body4_dark">@android:color/black</color>
     <color name="car_body4">@color/car_body4_dark</color>
 
-    <color name="car_action1_light">@color/car_grey_50</color>
-    <color name="car_action1_dark">@color/car_grey_900</color>
+    <color name="car_action1_light">@color/car_grey_900</color>
+    <color name="car_action1_dark">@color/car_grey_50</color>
     <color name="car_action1">@color/car_action1_dark</color>
 
     <!-- The tinting colors to create a light- and dark-colored icon respectively. -->
@@ -164,4 +164,8 @@
     <color name="car_highlight_light">@color/car_teal_700</color>
     <color name="car_highlight_dark">@color/car_teal_200</color>
     <color name="car_highlight">@color/car_highlight_light</color>
+
+    <color name="car_accent_light">@color/car_teal_700</color>
+    <color name="car_accent_dark">@color/car_teal_200</color>
+    <color name="car_accent">@color/car_accent_light</color>
 </resources>
diff --git a/car/res/values/styles.xml b/car/res/values/styles.xml
index df194cc..78d9603 100644
--- a/car/res/values/styles.xml
+++ b/car/res/values/styles.xml
@@ -124,42 +124,46 @@
     </style>
 
     <!-- The styles for the regular and borderless buttons -->
-    <style name="CarButton" parent="Widget.AppCompat.Button">
+    <style name="Widget.Car.Button" parent="Widget.AppCompat.Button">
         <item name="android:layout_height">@dimen/car_button_height</item>
         <item name="android:minWidth">@dimen/car_button_min_width</item>
         <item name="android:paddingStart">@dimen/car_button_horizontal_padding</item>
         <item name="android:paddingEnd">@dimen/car_button_horizontal_padding</item>
-        <item name="android:textStyle">normal</item>
         <item name="android:textSize">@dimen/car_action1_size</item>
-        <item name="android:textColor">@drawable/car_button_text_color</item>
-        <item name="android:textAllCaps">true</item>
         <item name="android:background">@drawable/car_button_background</item>
+        <item name="android:textColor">@drawable/car_button_text_color</item>
     </style>
 
-    <style name="CarButton.Borderless" parent="Widget.AppCompat.Button.Borderless">
+    <style name="Widget.Car.Button.Borderless.Colored"
+           parent="Widget.AppCompat.Button.Borderless.Colored">
         <item name="android:layout_height">@dimen/car_button_height</item>
         <item name="android:paddingStart">@dimen/car_borderless_button_horizontal_padding</item>
         <item name="android:paddingEnd">@dimen/car_borderless_button_horizontal_padding</item>
-        <item name="android:textStyle">normal</item>
         <item name="android:textSize">@dimen/car_action1_size</item>
         <item name="android:textColor">@drawable/car_borderless_button_text_color</item>
-        <item name="android:textAllCaps">true</item>
     </style>
 
     <!-- Style for the progress bars -->
-    <style name="CarProgressBar.Horizontal"
+    <style name="Widget.Car.ProgressBar.Horizontal"
            parent="Widget.AppCompat.ProgressBar.Horizontal">
         <item name="android:minHeight">@dimen/car_progress_bar_height</item>
         <item name="android:maxHeight">@dimen/car_progress_bar_height</item>
     </style>
 
+    <style name="Widget.Car.EditText" parent="Widget.AppCompat.EditText">
+        <item name="android:textColor">?attr/editTextColor</item>
+        <item name="android:textAppearance">@style/CarBody1</item>
+    </style>
+
     <!-- Styles for TextInputLayout hints -->
     <style name="CarHintTextAppearance" parent="CarBody2">
     </style>
 
-    <!-- Styles for Car Dialogs -->
-    <style name="CarDialog" parent="Theme.AppCompat.Light.Dialog.Alert">
-        <item name="android:background">@color/car_card</item>
-        <item name="android:listDividerAlertDialog">@drawable/car_list_divider</item>
+    <style name="CarListVerticalDivider">
+        <item name="android:layout_width">@dimen/car_vertical_line_divider_width</item>
+        <item name="android:layout_height">@dimen/car_vertical_line_divider_height</item>
+        <item name="android:layout_marginStart">@dimen/car_padding_4</item>
+        <item name="android:background">@color/car_list_divider</item>
     </style>
+
 </resources>
diff --git a/car/res/values/themes.xml b/car/res/values/themes.xml
index 4244a22..ebb57d8 100644
--- a/car/res/values/themes.xml
+++ b/car/res/values/themes.xml
@@ -25,4 +25,28 @@
         <item name="contentInsetStart">@dimen/car_keyline_1</item>
         <item name="contentInsetEnd">@dimen/car_keyline_1</item>
     </style>
+
+    <!-- Styles for Car Dialogs -->
+    <style name="Theme.Car.Light.Dialog.Alert" parent="Theme.AppCompat.Light.Dialog.Alert">
+        <item name="android:background">@color/car_card</item>
+        <item name="android:listDividerAlertDialog">@drawable/car_list_divider</item>
+    </style>
+
+    <!-- Base style for the Car -->
+    <style name="Theme.Car.NoActionBar" parent="Theme.AppCompat.NoActionBar">
+        <item name="android:colorAccent">@color/car_accent</item>
+        <item name="android:colorButtonNormal">@color/car_accent</item>
+        <item name="android:buttonStyle">@style/Widget.Car.Button</item>
+        <item name="android:borderlessButtonStyle">@style/Widget.Car.Button.Borderless.Colored
+        </item>
+        <item name="android:alertDialogTheme">@style/Theme.Car.Light.Dialog.Alert</item>
+        <item name="android:progressBarStyleHorizontal">
+            @style/Widget.Car.ProgressBar.Horizontal
+        </item>
+        <item name="android:textColorHint">@color/car_body2</item>
+        <item name="android:editTextStyle">@style/Widget.Car.EditText</item>
+        <item name="android:editTextColor">@color/car_body1</item>
+        <item name="android:colorControlNormal">@color/car_body2</item>
+        <item name="android:colorControlHighlight">?android:attr/colorAccent</item>
+    </style>
 </resources>
diff --git a/car/src/main/java/androidx/car/widget/ListItem.java b/car/src/main/java/androidx/car/widget/ListItem.java
index d292d6b..d345833 100644
--- a/car/src/main/java/androidx/car/widget/ListItem.java
+++ b/car/src/main/java/androidx/car/widget/ListItem.java
@@ -26,6 +26,7 @@
 import android.support.v7.widget.RecyclerView;
 import android.text.TextUtils;
 import android.view.View;
+import android.widget.CompoundButton;
 import android.widget.RelativeLayout;
 
 import java.lang.annotation.Retention;
@@ -58,6 +59,7 @@
  *         <li>Supplemental Icon
  *         <li>One Action Button
  *         <li>Two Action Buttons
+ *         <li>Switch</li>
  *     </ul>
  * </ul>
  *
@@ -87,6 +89,7 @@
                 vh.getPrimaryIcon(),
                 vh.getTitle(), vh.getBody(),
                 vh.getSupplementalIcon(), vh.getSupplementalIconDivider(),
+                vh.getSwitch(), vh.getSwitchDivider(),
                 vh.getAction1(), vh.getAction1Divider(), vh.getAction2(), vh.getAction2Divider()};
         for (View v : subviews) {
             v.setVisibility(View.GONE);
@@ -94,6 +97,20 @@
     }
 
     /**
+     * Returns whether the divider that comes after the ListItem should be hidden or not.
+     *
+     * <p>Note: For this to work, one must invoke
+     * {@code PagedListView.setDividerVisibilityManager(adapter} for {@link ListItemAdapter} and
+     * have dividers enabled on {@link PagedListView}.
+     *
+     * @return {@code true} if divider coming after the item should be hidden, {@code false}
+     * otherwise.
+     */
+    boolean shouldHideDivider() {
+        return mBuilder.mHideDivider;
+    }
+
+    /**
      * Functional interface to provide a way to interact with views in
      * {@link ListItemAdapter.ViewHolder}. {@code ViewBinder}s added to a
      * {@code ListItem} will be called when {@code ListItem} {@code bind}s to
@@ -127,13 +144,15 @@
 
         @Retention(SOURCE)
         @IntDef({SUPPLEMENTAL_ACTION_NO_ACTION, SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON,
-                SUPPLEMENTAL_ACTION_ONE_ACTION, SUPPLEMENTAL_ACTION_TWO_ACTIONS})
+                SUPPLEMENTAL_ACTION_ONE_ACTION, SUPPLEMENTAL_ACTION_TWO_ACTIONS,
+                SUPPLEMENTAL_ACTION_SWITCH})
         private @interface SupplementalActionType {}
 
         private static final int SUPPLEMENTAL_ACTION_NO_ACTION = 0;
         private static final int SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON = 1;
         private static final int SUPPLEMENTAL_ACTION_ONE_ACTION = 2;
         private static final int SUPPLEMENTAL_ACTION_TWO_ACTIONS = 3;
+        private static final int SUPPLEMENTAL_ACTION_SWITCH = 4;
 
         private final Context mContext;
         private final List<ViewBinder> mBinders = new ArrayList<>();
@@ -149,12 +168,18 @@
         private String mTitle;
         private String mBody;
         private boolean mIsBodyPrimary;
+        // tag for indicating whether to hide the divider
+        private boolean mHideDivider;
 
         @SupplementalActionType private int mSupplementalActionType = SUPPLEMENTAL_ACTION_NO_ACTION;
         private int mSupplementalIconResId;
         private View.OnClickListener mSupplementalIconOnClickListener;
         private boolean mShowSupplementalIconDivider;
 
+        private boolean mSwitchChecked;
+        private boolean mShowSwitchDivider;
+        private CompoundButton.OnCheckedChangeListener mSwitchOnCheckedChangeListener;
+
         private String mAction1Text;
         private View.OnClickListener mAction1OnClickListener;
         private boolean mShowAction1Divider;
@@ -282,12 +307,15 @@
                                 R.dimen.car_keyline_1));
 
                         if (!TextUtils.isEmpty(mBody)) {
-                            // Set top margin.
+                            // Set icon top margin so that the icon remains in the same position it
+                            // would've been in for non-long-text item, namely so that the center
+                            // line of icon matches that of line item.
                             layoutParams.removeRule(RelativeLayout.CENTER_VERTICAL);
-                            layoutParams.topMargin = mContext.getResources().getDimensionPixelSize(
-                                    R.dimen.car_padding_4);
+                            int itemHeight = mContext.getResources().getDimensionPixelSize(
+                                    R.dimen.car_double_line_list_item_height);
+                            layoutParams.topMargin = (itemHeight - iconSize) / 2;
                         } else {
-                            // Centered vertically.
+                            // If the icon can be centered vertically, leave the work for framework.
                             layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);
                             layoutParams.topMargin = 0;
                         }
@@ -481,12 +509,32 @@
                 case SUPPLEMENTAL_ACTION_NO_ACTION:
                     // Do nothing
                     break;
+                case SUPPLEMENTAL_ACTION_SWITCH:
+                    mBinders.add(vh -> {
+                        vh.getSwitch().setVisibility(View.VISIBLE);
+                        vh.getSwitch().setChecked(mSwitchChecked);
+                        vh.getSwitch().setOnCheckedChangeListener(mSwitchOnCheckedChangeListener);
+                        if (mShowSwitchDivider) {
+                            vh.getSwitchDivider().setVisibility(View.VISIBLE);
+                        }
+                    });
+                    break;
                 default:
                     throw new IllegalArgumentException("Unrecognized supplemental action type.");
             }
         }
 
         /**
+         * Instructs the Builder to always hide the item divider coming after this ListItem.
+         *
+         * @return This Builder object to allow for chaining calls to set methods.
+         */
+        public Builder withDividerHidden() {
+            mHideDivider = true;
+            return this;
+        }
+
+        /**
          * Sets {@link View.OnClickListener} of {@code ListItem}.
          *
          * @return This Builder object to allow for chaining calls to set methods.
@@ -652,6 +700,7 @@
          *
          * @param action1Text button text to display - this button will be closer to item end.
          * @param action2Text button text to display.
+         * @return This Builder object to allow for chaining calls to set methods.
          */
         public Builder withActions(String action1Text, boolean showAction1Divider,
                 View.OnClickListener action1OnClickListener,
@@ -675,6 +724,24 @@
         }
 
         /**
+         * Sets {@code Supplemental Action} to be represented by a {@link android.widget.Switch}.
+         *
+         * @param checked initial value for switched.
+         * @param showDivider whether to display a vertical bar between switch and text.
+         * @param listener callback to be invoked when the checked state is changed.
+         * @return This Builder object to allow for chaining calls to set methods.
+         */
+        public Builder withSwitch(boolean checked, boolean showDivider,
+                CompoundButton.OnCheckedChangeListener listener) {
+            mSupplementalActionType = SUPPLEMENTAL_ACTION_SWITCH;
+
+            mSwitchChecked = checked;
+            mShowSwitchDivider = showDivider;
+            mSwitchOnCheckedChangeListener = listener;
+            return this;
+        }
+
+        /**
          * Adds {@link ViewBinder} to interact with sub-views in
          * {@link ListItemAdapter.ViewHolder}. These ViewBinders will always bind after
          * other {@link Builder} methods have bond.
diff --git a/car/src/main/java/androidx/car/widget/ListItemAdapter.java b/car/src/main/java/androidx/car/widget/ListItemAdapter.java
index c9b6177..a338a51 100644
--- a/car/src/main/java/androidx/car/widget/ListItemAdapter.java
+++ b/car/src/main/java/androidx/car/widget/ListItemAdapter.java
@@ -26,6 +26,7 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.RelativeLayout;
+import android.widget.Switch;
 import android.widget.TextView;
 
 import androidx.car.R;
@@ -34,10 +35,16 @@
 /**
  * Adapter for {@link PagedListView} to display {@link ListItem}.
  *
- * Implements {@link PagedListView.ItemCap} - defaults to unlimited item count.
+ * <ul>
+ *     <li> Implements {@link PagedListView.ItemCap} - defaults to unlimited item count.
+ *     <li> Implements {@link PagedListView.DividerVisibilityManager} - to control dividers after
+ *     individual {@link ListItem}.
+ * </ul>
+ *
  */
 public class ListItemAdapter extends
-        RecyclerView.Adapter<ListItemAdapter.ViewHolder> implements PagedListView.ItemCap {
+        RecyclerView.Adapter<ListItemAdapter.ViewHolder> implements PagedListView.ItemCap,
+        PagedListView.DividerVisibilityManager {
 
     /**
      * Constant class for background style of items.
@@ -143,6 +150,15 @@
         mMaxItems = maxItems;
     }
 
+    @Override
+    public boolean shouldHideDivider(int position) {
+        // By default we should show the divider i.e. return false.
+
+        // Check if position is within range, and then check the item flag.
+        return position >= 0 && position < getItemCount()
+                && mItemProvider.get(position).shouldHideDivider();
+    }
+
     /**
      * Holds views of an item in PagedListView.
      *
@@ -166,6 +182,9 @@
         private Button mAction2;
         private View mAction2Divider;
 
+        private Switch mSwitch;
+        private View mSwitchDivider;
+
         public ViewHolder(View itemView) {
             super(itemView);
 
@@ -179,6 +198,9 @@
             mSupplementalIcon = itemView.findViewById(R.id.supplemental_icon);
             mSupplementalIconDivider = itemView.findViewById(R.id.supplemental_icon_divider);
 
+            mSwitch = itemView.findViewById(R.id.switch_widget);
+            mSwitchDivider = itemView.findViewById(R.id.switch_divider);
+
             mAction1 = itemView.findViewById(R.id.action1);
             mAction1Divider = itemView.findViewById(R.id.action1_divider);
             mAction2 = itemView.findViewById(R.id.action2);
@@ -209,6 +231,14 @@
             return mSupplementalIconDivider;
         }
 
+        public View getSwitchDivider() {
+            return mSwitchDivider;
+        }
+
+        public Switch getSwitch() {
+            return mSwitch;
+        }
+
         public Button getAction1() {
             return mAction1;
         }
diff --git a/car/src/main/java/androidx/car/widget/PagedListView.java b/car/src/main/java/androidx/car/widget/PagedListView.java
index d90d670..63924d8 100644
--- a/car/src/main/java/androidx/car/widget/PagedListView.java
+++ b/car/src/main/java/androidx/car/widget/PagedListView.java
@@ -153,6 +153,23 @@
     }
 
     /**
+     * Interface for controlling visibility of item dividers for individual items based on the
+     * item's position.
+     *
+     * <p> NOTE: interface takes effect only when dividers are enabled.
+     */
+    public interface DividerVisibilityManager {
+        /**
+         * Given an item position, returns whether the divider coming after that item should be
+         * hidden.
+         *
+         * @param position item position inside the adapter.
+         * @return true if divider is to be hidden, false if divider should be shown.
+         */
+        boolean shouldHideDivider(int position);
+    }
+
+    /**
      * The possible values for @{link #setGutter}. The default value is actually
      * {@link Gutter#BOTH}.
      */
@@ -409,9 +426,26 @@
             @NonNull RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter) {
         mAdapter = adapter;
         mRecyclerView.setAdapter(adapter);
+
         updateMaxItems();
     }
 
+    /**
+     * Sets {@link DividerVisibilityManager} on all {@code DividerDecoration} item decorations.
+     *
+     * @param dvm {@code DividerVisibilityManager} to be set.
+     */
+    public void setDividerVisibilityManager(DividerVisibilityManager dvm) {
+        int decorCount = mRecyclerView.getItemDecorationCount();
+        for (int i = 0; i < decorCount; i++) {
+            RecyclerView.ItemDecoration decor = mRecyclerView.getItemDecorationAt(i);
+            if (decor instanceof DividerDecoration) {
+                ((DividerDecoration) decor).setVisibilityManager(dvm);
+            }
+        }
+        mRecyclerView.invalidateItemDecorations();
+    }
+
     @Nullable
     @SuppressWarnings("unchecked")
     public RecyclerView.Adapter<? extends RecyclerView.ViewHolder> getAdapter() {
@@ -960,6 +994,7 @@
         private final int mDividerStartMargin;
         @IdRes private final int mDividerStartId;
         @IdRes private final int mDividerEndId;
+        private DividerVisibilityManager mVisibilityManager;
 
         /**
          * @param dividerStartMargin The start offset of the dividing line. This offset will be
@@ -989,11 +1024,23 @@
             mPaint.setColor(mContext.getResources().getColor(R.color.car_list_divider));
         }
 
+        /** Sets {@link DividerVisibilityManager} on the DividerDecoration.*/
+        public void setVisibilityManager(DividerVisibilityManager dvm) {
+            mVisibilityManager = dvm;
+        }
+
         @Override
         public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
             // Draw a divider line between each item. No need to draw the line for the last item.
             for (int i = 0, childCount = parent.getChildCount(); i < childCount - 1; i++) {
                 View container = parent.getChildAt(i);
+
+                // if divider should be hidden for this item, proceeds without drawing it
+                int itemPosition = parent.getChildAdapterPosition(container);
+                if (hideDividerForAdapterPosition(itemPosition)) {
+                    continue;
+                }
+
                 View nextContainer = parent.getChildAt(i + 1);
                 int spacing = nextContainer.getTop() - container.getBottom();
 
@@ -1034,14 +1081,21 @@
         public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
                 RecyclerView.State state) {
             super.getItemOffsets(outRect, view, parent, state);
-            // Skip top offset for first item and bottom offset for last.
-            int position = parent.getChildAdapterPosition(view);
-            if (position > 0) {
+            int pos = parent.getChildAdapterPosition(view);
+
+            // Skip top offset when there is no divider above.
+            if (pos > 0 && !hideDividerForAdapterPosition(pos - 1)) {
                 outRect.top = mDividerHeight / 2;
             }
-            if (position < state.getItemCount() - 1) {
+
+            // Skip bottom offset when there is no divider below.
+            if (pos < state.getItemCount() - 1 && !hideDividerForAdapterPosition(pos)) {
                 outRect.bottom = mDividerHeight / 2;
             }
         }
+
+        private boolean hideDividerForAdapterPosition(int position) {
+            return mVisibilityManager != null && mVisibilityManager.shouldHideDivider(position);
+        }
     }
 }
diff --git a/car/tests/AndroidManifest.xml b/car/tests/AndroidManifest.xml
index 8e5422d..f50977a 100644
--- a/car/tests/AndroidManifest.xml
+++ b/car/tests/AndroidManifest.xml
@@ -22,5 +22,6 @@
         <activity android:name="androidx.car.widget.ColumnCardViewTestActivity"/>
         <activity android:name="androidx.car.widget.PagedListViewSavedStateActivity"/>
         <activity android:name="androidx.car.widget.PagedListViewTestActivity"/>
+        <activity android:name="androidx.car.widget.DividerVisibilityManagerTestActivity"/>
     </application>
 </manifest>
diff --git a/car/tests/res/layout/activity_paged_list_view_with_dividers.xml b/car/tests/res/layout/activity_paged_list_view_with_dividers.xml
new file mode 100644
index 0000000..31191c4
--- /dev/null
+++ b/car/tests/res/layout/activity_paged_list_view_with_dividers.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.car.widget.PagedListView
+        android:id="@+id/paged_list_view_with_dividers"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:showPagedListViewDivider="true"
+        app:offsetScrollBar="true"/>
+</FrameLayout>
diff --git a/car/tests/res/values/dimens.xml b/car/tests/res/values/dimens.xml
new file mode 100644
index 0000000..58f1191
--- /dev/null
+++ b/car/tests/res/values/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<resources>
+    <!-- Set to something large for testing. -->
+    <dimen name="car_list_divider_height">10dp</dimen>
+</resources>
diff --git a/car/tests/src/androidx/car/widget/DividerVisibilityManagerTest.java b/car/tests/src/androidx/car/widget/DividerVisibilityManagerTest.java
new file mode 100644
index 0000000..3555313
--- /dev/null
+++ b/car/tests/src/androidx/car/widget/DividerVisibilityManagerTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.widget;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import android.content.pm.PackageManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import androidx.car.test.R;
+
+/** Unit tests for implementations of {@link PagedListView.DividerVisibilityManager}. */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public final class DividerVisibilityManagerTest {
+
+    /**
+     * Used by {@link TestAdapter} to calculate ViewHolder height so N items appear in one page of
+     * {@link PagedListView}. If you need to test behavior under multiple pages, set number of items
+     * to ITEMS_PER_PAGE * desired_pages.
+     * Actual value does not matter.
+     */
+    private static final int ITEMS_PER_PAGE = 10;
+
+    @Rule
+    public ActivityTestRule<DividerVisibilityManagerTestActivity> mActivityRule =
+            new ActivityTestRule<>(DividerVisibilityManagerTestActivity.class);
+
+    private DividerVisibilityManagerTestActivity mActivity;
+    private PagedListView mPagedListView;
+
+    @Before
+    public void setUp() {
+        mActivity = mActivityRule.getActivity();
+        mPagedListView = mActivity.findViewById(R.id.paged_list_view_with_dividers);
+    }
+
+    /** Returns {@code true} if the testing device has the automotive feature flag. */
+    private boolean isAutoDevice() {
+        PackageManager packageManager = mActivityRule.getActivity().getPackageManager();
+        return packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+    }
+
+    /** Sets up {@link #mPagedListView} with the given number of items. */
+    private void setUpPagedListView(int itemCount) {
+        try {
+            mActivityRule.runOnUiThread(() -> {
+                mPagedListView.setMaxPages(PagedListView.ItemCap.UNLIMITED);
+                mPagedListView.setAdapter(
+                        new TestAdapter(itemCount, mPagedListView.getMeasuredHeight()));
+            });
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+            throw new RuntimeException(throwable);
+        }
+    }
+
+    @Test
+    public void setCustomDividerVisibilityManager() throws Throwable {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        final int itemCount = 8;
+        setUpPagedListView(itemCount /* itemCount */);
+        RecyclerView.LayoutManager layoutManager =
+                mPagedListView.getRecyclerView().getLayoutManager();
+
+        // Fetch divider height.
+        final int dividerHeight = InstrumentationRegistry.getContext().getResources()
+                .getDimensionPixelSize(R.dimen.car_list_divider_height);
+
+
+        // Initially, dividers are present between each two items.
+        final View[] views = new View[itemCount];
+        mActivityRule.runOnUiThread(() -> {
+            for (int i = 0; i < layoutManager.getChildCount(); i++) {
+                views[i] = layoutManager.getChildAt(i);
+            }
+        });
+        for (int i = 0; i < itemCount - 1; i++) {
+            assertThat(views[i + 1].getTop() - views[i].getBottom(),
+                    is(equalTo(2 * (dividerHeight / 2))));
+        }
+
+
+        // Set DividerVisibilityManager on PagedListView.
+        final PagedListView.DividerVisibilityManager dvm = new TestDividerVisibilityManager();
+        mActivityRule.runOnUiThread(() -> {
+            mPagedListView.setDividerVisibilityManager(dvm);
+        });
+
+        mActivityRule.runOnUiThread(() -> {
+            for (int i = 0; i < layoutManager.getChildCount(); i++) {
+                views[i] = layoutManager.getChildAt(i);
+            }
+        });
+
+        for (int i = 0; i < itemCount - 1; i++) {
+            int distance = views[i + 1].getTop() - views[i].getBottom();
+            if (dvm.shouldHideDivider(i)) {
+                assertEquals(distance, 0);
+            } else {
+                assertEquals(distance, 2 * (dividerHeight / 2));
+            }
+        }
+    }
+
+    @Test
+    public void testListItemAdapterAsVisibilityManager() {
+        // Create and populate ListItemAdapter.
+        ListItemProvider provider = new ListItemProvider.ListProvider(Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withDividerHidden()
+                        .build(),
+                new ListItem.Builder(mActivity)
+                        .build(),
+                new ListItem.Builder(mActivity)
+                        .withDividerHidden()
+                        .build(),
+                new ListItem.Builder(mActivity)
+                        .withDividerHidden()
+                        .build()));
+
+        ListItemAdapter itemAdapter = new ListItemAdapter(mActivity, provider);
+        assertTrue(itemAdapter.shouldHideDivider(0));
+        assertFalse(itemAdapter.shouldHideDivider(1));
+        assertTrue(itemAdapter.shouldHideDivider(2));
+        assertTrue(itemAdapter.shouldHideDivider(3));
+    }
+
+    private class TestDividerVisibilityManager implements PagedListView.DividerVisibilityManager {
+        @Override
+        public boolean shouldHideDivider(int position) {
+            // Hide divider after items at even positions, show after items at odd positions.
+            return position % 2 == 0;
+        }
+    }
+
+    private static String itemText(int index) {
+        return "Data " + index;
+    }
+
+    /** A base adapter that will handle inflating the test view and binding data to it. */
+    private class TestAdapter extends RecyclerView.Adapter<TestViewHolder> {
+        private List<String> mData;
+        private int mParentHeight;
+
+        TestAdapter(int itemCount, int parentHeight) {
+            mData = new ArrayList<>();
+            for (int i = 0; i < itemCount; i++) {
+                mData.add(itemText(i));
+            }
+            mParentHeight = parentHeight;
+        }
+
+        @Override
+        public TestViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+            return new TestViewHolder(inflater, parent);
+        }
+
+        @Override
+        public void onBindViewHolder(TestViewHolder holder, int position) {
+            // Calculate height for an item so one page fits ITEMS_PER_PAGE items.
+            int height = (int) Math.floor(mParentHeight / ITEMS_PER_PAGE);
+            holder.itemView.setMinimumHeight(height);
+            holder.bind(mData.get(position));
+        }
+
+        @Override
+        public int getItemCount() {
+            return mData.size();
+        }
+    }
+
+    private class TestViewHolder extends RecyclerView.ViewHolder {
+        private TextView mTextView;
+
+        TestViewHolder(LayoutInflater inflater, ViewGroup parent) {
+            super(inflater.inflate(R.layout.paged_list_item_column_card, parent, false));
+            mTextView = itemView.findViewById(R.id.text_view);
+        }
+
+        public void bind(String text) {
+            mTextView.setText(text);
+        }
+    }
+}
diff --git a/v13/tests/java/android/support/v13/view/DragStartHelperTestActivity.java b/car/tests/src/androidx/car/widget/DividerVisibilityManagerTestActivity.java
similarity index 62%
copy from v13/tests/java/android/support/v13/view/DragStartHelperTestActivity.java
copy to car/tests/src/androidx/car/widget/DividerVisibilityManagerTestActivity.java
index 6a3605b..30c1174 100644
--- a/v13/tests/java/android/support/v13/view/DragStartHelperTestActivity.java
+++ b/car/tests/src/androidx/car/widget/DividerVisibilityManagerTestActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,16 +14,21 @@
  * limitations under the License.
  */
 
-package android.support.v13.view;
+package androidx.car.widget;
 
 import android.app.Activity;
 import android.os.Bundle;
-import android.support.v13.test.R;
 
-public class DragStartHelperTestActivity extends Activity {
+import androidx.car.test.R;
+
+/**
+ * Simple test activity for {@link PagedListView.DividerVisibilityManager} tests.
+ *
+ */
+public class DividerVisibilityManagerTestActivity extends Activity {
     @Override
-    protected void onCreate(Bundle savedInstanceState) {
+    public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.drag_source_activity);
+        setContentView(R.layout.activity_paged_list_view_with_dividers);
     }
 }
diff --git a/car/tests/src/androidx/car/widget/ListItemTest.java b/car/tests/src/androidx/car/widget/ListItemTest.java
index 8d2096a..fa0771b 100644
--- a/car/tests/src/androidx/car/widget/ListItemTest.java
+++ b/car/tests/src/androidx/car/widget/ListItemTest.java
@@ -23,6 +23,7 @@
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 
 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.core.Is.is;
 import static org.hamcrest.core.IsEqual.equalTo;
 import static org.hamcrest.number.IsCloseTo.closeTo;
@@ -180,6 +181,28 @@
     }
 
     @Test
+    public void testSwitchVisibleAndCheckedState() {
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withSwitch(true, true, null)
+                        .build(),
+                new ListItem.Builder(mActivity)
+                        .withSwitch(false, true, null)
+                        .build());
+        setupPagedListView(items);
+
+        ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        assertThat(viewHolder.getSwitch().getVisibility(), is(equalTo(View.VISIBLE)));
+        assertThat(viewHolder.getSwitch().isChecked(), is(equalTo(true)));
+        assertThat(viewHolder.getSwitchDivider().getVisibility(), is(equalTo(View.VISIBLE)));
+
+        viewHolder = getViewHolderAtPosition(1);
+        assertThat(viewHolder.getSwitch().getVisibility(), is(equalTo(View.VISIBLE)));
+        assertThat(viewHolder.getSwitch().isChecked(), is(equalTo(false)));
+        assertThat(viewHolder.getSwitchDivider().getVisibility(), is(equalTo(View.VISIBLE)));
+    }
+
+    @Test
     public void testDividersAreOptional() {
         List<ListItem> items = Arrays.asList(
                 new ListItem.Builder(mActivity)
@@ -191,11 +214,12 @@
                 new ListItem.Builder(mActivity)
                         .withActions("text", false, v -> { /* Do nothing. */ },
                                 "text", false, v -> { /* Do nothing. */ })
+                        .build(),
+                new ListItem.Builder(mActivity)
+                        .withSwitch(true, false, null)
                         .build());
         setupPagedListView(items);
 
-        setupPagedListView(items);
-
         ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(0);
         assertThat(viewHolder.getSupplementalIcon().getVisibility(), is(equalTo(View.VISIBLE)));
         assertThat(viewHolder.getSupplementalIconDivider().getVisibility(),
@@ -210,6 +234,30 @@
         assertThat(viewHolder.getAction1Divider().getVisibility(), is(equalTo(View.GONE)));
         assertThat(viewHolder.getAction2().getVisibility(), is(equalTo(View.VISIBLE)));
         assertThat(viewHolder.getAction2Divider().getVisibility(), is(equalTo(View.GONE)));
+
+        viewHolder = getViewHolderAtPosition(3);
+        assertThat(viewHolder.getSwitch().getVisibility(), is(equalTo(View.VISIBLE)));
+        assertThat(viewHolder.getSwitchDivider().getVisibility(), is(equalTo(View.GONE)));
+    }
+
+    @Test
+    public void testCanHideItemDividers() {
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withDividerHidden()
+                        .build(),
+                new ListItem.Builder(mActivity)
+                        .build());
+        setupPagedListView(items);
+
+        assertThat(items.get(0).shouldHideDivider(), is(true));
+        assertThat(items.get(1).shouldHideDivider(), is(false));
+
+        PagedListView.DividerVisibilityManager dvm = (PagedListView.DividerVisibilityManager)
+                mPagedListView.getAdapter();
+        assertThat(dvm, is(notNullValue()));
+        assertThat(dvm.shouldHideDivider(0), is(true));
+        assertThat(dvm.shouldHideDivider(1), is(false));
     }
 
     @Test
@@ -366,6 +414,49 @@
     }
 
     @Test
+    public void testSmallPrimaryIconTopMarginRemainsTheSameRegardlessOfTextLength() {
+        final String longText = InstrumentationRegistry.getContext().getResources().getString(
+                R.string.over_120_chars);
+        List<ListItem> items = Arrays.asList(
+                // Single line item.
+                new ListItem.Builder(mActivity)
+                        .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+                        .withTitle("one line text")
+                        .build(),
+                // Double line item with one line text.
+                new ListItem.Builder(mActivity)
+                        .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+                        .withTitle("one line text")
+                        .withBody("one line text")
+                        .build(),
+                // Double line item with long text.
+                new ListItem.Builder(mActivity)
+                        .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+                        .withTitle("one line text")
+                        .withBody(longText)
+                        .build(),
+                // Body text only - long text.
+                new ListItem.Builder(mActivity)
+                        .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+                        .withBody(longText)
+                        .build(),
+                // Body text only - one line text.
+                new ListItem.Builder(mActivity)
+                        .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+                        .withBody("one line text")
+                        .build());
+        setupPagedListView(items);
+
+        for (int i = 1; i < items.size(); i++) {
+            onView(withId(R.id.recycler_view)).perform(scrollToPosition(i));
+            // Implementation uses integer division so it may be off by 1 vs centered vertically.
+            assertThat((double) getViewHolderAtPosition(i - 1).getPrimaryIcon().getTop(),
+                    is(closeTo(
+                    (double) getViewHolderAtPosition(i).getPrimaryIcon().getTop(), 1.0d)));
+        }
+    }
+
+    @Test
     public void testClickingPrimaryActionIsSeparateFromSupplementalAction() {
         final boolean[] clicked = {false, false};
         List<ListItem> items = Arrays.asList(
@@ -413,6 +504,35 @@
     }
 
     @Test
+    public void testCheckingSwitch() {
+        final boolean[] clicked = {false, false};
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withSwitch(false, false, (button, isChecked) -> {
+                            // Initial value is false.
+                            assertTrue(isChecked);
+                            clicked[0] = true;
+                        })
+                        .build(),
+                new ListItem.Builder(mActivity)
+                        .withSwitch(true, false, (button, isChecked) -> {
+                            // Initial value is true.
+                            assertFalse(isChecked);
+                            clicked[1] = true;
+                        })
+                        .build());
+        setupPagedListView(items);
+
+        onView(withId(R.id.recycler_view)).perform(
+                actionOnItemAtPosition(0, clickChildViewWithId(R.id.switch_widget)));
+        assertTrue(clicked[0]);
+
+        onView(withId(R.id.recycler_view)).perform(
+                actionOnItemAtPosition(1, clickChildViewWithId(R.id.switch_widget)));
+        assertTrue(clicked[1]);
+    }
+
+    @Test
     public void testClickingSupplementalAction() {
         final boolean[] clicked = {false};
         List<ListItem> items = Arrays.asList(
diff --git a/compat/api/current.txt b/compat/api/current.txt
index 66525e2..04aebf6 100644
--- a/compat/api/current.txt
+++ b/compat/api/current.txt
@@ -1,3 +1,58 @@
+package android.support.v13.view {
+
+  public final class DragAndDropPermissionsCompat {
+    method public void release();
+  }
+
+  public class DragStartHelper {
+    ctor public DragStartHelper(android.view.View, android.support.v13.view.DragStartHelper.OnDragStartListener);
+    method public void attach();
+    method public void detach();
+    method public void getTouchPosition(android.graphics.Point);
+    method public boolean onLongClick(android.view.View);
+    method public boolean onTouch(android.view.View, android.view.MotionEvent);
+  }
+
+  public static abstract interface DragStartHelper.OnDragStartListener {
+    method public abstract boolean onDragStart(android.view.View, android.support.v13.view.DragStartHelper);
+  }
+
+}
+
+package android.support.v13.view.inputmethod {
+
+  public final class EditorInfoCompat {
+    ctor public EditorInfoCompat();
+    method public static java.lang.String[] getContentMimeTypes(android.view.inputmethod.EditorInfo);
+    method public static void setContentMimeTypes(android.view.inputmethod.EditorInfo, java.lang.String[]);
+    field public static final int IME_FLAG_FORCE_ASCII = -2147483648; // 0x80000000
+    field public static final int IME_FLAG_NO_PERSONALIZED_LEARNING = 16777216; // 0x1000000
+  }
+
+  public final class InputConnectionCompat {
+    ctor public InputConnectionCompat();
+    method public static boolean commitContent(android.view.inputmethod.InputConnection, android.view.inputmethod.EditorInfo, android.support.v13.view.inputmethod.InputContentInfoCompat, int, android.os.Bundle);
+    method public static android.view.inputmethod.InputConnection createWrapper(android.view.inputmethod.InputConnection, android.view.inputmethod.EditorInfo, android.support.v13.view.inputmethod.InputConnectionCompat.OnCommitContentListener);
+    field public static int INPUT_CONTENT_GRANT_READ_URI_PERMISSION;
+  }
+
+  public static abstract interface InputConnectionCompat.OnCommitContentListener {
+    method public abstract boolean onCommitContent(android.support.v13.view.inputmethod.InputContentInfoCompat, int, android.os.Bundle);
+  }
+
+  public final class InputContentInfoCompat {
+    ctor public InputContentInfoCompat(android.net.Uri, android.content.ClipDescription, android.net.Uri);
+    method public android.net.Uri getContentUri();
+    method public android.content.ClipDescription getDescription();
+    method public android.net.Uri getLinkUri();
+    method public void releasePermission();
+    method public void requestPermission();
+    method public java.lang.Object unwrap();
+    method public static android.support.v13.view.inputmethod.InputContentInfoCompat wrap(java.lang.Object);
+  }
+
+}
+
 package android.support.v4.accessibilityservice {
 
   public final class AccessibilityServiceInfoCompat {
@@ -30,6 +85,7 @@
     method public static android.net.Uri getReferrer(android.app.Activity);
     method public static deprecated boolean invalidateOptionsMenu(android.app.Activity);
     method public static void postponeEnterTransition(android.app.Activity);
+    method public static android.support.v13.view.DragAndDropPermissionsCompat requestDragAndDropPermissions(android.app.Activity, android.view.DragEvent);
     method public static void requestPermissions(android.app.Activity, java.lang.String[], int);
     method public static void setEnterSharedElementCallback(android.app.Activity, android.support.v4.app.SharedElementCallback);
     method public static void setExitSharedElementCallback(android.app.Activity, android.support.v4.app.SharedElementCallback);
@@ -1057,6 +1113,7 @@
     ctor public ArraySet();
     ctor public ArraySet(int);
     ctor public ArraySet(android.support.v4.util.ArraySet<E>);
+    ctor public ArraySet(java.util.Collection<E>);
     method public boolean add(E);
     method public void addAll(android.support.v4.util.ArraySet<? extends E>);
     method public boolean addAll(java.util.Collection<? extends E>);
@@ -1167,6 +1224,8 @@
 
   public class ObjectsCompat {
     method public static boolean equals(java.lang.Object, java.lang.Object);
+    method public static int hash(java.lang.Object...);
+    method public static int hashCode(java.lang.Object);
   }
 
   public class Pair<F, S> {
@@ -1730,7 +1789,7 @@
     field public static final int TYPE_TOUCH = 0; // 0x0
   }
 
-  public final deprecated class ViewConfigurationCompat {
+  public final class ViewConfigurationCompat {
     method public static float getScaledHorizontalScrollFactor(android.view.ViewConfiguration, android.content.Context);
     method public static deprecated int getScaledPagingTouchSlop(android.view.ViewConfiguration);
     method public static float getScaledVerticalScrollFactor(android.view.ViewConfiguration, android.content.Context);
@@ -2338,6 +2397,7 @@
     method public static void setCompoundDrawablesRelative(android.widget.TextView, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
     method public static void setCompoundDrawablesRelativeWithIntrinsicBounds(android.widget.TextView, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
     method public static void setCompoundDrawablesRelativeWithIntrinsicBounds(android.widget.TextView, int, int, int, int);
+    method public static void setCustomSelectionActionModeCallback(android.widget.TextView, android.view.ActionMode.Callback);
     method public static void setTextAppearance(android.widget.TextView, int);
     field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0
     field public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; // 0x1
diff --git a/v13/java/android/support/v13/view/DragAndDropPermissionsCompat.java b/compat/src/main/java/android/support/v13/view/DragAndDropPermissionsCompat.java
similarity index 100%
rename from v13/java/android/support/v13/view/DragAndDropPermissionsCompat.java
rename to compat/src/main/java/android/support/v13/view/DragAndDropPermissionsCompat.java
diff --git a/v13/java/android/support/v13/view/DragStartHelper.java b/compat/src/main/java/android/support/v13/view/DragStartHelper.java
similarity index 100%
rename from v13/java/android/support/v13/view/DragStartHelper.java
rename to compat/src/main/java/android/support/v13/view/DragStartHelper.java
diff --git a/v13/java/android/support/v13/view/inputmethod/EditorInfoCompat.java b/compat/src/main/java/android/support/v13/view/inputmethod/EditorInfoCompat.java
similarity index 100%
rename from v13/java/android/support/v13/view/inputmethod/EditorInfoCompat.java
rename to compat/src/main/java/android/support/v13/view/inputmethod/EditorInfoCompat.java
diff --git a/v13/java/android/support/v13/view/inputmethod/InputConnectionCompat.java b/compat/src/main/java/android/support/v13/view/inputmethod/InputConnectionCompat.java
similarity index 100%
rename from v13/java/android/support/v13/view/inputmethod/InputConnectionCompat.java
rename to compat/src/main/java/android/support/v13/view/inputmethod/InputConnectionCompat.java
diff --git a/v13/java/android/support/v13/view/inputmethod/InputContentInfoCompat.java b/compat/src/main/java/android/support/v13/view/inputmethod/InputContentInfoCompat.java
similarity index 100%
rename from v13/java/android/support/v13/view/inputmethod/InputContentInfoCompat.java
rename to compat/src/main/java/android/support/v13/view/inputmethod/InputContentInfoCompat.java
diff --git a/compat/src/main/java/android/support/v4/app/ActivityCompat.java b/compat/src/main/java/android/support/v4/app/ActivityCompat.java
index 5833481..9d15be1 100644
--- a/compat/src/main/java/android/support/v4/app/ActivityCompat.java
+++ b/compat/src/main/java/android/support/v4/app/ActivityCompat.java
@@ -34,7 +34,9 @@
 import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
 import android.support.annotation.RestrictTo;
+import android.support.v13.view.DragAndDropPermissionsCompat;
 import android.support.v4.content.ContextCompat;
+import android.view.DragEvent;
 import android.view.View;
 
 import java.util.List;
@@ -528,6 +530,19 @@
         return false;
     }
 
+    /**
+     * Create {@link DragAndDropPermissionsCompat} object bound to this activity and controlling
+     * the access permissions for content URIs associated with the {@link android.view.DragEvent}.
+     * @param dragEvent Drag event to request permission for
+     * @return The {@link DragAndDropPermissionsCompat} object used to control access to the content
+     * URIs. {@code null} if no content URIs are associated with the event or if permissions could
+     * not be granted.
+     */
+    public static DragAndDropPermissionsCompat requestDragAndDropPermissions(Activity activity,
+            DragEvent dragEvent) {
+        return DragAndDropPermissionsCompat.request(activity, dragEvent);
+    }
+
     @RequiresApi(21)
     private static class SharedElementCallback21Impl extends android.app.SharedElementCallback {
 
diff --git a/compat/src/main/java/android/support/v4/app/NotificationCompat.java b/compat/src/main/java/android/support/v4/app/NotificationCompat.java
index 80c7757..b3f0d32 100644
--- a/compat/src/main/java/android/support/v4/app/NotificationCompat.java
+++ b/compat/src/main/java/android/support/v4/app/NotificationCompat.java
@@ -447,6 +447,7 @@
     public @interface StreamType {}
 
     /** @hide */
+    @RestrictTo(LIBRARY_GROUP)
     @Retention(SOURCE)
     @IntDef({VISIBILITY_PUBLIC, VISIBILITY_PRIVATE, VISIBILITY_SECRET})
     public @interface NotificationVisibility {}
diff --git a/compat/src/main/java/android/support/v4/graphics/TypefaceCompatUtil.java b/compat/src/main/java/android/support/v4/graphics/TypefaceCompatUtil.java
index b5d206c..c524f82 100644
--- a/compat/src/main/java/android/support/v4/graphics/TypefaceCompatUtil.java
+++ b/compat/src/main/java/android/support/v4/graphics/TypefaceCompatUtil.java
@@ -94,11 +94,15 @@
     @RequiresApi(19)
     public static ByteBuffer mmap(Context context, CancellationSignal cancellationSignal, Uri uri) {
         final ContentResolver resolver = context.getContentResolver();
-        try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r", cancellationSignal);
-                FileInputStream fis = new FileInputStream(pfd.getFileDescriptor())) {
-            FileChannel channel = fis.getChannel();
-            final long size = channel.size();
-            return channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
+        try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r", cancellationSignal)) {
+            if (pfd == null) {
+                return null;
+            }
+            try (FileInputStream fis = new FileInputStream(pfd.getFileDescriptor())) {
+                FileChannel channel = fis.getChannel();
+                final long size = channel.size();
+                return channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
+            }
         } catch (IOException e) {
             return null;
         }
diff --git a/compat/src/main/java/android/support/v4/util/ArraySet.java b/compat/src/main/java/android/support/v4/util/ArraySet.java
index ab080fa..8444d2c 100644
--- a/compat/src/main/java/android/support/v4/util/ArraySet.java
+++ b/compat/src/main/java/android/support/v4/util/ArraySet.java
@@ -18,6 +18,8 @@
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
 import android.util.Log;
 
@@ -69,16 +71,15 @@
      * The first entry in the array is a pointer to the next array in the
      * list; the second entry is a pointer to the int[] hash code array for it.
      */
-    static Object[] sBaseCache;
-    static int sBaseCacheSize;
-    static Object[] sTwiceBaseCache;
-    static int sTwiceBaseCacheSize;
+    private static Object[] sBaseCache;
+    private static int sBaseCacheSize;
+    private static Object[] sTwiceBaseCache;
+    private static int sTwiceBaseCacheSize;
 
-    final boolean mIdentityHashCode;
-    int[] mHashes;
-    Object[] mArray;
-    int mSize;
-    MapCollections<E, E> mCollections;
+    private int[] mHashes;
+    private Object[] mArray;
+    private int mSize;
+    private MapCollections<E, E> mCollections;
 
     private int indexOf(Object key, int hash) {
         final int N = mSize;
@@ -238,19 +239,13 @@
      * will grow once items are added to it.
      */
     public ArraySet() {
-        this(0, false);
+        this(0);
     }
 
     /**
      * Create a new ArraySet with a given initial capacity.
      */
     public ArraySet(int capacity) {
-        this(capacity, false);
-    }
-
-    /** {@hide} */
-    public ArraySet(int capacity, boolean identityHashCode) {
-        mIdentityHashCode = identityHashCode;
         if (capacity == 0) {
             mHashes = INT;
             mArray = OBJECT;
@@ -263,15 +258,17 @@
     /**
      * Create a new ArraySet with the mappings from the given ArraySet.
      */
-    public ArraySet(ArraySet<E> set) {
+    public ArraySet(@Nullable ArraySet<E> set) {
         this();
         if (set != null) {
             addAll(set);
         }
     }
 
-    /** {@hide} */
-    public ArraySet(Collection<E> set) {
+    /**
+     * Create a new ArraySet with the mappings from the given {@link Collection}.
+     */
+    public ArraySet(@Nullable Collection<E> set) {
         this();
         if (set != null) {
             addAll(set);
@@ -326,8 +323,7 @@
      * @return Returns the index of the value if it exists, else a negative integer.
      */
     public int indexOf(Object key) {
-        return key == null ? indexOfNull()
-                : indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
+        return key == null ? indexOfNull() : indexOf(key, key.hashCode());
     }
 
     /**
@@ -335,6 +331,7 @@
      * @param index The desired index, must be between 0 and {@link #size()}-1.
      * @return Returns the value stored at the given index.
      */
+    @Nullable
     public E valueAt(int index) {
         return (E) mArray[index];
     }
@@ -357,14 +354,14 @@
      *             when the class of the object is inappropriate for this set.
      */
     @Override
-    public boolean add(E value) {
+    public boolean add(@Nullable E value) {
         final int hash;
         int index;
         if (value == null) {
             hash = 0;
             index = indexOfNull();
         } else {
-            hash = mIdentityHashCode ? System.identityHashCode(value) : value.hashCode();
+            hash = value.hashCode();
             index = indexOf(value, hash);
         }
         if (index >= 0) {
@@ -413,8 +410,7 @@
     @RestrictTo(LIBRARY_GROUP)
     public void append(E value) {
         final int index = mSize;
-        final int hash = value == null ? 0
-                : (mIdentityHashCode ? System.identityHashCode(value) : value.hashCode());
+        final int hash = value == null ? 0 : value.hashCode();
         if (index >= mHashes.length) {
             throw new IllegalStateException("Array is full");
         }
@@ -439,7 +435,7 @@
      * Perform a {@link #add(Object)} of all values in <var>array</var>
      * @param array The array whose contents are to be retrieved.
      */
-    public void addAll(ArraySet<? extends E> array) {
+    public void addAll(@NonNull ArraySet<? extends E> array) {
         final int N = array.mSize;
         ensureCapacity(mSize + N);
         if (mSize == 0) {
@@ -555,6 +551,7 @@
         return mSize;
     }
 
+    @NonNull
     @Override
     public Object[] toArray() {
         Object[] result = new Object[mSize];
@@ -562,8 +559,9 @@
         return result;
     }
 
+    @NonNull
     @Override
-    public <T> T[] toArray(T[] array) {
+    public <T> T[] toArray(@NonNull T[] array) {
         if (array.length < mSize) {
             @SuppressWarnings("unchecked") T[] newArray =
                     (T[]) Array.newInstance(array.getClass().getComponentType(), mSize);
@@ -732,7 +730,7 @@
      * in <var>collection</var>, else returns false.
      */
     @Override
-    public boolean containsAll(Collection<?> collection) {
+    public boolean containsAll(@NonNull Collection<?> collection) {
         Iterator<?> it = collection.iterator();
         while (it.hasNext()) {
             if (!contains(it.next())) {
@@ -747,7 +745,7 @@
      * @param collection The collection whose contents are to be retrieved.
      */
     @Override
-    public boolean addAll(Collection<? extends E> collection) {
+    public boolean addAll(@NonNull Collection<? extends E> collection) {
         ensureCapacity(mSize + collection.size());
         boolean added = false;
         for (E value : collection) {
@@ -762,7 +760,7 @@
      * @return Returns true if any values were removed from the array set, else false.
      */
     @Override
-    public boolean removeAll(Collection<?> collection) {
+    public boolean removeAll(@NonNull Collection<?> collection) {
         boolean removed = false;
         for (Object value : collection) {
             removed |= remove(value);
@@ -777,7 +775,7 @@
      * @return Returns true if any values were removed from the array set, else false.
      */
     @Override
-    public boolean retainAll(Collection<?> collection) {
+    public boolean retainAll(@NonNull Collection<?> collection) {
         boolean removed = false;
         for (int i = mSize - 1; i >= 0; i--) {
             if (!collection.contains(mArray[i])) {
diff --git a/compat/src/main/java/android/support/v4/util/ObjectsCompat.java b/compat/src/main/java/android/support/v4/util/ObjectsCompat.java
index b6c740e..747cfb4 100644
--- a/compat/src/main/java/android/support/v4/util/ObjectsCompat.java
+++ b/compat/src/main/java/android/support/v4/util/ObjectsCompat.java
@@ -18,6 +18,7 @@
 import android.os.Build;
 import android.support.annotation.Nullable;
 
+import java.util.Arrays;
 import java.util.Objects;
 
 /**
@@ -51,4 +52,46 @@
             return (a == b) || (a != null && a.equals(b));
         }
     }
+
+    /**
+     * Returns the hash code of a non-{@code null} argument and 0 for a {@code null} argument.
+     *
+     * @param o an object
+     * @return the hash code of a non-{@code null} argument and 0 for a {@code null} argument
+     * @see Object#hashCode
+     */
+    public static int hashCode(@Nullable Object o) {
+        return o != null ? o.hashCode() : 0;
+    }
+
+    /**
+     * Generates a hash code for a sequence of input values. The hash code is generated as if all
+     * the input values were placed into an array, and that array were hashed by calling
+     * {@link Arrays#hashCode(Object[])}.
+     *
+     * <p>This method is useful for implementing {@link Object#hashCode()} on objects containing
+     * multiple fields. For example, if an object that has three fields, {@code x}, {@code y}, and
+     * {@code z}, one could write:
+     *
+     * <blockquote><pre>
+     * &#064;Override public int hashCode() {
+     *     return ObjectsCompat.hash(x, y, z);
+     * }
+     * </pre></blockquote>
+     *
+     * <b>Warning: When a single object reference is supplied, the returned value does not equal the
+     * hash code of that object reference.</b> This value can be computed by calling
+     * {@link #hashCode(Object)}.
+     *
+     * @param values the values to be hashed
+     * @return a hash value of the sequence of input values
+     * @see Arrays#hashCode(Object[])
+     */
+    public static int hash(@Nullable Object... values) {
+        if (Build.VERSION.SDK_INT >= 19) {
+            return Objects.hash(values);
+        } else {
+            return Arrays.hashCode(values);
+        }
+    }
 }
diff --git a/compat/src/main/java/android/support/v4/view/ViewConfigurationCompat.java b/compat/src/main/java/android/support/v4/view/ViewConfigurationCompat.java
index f14b806..60d37a9 100644
--- a/compat/src/main/java/android/support/v4/view/ViewConfigurationCompat.java
+++ b/compat/src/main/java/android/support/v4/view/ViewConfigurationCompat.java
@@ -27,10 +27,7 @@
 
 /**
  * Helper for accessing features in {@link ViewConfiguration}.
- *
- * @deprecated Use {@link ViewConfiguration} directly.
  */
-@Deprecated
 public final class ViewConfigurationCompat {
     private static final String TAG = "ViewConfigCompat";
 
diff --git a/compat/src/main/java/android/support/v4/widget/TextViewCompat.java b/compat/src/main/java/android/support/v4/widget/TextViewCompat.java
index dc87a38..8789815 100644
--- a/compat/src/main/java/android/support/v4/widget/TextViewCompat.java
+++ b/compat/src/main/java/android/support/v4/widget/TextViewCompat.java
@@ -18,6 +18,11 @@
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.support.annotation.DrawableRes;
@@ -28,14 +33,22 @@
 import android.support.annotation.RestrictTo;
 import android.support.annotation.StyleRes;
 import android.support.v4.os.BuildCompat;
+import android.text.Editable;
 import android.util.Log;
 import android.util.TypedValue;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuItem;
 import android.view.View;
 import android.widget.TextView;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Helper for accessing features in {@link TextView}.
@@ -219,6 +232,11 @@
             }
             return new int[0];
         }
+
+        public void setCustomSelectionActionModeCallback(TextView textView,
+                ActionMode.Callback callback) {
+            textView.setCustomSelectionActionModeCallback(callback);
+        }
     }
 
     @RequiresApi(16)
@@ -314,8 +332,160 @@
         }
     }
 
+    @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 TextViewCompatApi23Impl {
+    static class TextViewCompatApi27Impl extends TextViewCompatApi26Impl {
         @Override
         public void setAutoSizeTextTypeWithDefaults(TextView textView, int autoSizeTextType) {
             textView.setAutoSizeTextTypeWithDefaults(autoSizeTextType);
@@ -369,6 +539,8 @@
     static {
         if (BuildCompat.isAtLeastOMR1()) {
             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) {
@@ -600,4 +772,31 @@
     public static int[] getAutoSizeTextAvailableSizes(@NonNull TextView textView) {
         return IMPL.getAutoSizeTextAvailableSizes(textView);
     }
+
+    /**
+     * Sets a selection action mode callback on a TextView.
+     *
+     * Also this method can be used to fix a bug in framework SDK 26. On these affected devices,
+     * the bug causes the menu containing the options for handling ACTION_PROCESS_TEXT after text
+     * selection to miss a number of items. This method can be used to fix this wrong behaviour for
+     * a text view, by passing any custom callback implementation. If no custom callback is desired,
+     * a no-op implementation should be provided.
+     *
+     * Note that, by default, the bug will only be fixed when the default floating toolbar menu
+     * implementation is used. If a custom implementation of {@link Menu} is provided, this should
+     * provide the method Menu#removeItemAt(int) which removes a menu item by its position,
+     * as given by Menu#getItem(int). Also, the following post condition should hold: a call
+     * to removeItemAt(i), should not modify the results of getItem(j) for any j < i. Intuitively,
+     * removing an element from the menu should behave as removing an element from a list.
+     * Note that this method does not exist in the {@link Menu} interface. However, it is required,
+     * and going to be called by reflection, in order to display the correct process text items in
+     * the menu.
+     *
+     * @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);
+    }
 }
diff --git a/compat/tests/AndroidManifest.xml b/compat/tests/AndroidManifest.xml
index ed6727f..8f78188 100644
--- a/compat/tests/AndroidManifest.xml
+++ b/compat/tests/AndroidManifest.xml
@@ -40,6 +40,8 @@
         <activity android:name="android.support.v4.app.TestSupportActivity"
                   android:icon="@drawable/test_drawable_blue"/>
 
+        <activity android:name="android.support.v13.view.DragStartHelperTestActivity"/>
+
         <provider android:name="android.support.v4.provider.MockFontProvider"
                   android:authorities="android.support.provider.fonts.font"
                   android:exported="false"
diff --git a/v13/tests/java/android/support/v13/view/DragStartHelperTest.java b/compat/tests/java/android/support/v13/view/DragStartHelperTest.java
similarity index 99%
rename from v13/tests/java/android/support/v13/view/DragStartHelperTest.java
rename to compat/tests/java/android/support/v13/view/DragStartHelperTest.java
index 993a4e5..67dda14 100644
--- a/v13/tests/java/android/support/v13/view/DragStartHelperTest.java
+++ b/compat/tests/java/android/support/v13/view/DragStartHelperTest.java
@@ -38,7 +38,7 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
-import android.support.v13.test.R;
+import android.support.compat.test.R;
 import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.View;
diff --git a/v13/tests/java/android/support/v13/view/DragStartHelperTestActivity.java b/compat/tests/java/android/support/v13/view/DragStartHelperTestActivity.java
similarity index 95%
rename from v13/tests/java/android/support/v13/view/DragStartHelperTestActivity.java
rename to compat/tests/java/android/support/v13/view/DragStartHelperTestActivity.java
index 6a3605b..40da559 100644
--- a/v13/tests/java/android/support/v13/view/DragStartHelperTestActivity.java
+++ b/compat/tests/java/android/support/v13/view/DragStartHelperTestActivity.java
@@ -18,7 +18,7 @@
 
 import android.app.Activity;
 import android.os.Bundle;
-import android.support.v13.test.R;
+import android.support.compat.test.R;
 
 public class DragStartHelperTestActivity extends Activity {
     @Override
diff --git a/compat/tests/java/android/support/v4/app/ActivityCompatTest.java b/compat/tests/java/android/support/v4/app/ActivityCompatTest.java
index 35889fb..3b4f1b5 100644
--- a/compat/tests/java/android/support/v4/app/ActivityCompatTest.java
+++ b/compat/tests/java/android/support/v4/app/ActivityCompatTest.java
@@ -25,7 +25,6 @@
 
 import android.Manifest;
 import android.app.Activity;
-import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v4.BaseInstrumentationTestCase;
@@ -41,7 +40,6 @@
         super(TestSupportActivity.class);
     }
 
-    @SdkSuppress(minSdkVersion = 24)
     @SmallTest
     @Test
     public void testPermissionDelegate() {
diff --git a/compat/tests/java/android/support/v4/graphics/TypefaceCompatUtilTest.java b/compat/tests/java/android/support/v4/graphics/TypefaceCompatUtilTest.java
new file mode 100644
index 0000000..47e6130
--- /dev/null
+++ b/compat/tests/java/android/support/v4/graphics/TypefaceCompatUtilTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.graphics;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.v4.provider.MockFontProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@SmallTest
+public class TypefaceCompatUtilTest {
+
+    public Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    @Test
+    @RequiresApi(19)
+    public void testMmapNullPfd() {
+        if (Build.VERSION.SDK_INT < 19) {
+            // The API tested here requires SDK level 19.
+            return;
+        }
+        final Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(MockFontProvider.AUTHORITY).build();
+        final Uri fileUri = ContentUris.withAppendedId(uri, MockFontProvider.INVALID_FONT_FILE_ID);
+        // Should not crash.
+        TypefaceCompatUtil.mmap(mContext, null, fileUri);
+    }
+}
diff --git a/compat/tests/java/android/support/v4/provider/MockFontProvider.java b/compat/tests/java/android/support/v4/provider/MockFontProvider.java
index f584d68..04759d0 100644
--- a/compat/tests/java/android/support/v4/provider/MockFontProvider.java
+++ b/compat/tests/java/android/support/v4/provider/MockFontProvider.java
@@ -40,10 +40,12 @@
  * Provides a test Content Provider implementing {@link FontsContractCompat}.
  */
 public class MockFontProvider extends ContentProvider {
+    public static final String AUTHORITY = "android.support.provider.fonts.font";
+
     static final String[] FONT_FILES = {
             "samplefont.ttf", "large_a.ttf", "large_b.ttf", "large_c.ttf", "large_d.ttf"
     };
-    private static final int INVALID_FONT_FILE_ID = -1;
+    public static final int INVALID_FONT_FILE_ID = -1;
     private static final int SAMPLE_FONT_FILE_0_ID = 0;
     private static final int LARGE_A_FILE_ID = 1;
     private static final int LARGE_B_FILE_ID = 2;
diff --git a/compat/tests/java/android/support/v4/util/ObjectsCompatTest.java b/compat/tests/java/android/support/v4/util/ObjectsCompatTest.java
index b836ae0..f48f9b7 100644
--- a/compat/tests/java/android/support/v4/util/ObjectsCompatTest.java
+++ b/compat/tests/java/android/support/v4/util/ObjectsCompatTest.java
@@ -16,6 +16,7 @@
 
 package android.support.v4.util;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -48,4 +49,12 @@
         assertTrue(ObjectsCompat.equals(a, c));
     }
 
+    @Test
+    public void testHashCode() {
+        String a = "aaa";
+        String n = null;
+
+        assertEquals(ObjectsCompat.hashCode(a), a.hashCode());
+        assertEquals(ObjectsCompat.hashCode(n), 0);
+    }
 }
diff --git a/compat/tests/java/android/support/v4/widget/TextViewCompatTest.java b/compat/tests/java/android/support/v4/widget/TextViewCompatTest.java
index 0a66e6b..cf2b52f 100644
--- a/compat/tests/java/android/support/v4/widget/TextViewCompatTest.java
+++ b/compat/tests/java/android/support/v4/widget/TextViewCompatTest.java
@@ -30,20 +30,43 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Looper;
 import android.support.annotation.ColorInt;
 import android.support.compat.test.R;
+import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.v4.BaseInstrumentationTestCase;
 import android.support.v4.testutils.TestUtils;
 import android.support.v4.view.ViewCompat;
+import android.support.v7.view.menu.MenuBuilder;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
 import android.widget.TextView;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.List;
 
 @SmallTest
 public class TextViewCompatTest extends BaseInstrumentationTestCase<TextViewTestActivity> {
@@ -411,4 +434,130 @@
         assertEquals(drawableEnd, drawablesRelative[2]);
         assertEquals(drawableBottom, drawablesRelative[3]);
     }
+
+    @Test
+    public void testSetCustomSelectionActionModeCallback_doesNotIgnoreTheGivenCallback() {
+        // JB devices require the current thread to be prepared as a looper for this test.
+        // The test causes the creation of an Editor object, which uses an UserDictionaryListener
+        // that is handled on the main looper.
+        Looper.prepare();
+
+        final boolean[] callbackCalled = new boolean[4];
+        TextViewCompat.setCustomSelectionActionModeCallback(mTextView, new ActionMode.Callback() {
+            @Override
+            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+                callbackCalled[0] = true;
+                return true;
+            }
+
+            @Override
+            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+                callbackCalled[1] = true;
+                return true;
+            }
+
+            @Override
+            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+                callbackCalled[2] = true;
+                return true;
+            }
+
+            @Override
+            public void onDestroyActionMode(ActionMode mode) {
+                callbackCalled[3] = true;
+            }
+        });
+        final Menu menu = new MenuBuilder(mTextView.getContext());
+        final MenuItem item = menu.add("Option");
+        mTextView.getCustomSelectionActionModeCallback().onCreateActionMode(null, menu);
+        mTextView.getCustomSelectionActionModeCallback().onPrepareActionMode(null, menu);
+        mTextView.getCustomSelectionActionModeCallback().onActionItemClicked(null, item);
+        mTextView.getCustomSelectionActionModeCallback().onDestroyActionMode(null);
+        for (boolean called : callbackCalled) {
+            assertTrue(called);
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 26, maxSdkVersion =  27)
+    public void testSetCustomSelectionActionModeCallback_fixesBugInO() {
+        // Create mock context and package manager for the text view.
+        final PackageManager packageManagerMock = spy(mTextView.getContext().getPackageManager());
+        final Context contextMock = spy(mTextView.getContext());
+        when(contextMock.getPackageManager()).thenReturn(packageManagerMock);
+        final TextView tvMock = spy(mTextView);
+        // Set the new context on textViewMock by reflection, as TextView#getContext() is final.
+        try {
+            final Field contextField = View.class.getDeclaredField("mContext");
+            contextField.setAccessible(true);
+            contextField.set(tvMock, contextMock);
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            // We should be able to set mContext by reflection.
+            assertTrue(false);
+        }
+        // Create fake activities able to handle the ACTION_PROCESS_TEXT intent.
+        final ResolveInfo info1 = new ResolveInfo();
+        info1.activityInfo = new ActivityInfo();
+        info1.activityInfo.packageName = contextMock.getPackageName();
+        info1.activityInfo.name = "Activity 1";
+        info1.nonLocalizedLabel = "Option 3";
+        final ResolveInfo info2 = new ResolveInfo();
+        info2.activityInfo = new ActivityInfo();
+        info2.activityInfo.packageName = contextMock.getPackageName();
+        info2.activityInfo.name = "Activity 2";
+        info2.nonLocalizedLabel = "Option 4";
+        final ResolveInfo info3 = new ResolveInfo();
+        info3.activityInfo = new ActivityInfo();
+        info3.activityInfo.packageName = contextMock.getPackageName();
+        info3.activityInfo.name = "Activity 3";
+        info3.nonLocalizedLabel = "Option 5";
+        final List<ResolveInfo> infos = Arrays.asList(info1, info2, info3);
+        doAnswer(new Answer() {
+            @Override
+            public Object answer(final InvocationOnMock invocation) throws Throwable {
+                final Intent intent = invocation.getArgument(0);
+                if (Intent.ACTION_PROCESS_TEXT.equals(intent.getAction())) {
+                    return infos;
+                }
+                return invocation.callRealMethod();
+            }
+        }).when(packageManagerMock).queryIntentActivities((Intent) any(), anyInt());
+        // Set a no op callback on the mocked text view, which should fix the SDK26 bug.
+        TextViewCompat.setCustomSelectionActionModeCallback(tvMock, new ActionMode.Callback() {
+            @Override
+            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+                return true;
+            }
+
+            @Override
+            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+                return true;
+            }
+
+            @Override
+            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+                return true;
+            }
+
+            @Override
+            public void onDestroyActionMode(ActionMode mode) {
+
+            }
+        });
+        // Create a fake menu with two non process text items and two process text items.
+        final Menu menu = new MenuBuilder(tvMock.getContext());
+        menu.add(Menu.NONE, Menu.NONE, 1, "Option 1");
+        menu.add(Menu.NONE, Menu.NONE, 2, "Option 2");
+        menu.add(Menu.NONE, Menu.NONE, 100, "Option 3")
+                .setIntent(new Intent(Intent.ACTION_PROCESS_TEXT));
+        menu.add(Menu.NONE, Menu.NONE, 101, "Option 5")
+                .setIntent(new Intent(Intent.ACTION_PROCESS_TEXT));
+        // Run the callback and verify that the menu was updated. Its size should have increased
+        // with 1, as now there are 3 process text options instead of 2 to be displayed.
+        tvMock.getCustomSelectionActionModeCallback().onPrepareActionMode(null, menu);
+        assertEquals(5, menu.size());
+        for (int i = 0; i < menu.size(); ++i) {
+            assertEquals("Option " + (i + 1), menu.getItem(i).getTitle());
+        }
+    }
 }
diff --git a/v13/tests/res/layout/drag_source_activity.xml b/compat/tests/res/layout/drag_source_activity.xml
similarity index 100%
rename from v13/tests/res/layout/drag_source_activity.xml
rename to compat/tests/res/layout/drag_source_activity.xml
diff --git a/core-ui/src/main/java/android/support/v4/widget/CursorAdapter.java b/core-ui/src/main/java/android/support/v4/widget/CursorAdapter.java
index e68229e..3ea6fc8 100644
--- a/core-ui/src/main/java/android/support/v4/widget/CursorAdapter.java
+++ b/core-ui/src/main/java/android/support/v4/widget/CursorAdapter.java
@@ -43,55 +43,55 @@
         CursorFilter.CursorFilterClient {
     /**
      * This field should be made private, so it is hidden from the SDK.
-     * {@hide}
+     * @hide
      */
     @RestrictTo(LIBRARY_GROUP)
     protected boolean mDataValid;
     /**
      * This field should be made private, so it is hidden from the SDK.
-     * {@hide}
+     * @hide
      */
     @RestrictTo(LIBRARY_GROUP)
     protected boolean mAutoRequery;
     /**
      * This field should be made private, so it is hidden from the SDK.
-     * {@hide}
+     * @hide
      */
     @RestrictTo(LIBRARY_GROUP)
     protected Cursor mCursor;
     /**
      * This field should be made private, so it is hidden from the SDK.
-     * {@hide}
+     * @hide
      */
     @RestrictTo(LIBRARY_GROUP)
     protected Context mContext;
     /**
      * This field should be made private, so it is hidden from the SDK.
-     * {@hide}
+     * @hide
      */
     @RestrictTo(LIBRARY_GROUP)
     protected int mRowIDColumn;
     /**
      * This field should be made private, so it is hidden from the SDK.
-     * {@hide}
+     * @hide
      */
     @RestrictTo(LIBRARY_GROUP)
     protected ChangeObserver mChangeObserver;
     /**
      * This field should be made private, so it is hidden from the SDK.
-     * {@hide}
+     * @hide
      */
     @RestrictTo(LIBRARY_GROUP)
     protected DataSetObserver mDataSetObserver;
     /**
      * This field should be made private, so it is hidden from the SDK.
-     * {@hide}
+     * @hide
      */
     @RestrictTo(LIBRARY_GROUP)
     protected CursorFilter mCursorFilter;
     /**
      * This field should be made private, so it is hidden from the SDK.
-     * {@hide}
+     * @hide
      */
     @RestrictTo(LIBRARY_GROUP)
     protected FilterQueryProvider mFilterQueryProvider;
diff --git a/core-ui/src/main/java/android/support/v4/widget/SimpleCursorAdapter.java b/core-ui/src/main/java/android/support/v4/widget/SimpleCursorAdapter.java
index 291f9e1..ba3ee50 100644
--- a/core-ui/src/main/java/android/support/v4/widget/SimpleCursorAdapter.java
+++ b/core-ui/src/main/java/android/support/v4/widget/SimpleCursorAdapter.java
@@ -37,14 +37,14 @@
     /**
      * A list of columns containing the data to bind to the UI.
      * This field should be made private, so it is hidden from the SDK.
-     * {@hide}
+     * @hide
      */
     @RestrictTo(LIBRARY_GROUP)
     protected int[] mFrom;
     /**
      * A list of View ids representing the views to which the data must be bound.
      * This field should be made private, so it is hidden from the SDK.
-     * {@hide}
+     * @hide
      */
     @RestrictTo(LIBRARY_GROUP)
     protected int[] mTo;
diff --git a/customtabs/api/current.txt b/customtabs/api/current.txt
index 8494391..e172dba 100644
--- a/customtabs/api/current.txt
+++ b/customtabs/api/current.txt
@@ -155,3 +155,54 @@
 
 }
 
+package androidx.browser.browseractions {
+
+  public class BrowserActionItem {
+    ctor public BrowserActionItem(java.lang.String, android.app.PendingIntent, int);
+    ctor public BrowserActionItem(java.lang.String, android.app.PendingIntent);
+    method public android.app.PendingIntent getAction();
+    method public int getIconId();
+    method public java.lang.String getTitle();
+  }
+
+  public class BrowserActionsIntent {
+    method public static java.lang.String getCreatorPackageName(android.content.Intent);
+    method public android.content.Intent getIntent();
+    method public static void launchIntent(android.content.Context, android.content.Intent);
+    method public static void openBrowserAction(android.content.Context, android.net.Uri);
+    method public static void openBrowserAction(android.content.Context, android.net.Uri, int, java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem>, android.app.PendingIntent);
+    method public static java.util.List<androidx.browser.browseractions.BrowserActionItem> parseBrowserActionItems(java.util.ArrayList<android.os.Bundle>);
+    field public static final java.lang.String ACTION_BROWSER_ACTIONS_OPEN = "androidx.browser.browseractions.browser_action_open";
+    field public static final java.lang.String EXTRA_APP_ID = "androidx.browser.browseractions.APP_ID";
+    field public static final java.lang.String EXTRA_MENU_ITEMS = "androidx.browser.browseractions.extra.MENU_ITEMS";
+    field public static final java.lang.String EXTRA_SELECTED_ACTION_PENDING_INTENT = "androidx.browser.browseractions.extra.SELECTED_ACTION_PENDING_INTENT";
+    field public static final java.lang.String EXTRA_TYPE = "androidx.browser.browseractions.extra.TYPE";
+    field public static final int ITEM_COPY = 3; // 0x3
+    field public static final int ITEM_DOWNLOAD = 2; // 0x2
+    field public static final int ITEM_INVALID_ITEM = -1; // 0xffffffff
+    field public static final int ITEM_OPEN_IN_INCOGNITO = 1; // 0x1
+    field public static final int ITEM_OPEN_IN_NEW_TAB = 0; // 0x0
+    field public static final int ITEM_SHARE = 4; // 0x4
+    field public static final java.lang.String KEY_ACTION = "androidx.browser.browseractions.ACTION";
+    field public static final java.lang.String KEY_ICON_ID = "androidx.browser.browseractions.ICON_ID";
+    field public static final java.lang.String KEY_TITLE = "androidx.browser.browseractions.TITLE";
+    field public static final int MAX_CUSTOM_ITEMS = 5; // 0x5
+    field public static final int URL_TYPE_AUDIO = 3; // 0x3
+    field public static final int URL_TYPE_FILE = 4; // 0x4
+    field public static final int URL_TYPE_IMAGE = 1; // 0x1
+    field public static final int URL_TYPE_NONE = 0; // 0x0
+    field public static final int URL_TYPE_PLUGIN = 5; // 0x5
+    field public static final int URL_TYPE_VIDEO = 2; // 0x2
+  }
+
+  public static final class BrowserActionsIntent.Builder {
+    ctor public BrowserActionsIntent.Builder(android.content.Context, android.net.Uri);
+    method public androidx.browser.browseractions.BrowserActionsIntent build();
+    method public androidx.browser.browseractions.BrowserActionsIntent.Builder setCustomItems(java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem>);
+    method public androidx.browser.browseractions.BrowserActionsIntent.Builder setCustomItems(androidx.browser.browseractions.BrowserActionItem...);
+    method public androidx.browser.browseractions.BrowserActionsIntent.Builder setOnItemSelectedAction(android.app.PendingIntent);
+    method public androidx.browser.browseractions.BrowserActionsIntent.Builder setUrlType(int);
+  }
+
+}
+
diff --git a/customtabs/src/main/java/android/support/customtabs/CustomTabsClient.java b/customtabs/src/main/java/android/support/customtabs/CustomTabsClient.java
index 2e955cb..371b5a1 100644
--- a/customtabs/src/main/java/android/support/customtabs/CustomTabsClient.java
+++ b/customtabs/src/main/java/android/support/customtabs/CustomTabsClient.java
@@ -45,7 +45,7 @@
     private final ICustomTabsService mService;
     private final ComponentName mServiceComponentName;
 
-    /**@hide*/
+    /** @hide */
     @RestrictTo(LIBRARY_GROUP)
     CustomTabsClient(ICustomTabsService service, ComponentName componentName) {
         mService = service;
diff --git a/customtabs/src/main/java/androidx/browser/browseractions/BrowserActionItem.java b/customtabs/src/main/java/androidx/browser/browseractions/BrowserActionItem.java
new file mode 100644
index 0000000..4bcb83e
--- /dev/null
+++ b/customtabs/src/main/java/androidx/browser/browseractions/BrowserActionItem.java
@@ -0,0 +1,75 @@
+/*
+ * 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.browser.browseractions;
+
+import android.app.PendingIntent;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
+
+/**
+ * A wrapper class holding custom item of Browser Actions menu.
+ * The Bitmap is optional for a BrowserActionItem.
+ */
+public class BrowserActionItem {
+    private final String mTitle;
+    private final PendingIntent mAction;
+    @DrawableRes
+    private final int mIconId;
+
+    /**
+     * Constructor for BrowserActionItem with icon, string and action provided.
+     * @param title The string shown for a custom item.
+     * @param action The PendingIntent executed when a custom item is selected
+     * @param iconId The resource id of the icon shown for a custom item.
+     */
+    public BrowserActionItem(
+            @NonNull String title, @NonNull PendingIntent action, @DrawableRes int iconId) {
+        mTitle = title;
+        mAction = action;
+        mIconId = iconId;
+    }
+
+    /**
+     * Constructor for BrowserActionItem with only string and action provided.
+     * @param title The icon shown for a custom item.
+     * @param action The string shown for a custom item.
+     */
+    public BrowserActionItem(@NonNull String title, @NonNull PendingIntent action) {
+        this(title, action, 0);
+    }
+
+    /**
+     * @return The resource id of the icon.
+     */
+    public int getIconId() {
+        return mIconId;
+    }
+
+    /**
+     * @return The title of a custom item.
+     */
+    public String getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * @return The action of a custom item.
+     */
+    public PendingIntent getAction() {
+        return mAction;
+    }
+}
diff --git a/customtabs/src/main/java/androidx/browser/browseractions/BrowserActionsIntent.java b/customtabs/src/main/java/androidx/browser/browseractions/BrowserActionsIntent.java
new file mode 100644
index 0000000..beb3d6c
--- /dev/null
+++ b/customtabs/src/main/java/androidx/browser/browseractions/BrowserActionsIntent.java
@@ -0,0 +1,391 @@
+/*
+ * 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.browser.browseractions;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+import android.support.v4.content.ContextCompat;
+import android.text.TextUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Class holding the {@link Intent} and start bundle for a Browser Actions Activity.
+ *
+ * <p>
+ * <strong>Note:</strong> The constants below are public for the browser implementation's benefit.
+ * You are strongly encouraged to use {@link BrowserActionsIntent.Builder}.</p>
+ */
+public class BrowserActionsIntent {
+    private static final String TAG = "BrowserActions";
+    // Used to verify that an URL intent handler exists.
+    private static final String TEST_URL = "https://www.example.com";
+
+    /**
+     * Extra that specifies {@link PendingIntent} indicating which Application sends the {@link
+     * BrowserActionsIntent}.
+     */
+    public static final String EXTRA_APP_ID = "androidx.browser.browseractions.APP_ID";
+
+    /**
+     * Indicates that the user explicitly opted out of Browser Actions in the calling application.
+     */
+    public static final String ACTION_BROWSER_ACTIONS_OPEN =
+            "androidx.browser.browseractions.browser_action_open";
+
+    /**
+     * Extra resource id that specifies the icon of a custom item shown in the Browser Actions menu.
+     */
+    public static final String KEY_ICON_ID = "androidx.browser.browseractions.ICON_ID";
+
+    /**
+     * Extra string that specifies the title of a custom item shown in the Browser Actions menu.
+     */
+    public static final String KEY_TITLE = "androidx.browser.browseractions.TITLE";
+
+    /**
+     * Extra PendingIntent to be launched when a custom item is selected in the Browser Actions
+     * menu.
+     */
+    public static final String KEY_ACTION = "androidx.browser.browseractions.ACTION";
+
+    /**
+     * Extra that specifies the type of url for the Browser Actions menu.
+     */
+    public static final String EXTRA_TYPE = "androidx.browser.browseractions.extra.TYPE";
+
+    /**
+     * Extra that specifies List<Bundle> used for adding custom items to the Browser Actions menu.
+     */
+    public static final String EXTRA_MENU_ITEMS =
+            "androidx.browser.browseractions.extra.MENU_ITEMS";
+
+    /**
+     * Extra that specifies the PendingIntent to be launched when a browser specified menu item is
+     * selected. The id of the chosen item will be notified through the data of its Intent.
+     */
+    public static final String EXTRA_SELECTED_ACTION_PENDING_INTENT =
+            "androidx.browser.browseractions.extra.SELECTED_ACTION_PENDING_INTENT";
+
+    /**
+     * The maximum allowed number of custom items.
+     */
+    public static final int MAX_CUSTOM_ITEMS = 5;
+
+    /**
+     * Defines the types of url for Browser Actions menu.
+     */
+    /** @hide */
+    @RestrictTo(LIBRARY_GROUP)
+    @IntDef({URL_TYPE_NONE, URL_TYPE_IMAGE, URL_TYPE_VIDEO, URL_TYPE_AUDIO, URL_TYPE_FILE,
+            URL_TYPE_PLUGIN})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BrowserActionsUrlType {}
+    public static final int URL_TYPE_NONE = 0;
+    public static final int URL_TYPE_IMAGE = 1;
+    public static final int URL_TYPE_VIDEO = 2;
+    public static final int URL_TYPE_AUDIO = 3;
+    public static final int URL_TYPE_FILE = 4;
+    public static final int URL_TYPE_PLUGIN = 5;
+
+    /**
+     * Defines the the ids of the browser specified menu items in Browser Actions.
+     * TODO(ltian): A long term solution need, since other providers might have customized menus.
+     */
+    /** @hide */
+    @RestrictTo(LIBRARY_GROUP)
+    @IntDef({ITEM_INVALID_ITEM, ITEM_OPEN_IN_NEW_TAB, ITEM_OPEN_IN_INCOGNITO, ITEM_DOWNLOAD,
+            ITEM_COPY, ITEM_SHARE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BrowserActionsItemId {}
+    public static final int ITEM_INVALID_ITEM = -1;
+    public static final int ITEM_OPEN_IN_NEW_TAB = 0;
+    public static final int ITEM_OPEN_IN_INCOGNITO = 1;
+    public static final int ITEM_DOWNLOAD = 2;
+    public static final int ITEM_COPY = 3;
+    public static final int ITEM_SHARE = 4;
+
+    /**
+     * An {@link Intent} used to start the Browser Actions Activity.
+     */
+    @NonNull private final Intent mIntent;
+
+    /**
+     * Gets the Intent of {@link BrowserActionsIntent}.
+     * @return the Intent of {@link BrowserActionsIntent}.
+     */
+    @NonNull public Intent getIntent() {
+        return mIntent;
+    }
+
+    private BrowserActionsIntent(@NonNull Intent intent) {
+        this.mIntent = intent;
+    }
+
+    /**
+     * Builder class for opening a Browser Actions context menu.
+     */
+    public static final class Builder {
+        private final Intent mIntent = new Intent(BrowserActionsIntent.ACTION_BROWSER_ACTIONS_OPEN);
+        private Context mContext;
+        private Uri mUri;
+        @BrowserActionsUrlType
+        private int mType;
+        private ArrayList<Bundle> mMenuItems = null;
+        private PendingIntent mOnItemSelectedPendingIntent = null;
+
+        /**
+         * Constructs a {@link BrowserActionsIntent.Builder} object associated with default setting
+         * for a selected url.
+         * @param context The context requesting the Browser Actions context menu.
+         * @param uri The selected url for Browser Actions menu.
+         */
+        public Builder(Context context, Uri uri) {
+            mContext = context;
+            mUri = uri;
+            mType = URL_TYPE_NONE;
+            mMenuItems = new ArrayList<>();
+        }
+
+        /**
+         * Sets the type of Browser Actions context menu.
+         * @param type The type of url.
+         */
+        public Builder setUrlType(@BrowserActionsUrlType int type) {
+            mType = type;
+            return this;
+        }
+
+        /**
+         * Sets the custom items list.
+         * Only maximum MAX_CUSTOM_ITEMS custom items are allowed,
+         * otherwise throws an {@link IllegalStateException}.
+         * @param items The list of {@link BrowserActionItem} for custom items.
+         */
+        public Builder setCustomItems(ArrayList<BrowserActionItem> items) {
+            if (items.size() > MAX_CUSTOM_ITEMS) {
+                throw new IllegalStateException(
+                        "Exceeded maximum toolbar item count of " + MAX_CUSTOM_ITEMS);
+            }
+            for (int i = 0; i < items.size(); i++) {
+                if (TextUtils.isEmpty(items.get(i).getTitle())
+                        || items.get(i).getAction() == null) {
+                    throw new IllegalArgumentException(
+                            "Custom item should contain a non-empty title and non-null intent.");
+                } else {
+                    mMenuItems.add(getBundleFromItem(items.get(i)));
+                }
+            }
+            return this;
+        }
+
+        /**
+         * Sets the custom items list.
+         * Only maximum MAX_CUSTOM_ITEMS custom items are allowed,
+         * otherwise throws an {@link IllegalStateException}.
+         * @param items The varargs of {@link BrowserActionItem} for custom items.
+         */
+        public Builder setCustomItems(BrowserActionItem... items) {
+            return setCustomItems(new ArrayList<BrowserActionItem>(Arrays.asList(items)));
+        }
+
+        /**
+         * Set the PendingIntent to be launched when a a browser specified menu item is selected.
+         * @param onItemSelectedPendingIntent The PendingIntent to be launched.
+         */
+        public Builder setOnItemSelectedAction(PendingIntent onItemSelectedPendingIntent) {
+            mOnItemSelectedPendingIntent = onItemSelectedPendingIntent;
+            return this;
+        }
+
+        /**
+         * Populates a {@link Bundle} to hold a custom item for Browser Actions menu.
+         * @param item A custom item for Browser Actions menu.
+         * @return The Bundle of custom item.
+         */
+        private Bundle getBundleFromItem(BrowserActionItem item) {
+            Bundle bundle = new Bundle();
+            bundle.putString(KEY_TITLE, item.getTitle());
+            bundle.putParcelable(KEY_ACTION, item.getAction());
+            if (item.getIconId() != 0) bundle.putInt(KEY_ICON_ID, item.getIconId());
+            return bundle;
+        }
+
+        /**
+         * Combines all the options that have been set and returns a new {@link
+         * BrowserActionsIntent} object.
+         */
+        public BrowserActionsIntent build() {
+            mIntent.setData(mUri);
+            mIntent.putExtra(EXTRA_TYPE, mType);
+            mIntent.putParcelableArrayListExtra(EXTRA_MENU_ITEMS, mMenuItems);
+            PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+            mIntent.putExtra(EXTRA_APP_ID, pendingIntent);
+            if (mOnItemSelectedPendingIntent != null) {
+                mIntent.putExtra(
+                        EXTRA_SELECTED_ACTION_PENDING_INTENT, mOnItemSelectedPendingIntent);
+            }
+            return new BrowserActionsIntent(mIntent);
+        }
+    }
+
+    /**
+     * Construct a BrowserActionsIntent with default settings and launch it to open a Browser
+     * Actions menu.
+     * @param context The context requesting for a Browser Actions menu.
+     * @param uri The url for Browser Actions menu.
+     */
+    public static void openBrowserAction(Context context, Uri uri) {
+        BrowserActionsIntent intent = new BrowserActionsIntent.Builder(context, uri).build();
+        launchIntent(context, intent.getIntent());
+    }
+
+    /**
+     * Construct a BrowserActionsIntent with custom settings and launch it to open a Browser Actions
+     * menu.
+     * @param context The context requesting for a Browser Actions menu.
+     * @param uri The url for Browser Actions menu.
+     * @param type The type of the url for context menu to be opened.
+     * @param items List of custom items to be added to Browser Actions menu.
+     * @param pendingIntent The PendingIntent to be launched when a browser specified menu item is
+     * selected.
+     */
+    public static void openBrowserAction(Context context, Uri uri, int type,
+            ArrayList<BrowserActionItem> items, PendingIntent pendingIntent) {
+        BrowserActionsIntent intent = new BrowserActionsIntent.Builder(context, uri)
+                .setUrlType(type)
+                .setCustomItems(items)
+                .setOnItemSelectedAction(pendingIntent)
+                .build();
+        launchIntent(context, intent.getIntent());
+    }
+
+    /**
+     * Launch an Intent to open a Browser Actions menu.
+     * It first checks if any Browser Actions provider is available to create the menu.
+     * If the default Browser supports Browser Actions, menu will be opened by the default Browser,
+     * otherwise show a intent picker.
+     * If not provider, a Browser Actions menu is opened locally from support library.
+     * @param context The context requesting for a Browser Actions menu.
+     * @param intent The {@link Intent} holds the setting for Browser Actions menu.
+     */
+    public static void launchIntent(Context context, Intent intent) {
+        List<ResolveInfo> handlers = getBrowserActionsIntentHandlers(context);
+        if (handlers == null || handlers.size() == 0) {
+            openFallbackBrowserActionsMenu(context, intent);
+            return;
+        } else if (handlers.size() == 1) {
+            intent.setPackage(handlers.get(0).activityInfo.packageName);
+        } else {
+            Intent viewIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(TEST_URL));
+            PackageManager pm = context.getPackageManager();
+            ResolveInfo defaultHandler =
+                    pm.resolveActivity(viewIntent, PackageManager.MATCH_DEFAULT_ONLY);
+            if (defaultHandler != null) {
+                String defaultPackageName = defaultHandler.activityInfo.packageName;
+                for (int i = 0; i < handlers.size(); i++) {
+                    if (defaultPackageName.equals(handlers.get(i).activityInfo.packageName)) {
+                        intent.setPackage(defaultPackageName);
+                        break;
+                    }
+                }
+            }
+        }
+        ContextCompat.startActivity(context, intent, null);
+    }
+
+    /**
+     * Returns a list of Browser Actions providers available to handle the {@link
+     * BrowserActionsIntent}.
+     * @param context The context requesting for a Browser Actions menu.
+     * @return List of Browser Actions providers available to handle the intent.
+     */
+    private static List<ResolveInfo> getBrowserActionsIntentHandlers(Context context) {
+        Intent intent =
+                new Intent(BrowserActionsIntent.ACTION_BROWSER_ACTIONS_OPEN, Uri.parse(TEST_URL));
+        PackageManager pm = context.getPackageManager();
+        return pm.queryIntentActivities(intent, PackageManager.MATCH_ALL);
+    }
+
+    private static void openFallbackBrowserActionsMenu(Context context, Intent intent) {
+        Uri uri = intent.getData();
+        int type = intent.getIntExtra(EXTRA_TYPE, URL_TYPE_NONE);
+        ArrayList<Bundle> bundles = intent.getParcelableArrayListExtra(EXTRA_MENU_ITEMS);
+        List<BrowserActionItem> items = bundles != null ? parseBrowserActionItems(bundles) : null;
+        // TODO(ltian): display a fallback dialog showing all custom items from support library.
+        // http://crbug.com/789806.
+        return;
+    }
+
+    /**
+     * Gets custom item list for browser action menu.
+     * @param bundles Data for custom items from {@link BrowserActionsIntent}.
+     * @return List of {@link BrowserActionItem}
+     */
+    public static List<BrowserActionItem> parseBrowserActionItems(ArrayList<Bundle> bundles) {
+        List<BrowserActionItem> mActions = new ArrayList<>();
+        for (int i = 0; i < bundles.size(); i++) {
+            Bundle bundle = bundles.get(i);
+            String title = bundle.getString(BrowserActionsIntent.KEY_TITLE);
+            PendingIntent action = bundle.getParcelable(BrowserActionsIntent.KEY_ACTION);
+            @DrawableRes
+            int iconId = bundle.getInt(BrowserActionsIntent.KEY_ICON_ID);
+            if (TextUtils.isEmpty(title) || action == null) {
+                throw new IllegalArgumentException(
+                        "Custom item should contain a non-empty title and non-null intent.");
+            } else {
+                BrowserActionItem item = new BrowserActionItem(title, action, iconId);
+                mActions.add(item);
+            }
+        }
+        return mActions;
+    }
+
+    /**
+     * Get the package name of the creator application.
+     * @param intent The {@link BrowserActionsIntent}.
+     * @return The creator package name.
+     */
+    @SuppressWarnings("deprecation")
+    public static String getCreatorPackageName(Intent intent) {
+        PendingIntent pendingIntent = intent.getParcelableExtra(BrowserActionsIntent.EXTRA_APP_ID);
+        if (pendingIntent != null) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+                return pendingIntent.getCreatorPackage();
+            } else {
+                return pendingIntent.getTargetPackage();
+            }
+        }
+        return null;
+    }
+}
diff --git a/customtabs/tests/src/androidx/browser/browseractions/BrowserActionsIntentTest.java b/customtabs/tests/src/androidx/browser/browseractions/BrowserActionsIntentTest.java
new file mode 100644
index 0000000..49f3fda
--- /dev/null
+++ b/customtabs/tests/src/androidx/browser/browseractions/BrowserActionsIntentTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.browser.browseractions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+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.ArrayList;
+import java.util.List;
+
+/** Unit tests for {@link BrowserActionsIntent}. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class BrowserActionsIntentTest {
+    private static final String TEST_URL = "http://www.example.com";
+    private static final String CUSTOM_ITEM_TITLE = "Share url";
+    private Uri mUri = Uri.parse(TEST_URL);
+    private Context mContext = InstrumentationRegistry.getTargetContext();
+
+    /**
+     * Test whether default {@link BrowserActionsIntent} is populated correctly.
+     */
+    @Test
+    public void testDefaultBrowserActionsIntent() {
+        BrowserActionsIntent browserActionsIntent =
+                new BrowserActionsIntent.Builder(mContext, mUri).build();
+        Intent intent = browserActionsIntent.getIntent();
+        assertNotNull(intent);
+
+        assertEquals(BrowserActionsIntent.ACTION_BROWSER_ACTIONS_OPEN, intent.getAction());
+        assertEquals(mUri, intent.getData());
+        assertTrue(intent.hasExtra(BrowserActionsIntent.EXTRA_TYPE));
+        assertEquals(BrowserActionsIntent.URL_TYPE_NONE,
+                intent.getIntExtra(BrowserActionsIntent.EXTRA_TYPE, 0));
+        assertTrue(intent.hasExtra(BrowserActionsIntent.EXTRA_APP_ID));
+        assertEquals(mContext.getPackageName(), BrowserActionsIntent.getCreatorPackageName(intent));
+        assertFalse(intent.hasExtra(BrowserActionsIntent.EXTRA_SELECTED_ACTION_PENDING_INTENT));
+    }
+
+    @Test
+    /**
+     * Test whether custom items are set correctly.
+     */
+    public void testCustomItem() {
+        PendingIntent action1 = createCustomItemAction(TEST_URL);
+        BrowserActionItem customItemWithoutIcon = new BrowserActionItem(CUSTOM_ITEM_TITLE, action1);
+        PendingIntent action2 = createCustomItemAction(TEST_URL);
+        BrowserActionItem customItemWithIcon =
+                new BrowserActionItem(CUSTOM_ITEM_TITLE, action2, android.R.drawable.ic_menu_share);
+        ArrayList<BrowserActionItem> customItems = new ArrayList<>();
+        customItems.add(customItemWithIcon);
+        customItems.add(customItemWithoutIcon);
+
+        BrowserActionsIntent browserActionsIntent = new BrowserActionsIntent.Builder(mContext, mUri)
+                .setCustomItems(customItems)
+                .build();
+        Intent intent = browserActionsIntent.getIntent();
+        assertTrue(intent.hasExtra(BrowserActionsIntent.EXTRA_MENU_ITEMS));
+        ArrayList<Bundle> bundles =
+                intent.getParcelableArrayListExtra(BrowserActionsIntent.EXTRA_MENU_ITEMS);
+        assertNotNull(bundles);
+        List<BrowserActionItem> items = BrowserActionsIntent.parseBrowserActionItems(bundles);
+        assertEquals(2, items.size());
+        BrowserActionItem items1 = items.get(0);
+        assertEquals(CUSTOM_ITEM_TITLE, items1.getTitle());
+        assertEquals(android.R.drawable.ic_menu_share, items1.getIconId());
+        assertEquals(action1, items1.getAction());
+        BrowserActionItem items2 = items.get(1);
+        assertEquals(CUSTOM_ITEM_TITLE, items2.getTitle());
+        assertEquals(0, items2.getIconId());
+        assertEquals(action2, items2.getAction());
+    }
+
+    private PendingIntent createCustomItemAction(String url) {
+        Context context = InstrumentationRegistry.getTargetContext();
+        Intent customIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+        return PendingIntent.getActivity(context, 0, customIntent, 0);
+    }
+}
diff --git a/dynamic-animation/src/main/java/android/support/animation/AnimationHandler.java b/dynamic-animation/src/main/java/android/support/animation/AnimationHandler.java
index 6c39b23..24bc43a 100644
--- a/dynamic-animation/src/main/java/android/support/animation/AnimationHandler.java
+++ b/dynamic-animation/src/main/java/android/support/animation/AnimationHandler.java
@@ -35,8 +35,6 @@
  * The handler uses the Choreographer by default for doing periodic callbacks. A custom
  * AnimationFrameCallbackProvider can be set on the handler to provide timing pulse that
  * may be independent of UI frame update. This could be useful in testing.
- *
- * @hide
  */
 class AnimationHandler {
     /**
@@ -57,7 +55,7 @@
      * the new frame, so that they can update animation values as needed.
      */
     class AnimationCallbackDispatcher {
-        public void dispatchAnimationFrame() {
+        void dispatchAnimationFrame() {
             mCurrentFrameTime = SystemClock.uptimeMillis();
             AnimationHandler.this.doAnimationFrame(mCurrentFrameTime);
             if (mAnimationCallbacks.size() > 0) {
@@ -72,7 +70,6 @@
     /**
      * Internal per-thread collections used to avoid set collisions as animations start and end
      * while being processed.
-     * @hide
      */
     private final SimpleArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime =
             new SimpleArrayMap<>();
@@ -249,7 +246,7 @@
      * timing pulse without using Choreographer. That way we could use any arbitrary interval for
      * our timing pulse in the tests.
      */
-    public abstract static class AnimationFrameCallbackProvider {
+    abstract static class AnimationFrameCallbackProvider {
         final AnimationCallbackDispatcher mDispatcher;
         AnimationFrameCallbackProvider(AnimationCallbackDispatcher dispatcher) {
             mDispatcher = dispatcher;
diff --git a/dynamic-animation/src/main/java/android/support/animation/DynamicAnimation.java b/dynamic-animation/src/main/java/android/support/animation/DynamicAnimation.java
index 8ea48b9..7cbd5bb 100644
--- a/dynamic-animation/src/main/java/android/support/animation/DynamicAnimation.java
+++ b/dynamic-animation/src/main/java/android/support/animation/DynamicAnimation.java
@@ -18,6 +18,7 @@
 
 import android.os.Looper;
 import android.support.annotation.FloatRange;
+import android.support.annotation.RestrictTo;
 import android.support.v4.view.ViewCompat;
 import android.util.AndroidRuntimeException;
 import android.view.View;
@@ -631,6 +632,7 @@
      *
      * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     @Override
     public boolean doAnimationFrame(long frameTime) {
         if (mLastFrameTime == 0) {
diff --git a/dynamic-animation/src/main/java/android/support/animation/SpringForce.java b/dynamic-animation/src/main/java/android/support/animation/SpringForce.java
index 5f95aa8..dfb4c67 100644
--- a/dynamic-animation/src/main/java/android/support/animation/SpringForce.java
+++ b/dynamic-animation/src/main/java/android/support/animation/SpringForce.java
@@ -17,6 +17,7 @@
 package android.support.animation;
 
 import android.support.annotation.FloatRange;
+import android.support.annotation.RestrictTo;
 
 /**
  * Spring Force defines the characteristics of the spring being used in the animation.
@@ -210,6 +211,7 @@
     /**
      * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     @Override
     public float getAcceleration(float lastDisplacement, float lastVelocity) {
 
@@ -224,6 +226,7 @@
     /**
      * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     @Override
     public boolean isAtEquilibrium(float value, float velocity) {
         if (Math.abs(velocity) < mVelocityThreshold
diff --git a/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiButton.java b/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiButton.java
index 752e052..2999ec8 100644
--- a/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiButton.java
+++ b/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiButton.java
@@ -18,8 +18,10 @@
 import android.content.Context;
 import android.os.Build;
 import android.support.annotation.RequiresApi;
+import android.support.v4.widget.TextViewCompat;
 import android.text.InputFilter;
 import android.util.AttributeSet;
+import android.view.ActionMode;
 import android.widget.Button;
 
 /**
@@ -80,4 +82,13 @@
         }
         return mEmojiTextViewHelper;
     }
+
+    /**
+     * See
+     * {@link TextViewCompat#setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)}
+     */
+    @Override
+    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
+        TextViewCompat.setCustomSelectionActionModeCallback(this, actionModeCallback);
+    }
 }
diff --git a/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiEditText.java b/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiEditText.java
index e1057e8..9457f59 100644
--- a/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiEditText.java
+++ b/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiEditText.java
@@ -21,8 +21,10 @@
 import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
 import android.support.text.emoji.EmojiCompat;
+import android.support.v4.widget.TextViewCompat;
 import android.text.method.KeyListener;
 import android.util.AttributeSet;
+import android.view.ActionMode;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.widget.EditText;
@@ -121,4 +123,13 @@
         }
         return mEmojiEditTextHelper;
     }
+
+    /**
+     * See
+     * {@link TextViewCompat#setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)}
+     */
+    @Override
+    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
+        TextViewCompat.setCustomSelectionActionModeCallback(this, actionModeCallback);
+    }
 }
diff --git a/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiTextView.java b/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiTextView.java
index 3e450dc..2f09f65 100644
--- a/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiTextView.java
+++ b/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiTextView.java
@@ -18,8 +18,10 @@
 import android.content.Context;
 import android.os.Build;
 import android.support.annotation.RequiresApi;
+import android.support.v4.widget.TextViewCompat;
 import android.text.InputFilter;
 import android.util.AttributeSet;
+import android.view.ActionMode;
 import android.widget.TextView;
 
 /**
@@ -80,4 +82,13 @@
         }
         return mEmojiTextViewHelper;
     }
+
+    /**
+     * See
+     * {@link TextViewCompat#setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)}
+     */
+    @Override
+    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
+        TextViewCompat.setCustomSelectionActionModeCallback(this, actionModeCallback);
+    }
 }
diff --git a/emoji/core/src/main/java/android/support/text/emoji/widget/ExtractButtonCompat.java b/emoji/core/src/main/java/android/support/text/emoji/widget/ExtractButtonCompat.java
index fc8eb78..55be022 100644
--- a/emoji/core/src/main/java/android/support/text/emoji/widget/ExtractButtonCompat.java
+++ b/emoji/core/src/main/java/android/support/text/emoji/widget/ExtractButtonCompat.java
@@ -22,7 +22,9 @@
 import android.os.Build;
 import android.support.annotation.RequiresApi;
 import android.support.annotation.RestrictTo;
+import android.support.v4.widget.TextViewCompat;
 import android.util.AttributeSet;
+import android.view.ActionMode;
 import android.widget.Button;
 
 /**
@@ -58,4 +60,13 @@
     public boolean hasWindowFocus() {
         return isEnabled() && getVisibility() == VISIBLE ? true : false;
     }
+
+    /**
+     * See
+     * {@link TextViewCompat#setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)}
+     */
+    @Override
+    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
+        TextViewCompat.setCustomSelectionActionModeCallback(this, actionModeCallback);
+    }
 }
diff --git a/exifinterface/src/main/java/android/support/media/ExifInterface.java b/exifinterface/src/main/java/android/support/media/ExifInterface.java
index eea69ab..6b437a6 100644
--- a/exifinterface/src/main/java/android/support/media/ExifInterface.java
+++ b/exifinterface/src/main/java/android/support/media/ExifInterface.java
@@ -23,6 +23,7 @@
 import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
 import android.util.Log;
 import android.util.Pair;
 
@@ -3550,6 +3551,7 @@
 
     // Indices of Exif Ifd tag groups
     /** @hide */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({IFD_TYPE_PRIMARY, IFD_TYPE_EXIF, IFD_TYPE_GPS, IFD_TYPE_INTEROPERABILITY,
             IFD_TYPE_THUMBNAIL, IFD_TYPE_PREVIEW, IFD_TYPE_ORF_MAKER_NOTE,
@@ -4567,6 +4569,7 @@
      * @param timeStamp number of milliseconds since Jan. 1, 1970, midnight local time.
      * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public void setDateTime(long timeStamp) {
         long sub = timeStamp % 1000;
         setAttribute(TAG_DATETIME, sFormatter.format(new Date(timeStamp)));
@@ -4578,6 +4581,7 @@
      * Returns -1 if the date time information if not available.
      * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public long getDateTime() {
         String dateTimeString = getAttribute(TAG_DATETIME);
         if (dateTimeString == null
@@ -4614,6 +4618,7 @@
      * Returns -1 if the date time information if not available.
      * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public long getGpsDateTime() {
         String date = getAttribute(TAG_GPS_DATESTAMP);
         String time = getAttribute(TAG_GPS_TIMESTAMP);
diff --git a/leanback/common/android/support/v17/leanback/transition/TransitionEpicenterCallback.java b/leanback/common/android/support/v17/leanback/transition/TransitionEpicenterCallback.java
index ec7f84c..bb8e686 100644
--- a/leanback/common/android/support/v17/leanback/transition/TransitionEpicenterCallback.java
+++ b/leanback/common/android/support/v17/leanback/transition/TransitionEpicenterCallback.java
@@ -14,11 +14,13 @@
 package android.support.v17.leanback.transition;
 
 import android.graphics.Rect;
+import android.support.annotation.RestrictTo;
 
 /**
  * Class to get the epicenter of Transition.
  * @hide
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 public abstract class TransitionEpicenterCallback {
 
     /**
@@ -31,4 +33,3 @@
      */
     public abstract Rect onGetEpicenter(Object transition);
 }
-
diff --git a/leanback/src/android/support/v17/leanback/app/PlaybackFragment.java b/leanback/src/android/support/v17/leanback/app/PlaybackFragment.java
index e2e6be4..dc59e0e 100644
--- a/leanback/src/android/support/v17/leanback/app/PlaybackFragment.java
+++ b/leanback/src/android/support/v17/leanback/app/PlaybackFragment.java
@@ -29,6 +29,7 @@
 import android.os.Message;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.animation.LogAccelerateInterpolator;
 import android.support.v17.leanback.animation.LogDecelerateInterpolator;
@@ -106,6 +107,7 @@
      * Resets the focus on the button in the middle of control row.
      * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public void resetFocus() {
         ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView()
                 .findViewHolderForAdapterPosition(0);
@@ -185,6 +187,7 @@
      * @hide
      * @deprecated use {@link PlaybackSupportFragment}
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     @Deprecated
     public static class OnFadeCompleteListener {
         public void onFadeInComplete() {
@@ -366,6 +369,7 @@
      * Sets the listener to be called when fade in or out has completed.
      * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public void setFadeCompleteListener(OnFadeCompleteListener listener) {
         mFadeCompleteListener = listener;
     }
@@ -374,6 +378,7 @@
      * Returns the listener to be called when fade in or out has completed.
      * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public OnFadeCompleteListener getFadeCompleteListener() {
         return mFadeCompleteListener;
     }
diff --git a/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragment.java b/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragment.java
index a8741ab..ee17e84 100644
--- a/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragment.java
+++ b/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragment.java
@@ -26,6 +26,7 @@
 import android.os.Message;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.animation.LogAccelerateInterpolator;
 import android.support.v17.leanback.animation.LogDecelerateInterpolator;
@@ -101,6 +102,7 @@
      * Resets the focus on the button in the middle of control row.
      * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public void resetFocus() {
         ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView()
                 .findViewHolderForAdapterPosition(0);
@@ -179,6 +181,7 @@
      * completion events.
      * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public static class OnFadeCompleteListener {
         public void onFadeInComplete() {
         }
@@ -359,6 +362,7 @@
      * Sets the listener to be called when fade in or out has completed.
      * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public void setFadeCompleteListener(OnFadeCompleteListener listener) {
         mFadeCompleteListener = listener;
     }
@@ -367,6 +371,7 @@
      * Returns the listener to be called when fade in or out has completed.
      * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public OnFadeCompleteListener getFadeCompleteListener() {
         return mFadeCompleteListener;
     }
diff --git a/leanback/src/android/support/v17/leanback/media/PlaybackControlGlue.java b/leanback/src/android/support/v17/leanback/media/PlaybackControlGlue.java
index 5bf6cc1..0a788f6 100644
--- a/leanback/src/android/support/v17/leanback/media/PlaybackControlGlue.java
+++ b/leanback/src/android/support/v17/leanback/media/PlaybackControlGlue.java
@@ -20,6 +20,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Message;
+import android.support.annotation.RestrictTo;
 import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
 import android.support.v17.leanback.widget.Action;
 import android.support.v17.leanback.widget.ArrayObjectAdapter;
@@ -356,6 +357,7 @@
     /**
      * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
             PresenterSelector presenterSelector) {
         SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector);
diff --git a/leanback/src/android/support/v17/leanback/util/MathUtil.java b/leanback/src/android/support/v17/leanback/util/MathUtil.java
index 487188d..bf74e40 100644
--- a/leanback/src/android/support/v17/leanback/util/MathUtil.java
+++ b/leanback/src/android/support/v17/leanback/util/MathUtil.java
@@ -13,10 +13,13 @@
  */
 package android.support.v17.leanback.util;
 
+import android.support.annotation.RestrictTo;
+
 /**
  * Math Utilities for leanback library.
  * @hide
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 public final class MathUtil {
 
     private MathUtil() {
diff --git a/leanback/src/android/support/v17/leanback/widget/DetailsParallaxDrawable.java b/leanback/src/android/support/v17/leanback/widget/DetailsParallaxDrawable.java
index 37e3480..1eea797 100644
--- a/leanback/src/android/support/v17/leanback/widget/DetailsParallaxDrawable.java
+++ b/leanback/src/android/support/v17/leanback/widget/DetailsParallaxDrawable.java
@@ -22,6 +22,7 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.support.annotation.ColorInt;
+import android.support.annotation.RestrictTo;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.graphics.CompositeDrawable;
 import android.support.v17.leanback.graphics.FitWidthBitmapDrawable;
@@ -56,6 +57,7 @@
  * </li>
  * @hide
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 public class DetailsParallaxDrawable extends CompositeDrawable {
     private Drawable mBottomDrawable;
 
diff --git a/leanback/src/android/support/v17/leanback/widget/GuidedActionEditText.java b/leanback/src/android/support/v17/leanback/widget/GuidedActionEditText.java
index f6a0eab..256e4f0 100644
--- a/leanback/src/android/support/v17/leanback/widget/GuidedActionEditText.java
+++ b/leanback/src/android/support/v17/leanback/widget/GuidedActionEditText.java
@@ -19,7 +19,9 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.support.v4.widget.TextViewCompat;
 import android.util.AttributeSet;
+import android.view.ActionMode;
 import android.view.KeyEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.EditText;
@@ -115,4 +117,13 @@
             setFocusable(false);
         }
     }
+
+    /**
+     * See
+     * {@link TextViewCompat#setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)}
+     */
+    @Override
+    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
+        TextViewCompat.setCustomSelectionActionModeCallback(this, actionModeCallback);
+    }
 }
diff --git a/leanback/src/android/support/v17/leanback/widget/MediaRowFocusView.java b/leanback/src/android/support/v17/leanback/widget/MediaRowFocusView.java
index 1418a2a..471f64e 100644
--- a/leanback/src/android/support/v17/leanback/widget/MediaRowFocusView.java
+++ b/leanback/src/android/support/v17/leanback/widget/MediaRowFocusView.java
@@ -17,14 +17,16 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.RectF;
+import android.support.annotation.RestrictTo;
+import android.support.v17.leanback.R;
 import android.util.AttributeSet;
 import android.view.View;
-import android.support.v17.leanback.R;
 
 /**
  * Creates a view for a media item row in a playlist
  * @hide
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 class MediaRowFocusView extends View {
 
     private final Paint mPaint;
diff --git a/leanback/src/android/support/v17/leanback/widget/ParallaxEffect.java b/leanback/src/android/support/v17/leanback/widget/ParallaxEffect.java
index 5c06e29..e1af762 100644
--- a/leanback/src/android/support/v17/leanback/widget/ParallaxEffect.java
+++ b/leanback/src/android/support/v17/leanback/widget/ParallaxEffect.java
@@ -17,6 +17,7 @@
 package android.support.v17.leanback.widget;
 
 import android.animation.PropertyValuesHolder;
+import android.support.annotation.RestrictTo;
 import android.support.v17.leanback.widget.Parallax.FloatProperty;
 import android.support.v17.leanback.widget.Parallax.FloatPropertyMarkerValue;
 import android.support.v17.leanback.widget.Parallax.IntProperty;
@@ -70,6 +71,7 @@
      * @return A list of Float objects that represents weight associated with each variable range.
      * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public final List<Float> getWeights() {
         return mWeights;
     }
@@ -96,6 +98,7 @@
      *                range.
      * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public final void setWeights(float... weights) {
         for (float weight : weights) {
             if (weight <= 0) {
@@ -121,6 +124,7 @@
      * @return This ParallaxEffect object, allowing calls to methods in this class to be chained.
      * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public final ParallaxEffect weights(float... weights) {
         setWeights(weights);
         return this;
diff --git a/leanback/src/android/support/v17/leanback/widget/ResizingTextView.java b/leanback/src/android/support/v17/leanback/widget/ResizingTextView.java
index 5888552..cc8fb55 100644
--- a/leanback/src/android/support/v17/leanback/widget/ResizingTextView.java
+++ b/leanback/src/android/support/v17/leanback/widget/ResizingTextView.java
@@ -16,9 +16,11 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.support.v17.leanback.R;
+import android.support.v4.widget.TextViewCompat;
 import android.text.Layout;
 import android.util.AttributeSet;
 import android.util.TypedValue;
+import android.view.ActionMode;
 import android.widget.TextView;
 
 /**
@@ -270,4 +272,13 @@
             setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), paddingBottom);
         }
     }
+
+    /**
+     * See
+     * {@link TextViewCompat#setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)}
+     */
+    @Override
+    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
+        TextViewCompat.setCustomSelectionActionModeCallback(this, actionModeCallback);
+    }
 }
diff --git a/leanback/src/android/support/v17/leanback/widget/RowHeaderView.java b/leanback/src/android/support/v17/leanback/widget/RowHeaderView.java
index 0a8f98e..70b9908 100644
--- a/leanback/src/android/support/v17/leanback/widget/RowHeaderView.java
+++ b/leanback/src/android/support/v17/leanback/widget/RowHeaderView.java
@@ -13,9 +13,11 @@
  */
 package android.support.v17.leanback.widget;
 
-import android.support.v17.leanback.R;
 import android.content.Context;
+import android.support.v17.leanback.R;
+import android.support.v4.widget.TextViewCompat;
 import android.util.AttributeSet;
+import android.view.ActionMode;
 import android.widget.TextView;
 
 /**
@@ -35,4 +37,12 @@
         super(context, attrs, defStyle);
     }
 
+    /**
+     * See
+     * {@link TextViewCompat#setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)}
+     */
+    @Override
+    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
+        TextViewCompat.setCustomSelectionActionModeCallback(this, actionModeCallback);
+    }
 }
diff --git a/leanback/src/android/support/v17/leanback/widget/StreamingTextView.java b/leanback/src/android/support/v17/leanback/widget/StreamingTextView.java
index 0b8781c..d64d78e 100644
--- a/leanback/src/android/support/v17/leanback/widget/StreamingTextView.java
+++ b/leanback/src/android/support/v17/leanback/widget/StreamingTextView.java
@@ -20,6 +20,7 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.support.v17.leanback.R;
+import android.support.v4.widget.TextViewCompat;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.SpannedString;
@@ -28,6 +29,7 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Property;
+import android.view.ActionMode;
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.EditText;
@@ -290,4 +292,14 @@
     }
 
     public void updateRecognizedText(String stableText, List<Float> rmsValues) {}
+
+    /**
+     * See
+     * {@link android.support.v4.widget.TextViewCompat
+     * #setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)}
+     */
+    @Override
+    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
+        TextViewCompat.setCustomSelectionActionModeCallback(this, actionModeCallback);
+    }
 }
diff --git a/leanback/src/android/support/v17/leanback/widget/VideoSurfaceView.java b/leanback/src/android/support/v17/leanback/widget/VideoSurfaceView.java
index 29d778c..d42a60d 100644
--- a/leanback/src/android/support/v17/leanback/widget/VideoSurfaceView.java
+++ b/leanback/src/android/support/v17/leanback/widget/VideoSurfaceView.java
@@ -17,6 +17,7 @@
 package android.support.v17.leanback.widget;
 
 import android.content.Context;
+import android.support.annotation.RestrictTo;
 import android.util.AttributeSet;
 import android.view.SurfaceView;
 
@@ -26,6 +27,7 @@
  * This class disables setTransitionVisibility() to avoid the problem.
  * @hide
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 public class VideoSurfaceView extends SurfaceView {
 
     public VideoSurfaceView(Context context) {
diff --git a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestActivity.java b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestActivity.java
index dd17fd3..da7add8 100644
--- a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestActivity.java
+++ b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestActivity.java
@@ -17,13 +17,10 @@
 
 package android.support.v17.leanback.app;
 
+import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
-import android.app.Activity;
 
-/**
- * @hide from javadoc
- */
 public class GuidedStepFragmentTestActivity extends Activity {
 
     /**
diff --git a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestBase.java b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestBase.java
index 34ec694..305be96 100644
--- a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestBase.java
+++ b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestBase.java
@@ -34,9 +34,6 @@
 import org.junit.Rule;
 import org.junit.rules.TestName;
 
-/**
- * @hide from javadoc
- */
 public class GuidedStepFragmentTestBase {
 
     private static final long TIMEOUT = 5000;
diff --git a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestActivity.java b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestActivity.java
index bac2f49..cfeb386 100644
--- a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestActivity.java
+++ b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestActivity.java
@@ -18,9 +18,6 @@
 import android.os.Bundle;
 import android.support.v4.app.FragmentActivity;
 
-/**
- * @hide from javadoc
- */
 public class GuidedStepSupportFragmentTestActivity extends FragmentActivity {
 
     /**
diff --git a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestBase.java b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestBase.java
index 12e4d09..adecf44 100644
--- a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestBase.java
+++ b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestBase.java
@@ -31,9 +31,6 @@
 import org.junit.Rule;
 import org.junit.rules.TestName;
 
-/**
- * @hide from javadoc
- */
 public class GuidedStepSupportFragmentTestBase {
 
     private static final long TIMEOUT = 5000;
diff --git a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepTestFragment.java b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepTestFragment.java
index 73e4083..555c9b0 100644
--- a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepTestFragment.java
+++ b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepTestFragment.java
@@ -29,9 +29,6 @@
 import java.util.HashMap;
 import java.util.List;
 
-/**
- * @hide from javadoc
- */
 public class GuidedStepTestFragment extends GuidedStepFragment {
 
     private static final String KEY_TEST_NAME = "key_test_name";
diff --git a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepTestSupportFragment.java b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepTestSupportFragment.java
index 95491ce..b356420 100644
--- a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepTestSupportFragment.java
+++ b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepTestSupportFragment.java
@@ -26,9 +26,6 @@
 import java.util.HashMap;
 import java.util.List;
 
-/**
- * @hide from javadoc
- */
 public class GuidedStepTestSupportFragment extends GuidedStepSupportFragment {
 
     private static final String KEY_TEST_NAME = "key_test_name";
diff --git a/leanback/tests/java/android/support/v17/leanback/media/PlaybackControlGlueTest.java b/leanback/tests/java/android/support/v17/leanback/media/PlaybackControlGlueTest.java
index d6ec18e..6a3b212 100644
--- a/leanback/tests/java/android/support/v17/leanback/media/PlaybackControlGlueTest.java
+++ b/leanback/tests/java/android/support/v17/leanback/media/PlaybackControlGlueTest.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
 import android.support.v17.leanback.widget.PlaybackControlsRow;
 import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
 import android.support.v17.leanback.widget.PlaybackRowPresenter;
@@ -33,6 +34,7 @@
 import org.junit.Test;
 import org.mockito.Mockito;
 
+@LargeTest
 public class PlaybackControlGlueTest {
 
     public static class PlaybackControlGlueImpl extends PlaybackControlGlue {
diff --git a/lifecycle/common/src/main/java/android/arch/lifecycle/GenericLifecycleObserver.java b/lifecycle/common/src/main/java/android/arch/lifecycle/GenericLifecycleObserver.java
index 59f09c4..4601478 100644
--- a/lifecycle/common/src/main/java/android/arch/lifecycle/GenericLifecycleObserver.java
+++ b/lifecycle/common/src/main/java/android/arch/lifecycle/GenericLifecycleObserver.java
@@ -16,10 +16,13 @@
 
 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 {
     /**
diff --git a/lifecycle/extensions/api/1.0.0.ignore b/lifecycle/extensions/api/1.0.0.ignore
index 8d6e57b..8d56990 100644
--- a/lifecycle/extensions/api/1.0.0.ignore
+++ b/lifecycle/extensions/api/1.0.0.ignore
@@ -8,4 +8,6 @@
 e8aa0bd
 5900545
 aadc61e
-f39f879
\ No newline at end of file
+f39f879
+1b622fa
+fa6b788
\ No newline at end of file
diff --git a/lifecycle/extensions/api/current.txt b/lifecycle/extensions/api/current.txt
index bf093a9..8bd7d59 100644
--- a/lifecycle/extensions/api/current.txt
+++ b/lifecycle/extensions/api/current.txt
@@ -1,18 +1,5 @@
 package android.arch.lifecycle {
 
-  public class AndroidViewModel extends android.arch.lifecycle.ViewModel {
-    ctor public AndroidViewModel(android.app.Application);
-    method public <T extends android.app.Application> T getApplication();
-  }
-
-  public deprecated class LifecycleActivity extends android.support.v4.app.FragmentActivity {
-    ctor public LifecycleActivity();
-  }
-
-  public deprecated class LifecycleFragment extends android.support.v4.app.Fragment {
-    ctor public LifecycleFragment();
-  }
-
   public class LifecycleService extends android.app.Service implements android.arch.lifecycle.LifecycleOwner {
     ctor public LifecycleService();
     method public android.arch.lifecycle.Lifecycle getLifecycle();
@@ -35,15 +22,15 @@
   }
 
   public class ViewModelProviders {
-    ctor public 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);
   }
 
-  public static class ViewModelProviders.DefaultFactory extends android.arch.lifecycle.ViewModelProvider.NewInstanceFactory {
-    ctor public ViewModelProviders.DefaultFactory(android.app.Application);
+  public static deprecated class ViewModelProviders.DefaultFactory extends android.arch.lifecycle.ViewModelProvider.AndroidViewModelFactory {
+    ctor public deprecated ViewModelProviders.DefaultFactory(android.app.Application);
   }
 
   public class ViewModelStores {
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleActivity.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleActivity.java
deleted file mode 100644
index 26bd508..0000000
--- a/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleActivity.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.lifecycle;
-
-import android.support.v4.app.FragmentActivity;
-
-/**
- * @deprecated Use {@code android.support.v7.app.AppCompatActivity} instead of this class.
- */
-@Deprecated
-public class LifecycleActivity extends FragmentActivity {
-}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleFragment.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleFragment.java
deleted file mode 100644
index c0da66b..0000000
--- a/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleFragment.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.lifecycle;
-
-import android.support.v4.app.Fragment;
-
-/**
- * @deprecated Use {@link Fragment} instead of it.
- */
-@Deprecated
-public class LifecycleFragment extends Fragment {
-}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelProviders.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelProviders.java
index b4b20aa..ab6bce6 100644
--- a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelProviders.java
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelProviders.java
@@ -16,7 +16,6 @@
 
 package android.arch.lifecycle;
 
-import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.Application;
 import android.arch.lifecycle.ViewModelProvider.Factory;
@@ -25,20 +24,16 @@
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentActivity;
 
-import java.lang.reflect.InvocationTargetException;
-
 /**
  * Utilities methods for {@link ViewModelStore} class.
  */
 public class ViewModelProviders {
 
-    @SuppressLint("StaticFieldLeak")
-    private static DefaultFactory sDefaultFactory;
-
-    private static void initializeFactoryIfNeeded(Application application) {
-        if (sDefaultFactory == null) {
-            sDefaultFactory = new DefaultFactory(application);
-        }
+    /**
+     * @deprecated This class should not be directly instantiated
+     */
+    @Deprecated
+    public ViewModelProviders() {
     }
 
     private static Application checkApplication(Activity activity) {
@@ -62,30 +57,34 @@
      * 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 DefaultFactory} to instantiate new ViewModels.
+     * It uses {@link ViewModelProvider.AndroidViewModelFactory} to instantiate new ViewModels.
      *
      * @param fragment a fragment, in whose scope ViewModels should be retained
      * @return a ViewModelProvider instance
      */
     @MainThread
     public static ViewModelProvider of(@NonNull Fragment fragment) {
-        initializeFactoryIfNeeded(checkApplication(checkActivity(fragment)));
-        return new ViewModelProvider(ViewModelStores.of(fragment), sDefaultFactory);
+        ViewModelProvider.AndroidViewModelFactory factory =
+                ViewModelProvider.AndroidViewModelFactory.getInstance(
+                        checkApplication(checkActivity(fragment)));
+        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 {@link DefaultFactory} to instantiate new ViewModels.
+     * It uses {@link ViewModelProvider.AndroidViewModelFactory} to instantiate new ViewModels.
      *
      * @param activity an activity, in whose scope ViewModels should be retained
      * @return a ViewModelProvider instance
      */
     @MainThread
     public static ViewModelProvider of(@NonNull FragmentActivity activity) {
-        initializeFactoryIfNeeded(checkApplication(activity));
-        return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);
+        ViewModelProvider.AndroidViewModelFactory factory =
+                ViewModelProvider.AndroidViewModelFactory.getInstance(
+                        checkApplication(activity));
+        return new ViewModelProvider(ViewModelStores.of(activity), factory);
     }
 
     /**
@@ -124,39 +123,22 @@
     /**
      * {@link Factory} which may create {@link AndroidViewModel} and
      * {@link ViewModel}, which have an empty constructor.
+     *
+     * @deprecated Use {@link ViewModelProvider.AndroidViewModelFactory}
      */
     @SuppressWarnings("WeakerAccess")
-    public static class DefaultFactory extends ViewModelProvider.NewInstanceFactory {
-
-        private Application mApplication;
-
+    @Deprecated
+    public static class DefaultFactory extends ViewModelProvider.AndroidViewModelFactory {
         /**
-         * Creates a {@code DefaultFactory}
+         * 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) {
-            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);
+            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
index d7d769d..e79c934 100644
--- a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelStores.java
+++ b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelStores.java
@@ -40,6 +40,9 @@
      */
     @MainThread
     public static ViewModelStore of(@NonNull FragmentActivity activity) {
+        if (activity instanceof ViewModelStoreOwner) {
+            return ((ViewModelStoreOwner) activity).getViewModelStore();
+        }
         return holderFragmentFor(activity).getViewModelStore();
     }
 
@@ -51,6 +54,9 @@
      */
     @MainThread
     public static ViewModelStore of(@NonNull Fragment fragment) {
+        if (fragment instanceof ViewModelStoreOwner) {
+            return ((ViewModelStoreOwner) fragment).getViewModelStore();
+        }
         return holderFragmentFor(fragment).getViewModelStore();
     }
 }
diff --git a/lifecycle/livedata/src/main/java/android/arch/lifecycle/LiveData.java b/lifecycle/livedata/src/main/java/android/arch/lifecycle/LiveData.java
index 5b09c32..3a753a1 100644
--- a/lifecycle/livedata/src/main/java/android/arch/lifecycle/LiveData.java
+++ b/lifecycle/livedata/src/main/java/android/arch/lifecycle/LiveData.java
@@ -21,7 +21,6 @@
 
 import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.internal.SafeIterableMap;
-import android.arch.lifecycle.Lifecycle.State;
 import android.support.annotation.MainThread;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
@@ -57,33 +56,12 @@
  * @param <T> The type of data held by this instance
  * @see ViewModel
  */
-@SuppressWarnings({"WeakerAccess", "unused"})
-// TODO: Thread checks are too strict right now, we may consider automatically moving them to main
-// thread.
 public abstract class LiveData<T> {
     private final Object mDataLock = new Object();
     static final int START_VERSION = -1;
     private static final Object NOT_SET = new Object();
 
-    private static final LifecycleOwner ALWAYS_ON = new LifecycleOwner() {
-
-        private LifecycleRegistry mRegistry = init();
-
-        private LifecycleRegistry init() {
-            LifecycleRegistry registry = new LifecycleRegistry(this);
-            registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
-            registry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-            registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
-            return registry;
-        }
-
-        @Override
-        public Lifecycle getLifecycle() {
-            return mRegistry;
-        }
-    };
-
-    private SafeIterableMap<Observer<T>, LifecycleBoundObserver> mObservers =
+    private SafeIterableMap<Observer<T>, ObserverWrapper> mObservers =
             new SafeIterableMap<>();
 
     // how many observers are in active state
@@ -110,8 +88,8 @@
         }
     };
 
-    private void considerNotify(LifecycleBoundObserver observer) {
-        if (!observer.active) {
+    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.
@@ -119,19 +97,19 @@
         // we still first check observer.active to keep it as the entrance for events. So even if
         // the observer moved to an active state, if we've not received that event, we better not
         // notify for a more predictable notification order.
-        if (!isActiveState(observer.owner.getLifecycle().getCurrentState())) {
+        if (!observer.shouldBeActive()) {
             observer.activeStateChanged(false);
             return;
         }
-        if (observer.lastVersion >= mVersion) {
+        if (observer.mLastVersion >= mVersion) {
             return;
         }
-        observer.lastVersion = mVersion;
+        observer.mLastVersion = mVersion;
         //noinspection unchecked
-        observer.observer.onChanged((T) mData);
+        observer.mObserver.onChanged((T) mData);
     }
 
-    private void dispatchingValue(@Nullable LifecycleBoundObserver initiator) {
+    private void dispatchingValue(@Nullable ObserverWrapper initiator) {
         if (mDispatchingValue) {
             mDispatchInvalidated = true;
             return;
@@ -143,7 +121,7 @@
                 considerNotify(initiator);
                 initiator = null;
             } else {
-                for (Iterator<Map.Entry<Observer<T>, LifecycleBoundObserver>> iterator =
+                for (Iterator<Map.Entry<Observer<T>, ObserverWrapper>> iterator =
                         mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                     considerNotify(iterator.next().getValue());
                     if (mDispatchInvalidated) {
@@ -190,8 +168,8 @@
             return;
         }
         LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
-        LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper);
-        if (existing != null && existing.owner != wrapper.owner) {
+        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
+        if (existing != null && !existing.isAttachedTo(owner)) {
             throw new IllegalArgumentException("Cannot add the same observer"
                     + " with different lifecycles");
         }
@@ -217,7 +195,16 @@
      */
     @MainThread
     public void observeForever(@NonNull Observer<T> observer) {
-        observe(ALWAYS_ON, observer);
+        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);
     }
 
     /**
@@ -228,11 +215,11 @@
     @MainThread
     public void removeObserver(@NonNull final Observer<T> observer) {
         assertMainThread("removeObserver");
-        LifecycleBoundObserver removed = mObservers.remove(observer);
+        ObserverWrapper removed = mObservers.remove(observer);
         if (removed == null) {
             return;
         }
-        removed.owner.getLifecycle().removeObserver(removed);
+        removed.detachObserver();
         removed.activeStateChanged(false);
     }
 
@@ -241,11 +228,12 @@
      *
      * @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<T>, LifecycleBoundObserver> entry : mObservers) {
-            if (entry.getValue().owner == owner) {
+        for (Map.Entry<Observer<T>, ObserverWrapper> entry : mObservers) {
+            if (entry.getValue().isAttachedTo(owner)) {
                 removeObserver(entry.getKey());
             }
         }
@@ -343,6 +331,7 @@
      *
      * @return true if this LiveData has observers
      */
+    @SuppressWarnings("WeakerAccess")
     public boolean hasObservers() {
         return mObservers.size() > 0;
     }
@@ -352,56 +341,96 @@
      *
      * @return true if this LiveData has active observers
      */
+    @SuppressWarnings("WeakerAccess")
     public boolean hasActiveObservers() {
         return mActiveCount > 0;
     }
 
-    class LifecycleBoundObserver implements GenericLifecycleObserver {
-        public final LifecycleOwner owner;
-        public final Observer<T> observer;
-        public boolean active;
-        public int lastVersion = START_VERSION;
+    class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
+        @NonNull final LifecycleOwner mOwner;
 
-        LifecycleBoundObserver(LifecycleOwner owner, Observer<T> observer) {
-            this.owner = owner;
-            this.observer = observer;
+        LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<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 (owner.getLifecycle().getCurrentState() == DESTROYED) {
-                removeObserver(observer);
+            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<T> mObserver;
+        boolean mActive;
+        int mLastVersion = START_VERSION;
+
+        ObserverWrapper(Observer<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
-            activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState()));
-        }
-
-        void activeStateChanged(boolean newActive) {
-            if (newActive == active) {
-                return;
-            }
-            active = newActive;
+            mActive = newActive;
             boolean wasInactive = LiveData.this.mActiveCount == 0;
-            LiveData.this.mActiveCount += active ? 1 : -1;
-            if (wasInactive && active) {
+            LiveData.this.mActiveCount += mActive ? 1 : -1;
+            if (wasInactive && mActive) {
                 onActive();
             }
-            if (LiveData.this.mActiveCount == 0 && !active) {
+            if (LiveData.this.mActiveCount == 0 && !mActive) {
                 onInactive();
             }
-            if (active) {
+            if (mActive) {
                 dispatchingValue(this);
             }
         }
     }
 
-    static boolean isActiveState(State state) {
-        return state.isAtLeast(STARTED);
+    private class AlwaysActiveObserver extends ObserverWrapper {
+
+        AlwaysActiveObserver(Observer<T> observer) {
+            super(observer);
+        }
+
+        @Override
+        boolean shouldBeActive() {
+            return true;
+        }
     }
 
-    private void assertMainThread(String methodName) {
+    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/src/test/java/android/arch/lifecycle/LiveDataTest.java b/lifecycle/livedata/src/test/java/android/arch/lifecycle/LiveDataTest.java
index c1dc54d..dafa46d 100644
--- a/lifecycle/livedata/src/test/java/android/arch/lifecycle/LiveDataTest.java
+++ b/lifecycle/livedata/src/test/java/android/arch/lifecycle/LiveDataTest.java
@@ -30,6 +30,7 @@
 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;
@@ -779,6 +780,28 @@
         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);
diff --git a/lifecycle/livedata/src/test/java/android/arch/lifecycle/TransformationsTest.java b/lifecycle/livedata/src/test/java/android/arch/lifecycle/TransformationsTest.java
index 940a3e8..02397da 100644
--- a/lifecycle/livedata/src/test/java/android/arch/lifecycle/TransformationsTest.java
+++ b/lifecycle/livedata/src/test/java/android/arch/lifecycle/TransformationsTest.java
@@ -21,6 +21,7 @@
 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;
@@ -191,4 +192,25 @@
         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/viewmodel/api/current.txt b/lifecycle/viewmodel/api/current.txt
index 56de7fe..6a82d4c 100644
--- a/lifecycle/viewmodel/api/current.txt
+++ b/lifecycle/viewmodel/api/current.txt
@@ -1,5 +1,10 @@
 package android.arch.lifecycle {
 
+  public class AndroidViewModel extends android.arch.lifecycle.ViewModel {
+    ctor public AndroidViewModel(android.app.Application);
+    method public <T extends android.app.Application> T getApplication();
+  }
+
   public abstract class ViewModel {
     ctor public ViewModel();
     method protected void onCleared();
@@ -12,6 +17,11 @@
     method public <T extends android.arch.lifecycle.ViewModel> T get(java.lang.String, java.lang.Class<T>);
   }
 
+  public static class ViewModelProvider.AndroidViewModelFactory extends android.arch.lifecycle.ViewModelProvider.NewInstanceFactory {
+    ctor public ViewModelProvider.AndroidViewModelFactory(android.app.Application);
+    method public static android.arch.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>);
   }
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/AndroidViewModel.java b/lifecycle/viewmodel/src/main/java/android/arch/lifecycle/AndroidViewModel.java
similarity index 100%
rename from lifecycle/extensions/src/main/java/android/arch/lifecycle/AndroidViewModel.java
rename to lifecycle/viewmodel/src/main/java/android/arch/lifecycle/AndroidViewModel.java
diff --git a/lifecycle/viewmodel/src/main/java/android/arch/lifecycle/ViewModelProvider.java b/lifecycle/viewmodel/src/main/java/android/arch/lifecycle/ViewModelProvider.java
index a7b3aeb..e01aa19 100644
--- a/lifecycle/viewmodel/src/main/java/android/arch/lifecycle/ViewModelProvider.java
+++ b/lifecycle/viewmodel/src/main/java/android/arch/lifecycle/ViewModelProvider.java
@@ -16,9 +16,12 @@
 
 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>
@@ -152,4 +155,57 @@
             }
         }
     }
+
+    /**
+     * {@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}
+         */
+        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/paging/runtime/src/main/java/android/arch/paging/PagedListAdapter.java b/paging/runtime/src/main/java/android/arch/paging/PagedListAdapter.java
index a8158c2..be23271 100644
--- a/paging/runtime/src/main/java/android/arch/paging/PagedListAdapter.java
+++ b/paging/runtime/src/main/java/android/arch/paging/PagedListAdapter.java
@@ -50,7 +50,7 @@
  * class MyViewModel extends ViewModel {
  *     public final LiveData&lt;PagedList&lt;User>> usersList;
  *     public MyViewModel(UserDao userDao) {
- *         usersList = LivePagedListBuilder&lt;>(
+ *         usersList = new LivePagedListBuilder&lt;>(
  *                 userDao.usersByLastName(), /* page size {@literal *}/ 20).build();
  *     }
  * }
diff --git a/paging/runtime/src/main/java/android/arch/paging/PagedListAdapterHelper.java b/paging/runtime/src/main/java/android/arch/paging/PagedListAdapterHelper.java
index 7a0b81a..ba8ffab 100644
--- a/paging/runtime/src/main/java/android/arch/paging/PagedListAdapterHelper.java
+++ b/paging/runtime/src/main/java/android/arch/paging/PagedListAdapterHelper.java
@@ -54,7 +54,7 @@
  * class MyViewModel extends ViewModel {
  *     public final LiveData&lt;PagedList&lt;User>> usersList;
  *     public MyViewModel(UserDao userDao) {
- *         usersList = LivePagedListBuilder&lt;>(
+ *         usersList = new LivePagedListBuilder&lt;>(
  *                 userDao.usersByLastName(), /* page size {@literal *}/ 20).build();
  *     }
  * }
@@ -72,10 +72,8 @@
  * }
  *
  * class UserAdapter extends RecyclerView.Adapter&lt;UserViewHolder> {
- *     private final PagedListAdapterHelper&lt;User> mHelper;
- *     public UserAdapter(PagedListAdapterHelper.Builder&lt;User> builder) {
- *         mHelper = new PagedListAdapterHelper(this, DIFF_CALLBACK);
- *     }
+ *     private final PagedListAdapterHelper&lt;User> mHelper
+ *             = new PagedListAdapterHelper(this, DIFF_CALLBACK);
  *     {@literal @}Override
  *     public int getItemCount() {
  *         return mHelper.getItemCount();
diff --git a/paging/runtime/src/main/java/android/support/v7/recyclerview/extensions/ListAdapterHelper.java b/paging/runtime/src/main/java/android/support/v7/recyclerview/extensions/ListAdapterHelper.java
index d0c7bb3..0c7806f 100644
--- a/paging/runtime/src/main/java/android/support/v7/recyclerview/extensions/ListAdapterHelper.java
+++ b/paging/runtime/src/main/java/android/support/v7/recyclerview/extensions/ListAdapterHelper.java
@@ -68,10 +68,8 @@
  * }
  *
  * class UserAdapter extends RecyclerView.Adapter&lt;UserViewHolder> {
- *     private final ListAdapterHelper&lt;User> mHelper;
- *     public UserAdapter(ListAdapterHelper.Builder&lt;User> builder) {
- *         mHelper = new ListAdapterHelper(this, User.DIFF_CALLBACK);
- *     }
+ *     private final ListAdapterHelper&lt;User> mHelper
+ *             = new ListAdapterHelper(this, User.DIFF_CALLBACK);
  *     {@literal @}Override
  *     public int getItemCount() {
  *         return mHelper.getItemCount();
diff --git a/persistence/db-framework/api/current.txt b/persistence/db-framework/api/current.txt
new file mode 100644
index 0000000..7051765
--- /dev/null
+++ b/persistence/db-framework/api/current.txt
@@ -0,0 +1,9 @@
+package android.arch.persistence.db.framework {
+
+  public final class FrameworkSQLiteOpenHelperFactory implements android.arch.persistence.db.SupportSQLiteOpenHelper.Factory {
+    ctor public FrameworkSQLiteOpenHelperFactory();
+    method public android.arch.persistence.db.SupportSQLiteOpenHelper create(android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration);
+  }
+
+}
+
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
index e9c2b74..d564a03 100644
--- 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
@@ -16,6 +16,8 @@
 
 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;
@@ -312,8 +314,4 @@
     public void close() throws IOException {
         mDelegate.close();
     }
-
-    private static boolean isEmpty(String input) {
-        return input == null || input.length() == 0;
-    }
 }
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
index ccb5614..7f07865 100644
--- 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
@@ -22,7 +22,7 @@
 /**
  * Delegates all calls to a {@link SQLiteStatement}.
  */
-class FrameworkSQLiteStatement implements SupportSQLiteStatement {
+class FrameworkSQLiteStatement extends FrameworkSQLiteProgram implements SupportSQLiteStatement {
     private final SQLiteStatement mDelegate;
 
     /**
@@ -31,40 +31,11 @@
      * @param delegate The SQLiteStatement to delegate calls to.
      */
     FrameworkSQLiteStatement(SQLiteStatement delegate) {
+        super(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 execute() {
         mDelegate.execute();
     }
@@ -88,9 +59,4 @@
     public String simpleQueryForString() {
         return mDelegate.simpleQueryForString();
     }
-
-    @Override
-    public void close() {
-        mDelegate.close();
-    }
 }
diff --git a/persistence/db/api/current.txt b/persistence/db/api/current.txt
new file mode 100644
index 0000000..f96f17a
--- /dev/null
+++ b/persistence/db/api/current.txt
@@ -0,0 +1,123 @@
+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 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 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/java/android/arch/persistence/db/SimpleSQLiteQuery.java b/persistence/db/src/main/java/android/arch/persistence/db/SimpleSQLiteQuery.java
index e2a3829..bcf4f49 100644
--- a/persistence/db/src/main/java/android/arch/persistence/db/SimpleSQLiteQuery.java
+++ b/persistence/db/src/main/java/android/arch/persistence/db/SimpleSQLiteQuery.java
@@ -17,8 +17,8 @@
 package android.arch.persistence.db;
 
 /**
- * A basic implemtation of {@link SupportSQLiteQuery} which receives a query and its args and binds
- * args based on the passed in Object type.
+ * 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;
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
index 65da379..32b5818 100644
--- a/room/common/src/main/java/android/arch/persistence/room/ColumnInfo.java
+++ b/room/common/src/main/java/android/arch/persistence/room/ColumnInfo.java
@@ -68,7 +68,7 @@
      * 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}, or {@link #RTRIM}.
+     * {@link #BINARY}, {@link #NOCASE}, {@link #RTRIM}, {@link #LOCALIZED} or {@link #UNICODE}.
      */
     @Collate int collate() default UNSPECIFIED;
 
@@ -141,8 +141,20 @@
      * @see #collate()
      */
     int RTRIM = 4;
+    /**
+     * Collation sequence that uses system's current locale.
+     *
+     * @see #collate()
+     */
+    int LOCALIZED = 5;
+    /**
+     * Collation sequence that uses Unicode Collation Algorithm.
+     *
+     * @see #collate()
+     */
+    int UNICODE = 6;
 
-    @IntDef({UNSPECIFIED, BINARY, NOCASE, RTRIM})
+    @IntDef({UNSPECIFIED, BINARY, NOCASE, RTRIM, LOCALIZED, UNICODE})
     @interface Collate {
     }
 }
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
new file mode 100644
index 0000000..b41feab
--- /dev/null
+++ b/room/common/src/main/java/android/arch/persistence/room/RawQuery.java
@@ -0,0 +1,157 @@
+/*
+ * 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 be {@link Entity Entities} that are linked from the containing
+     * {@link Database}.
+     * <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/compiler/build.gradle b/room/compiler/build.gradle
index f311a30..0d62cf8 100644
--- a/room/compiler/build.gradle
+++ b/room/compiler/build.gradle
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+
+import android.support.SupportConfig
+
 import static android.support.dependencies.DependenciesKt.*
 import android.support.LibraryGroups
 import android.support.LibraryVersions
@@ -53,7 +56,7 @@
     testCompile(INTELLIJ_ANNOTATIONS)
     testCompile(JSR250)
     testCompile(MOCKITO_CORE)
-    testCompile fileTree(dir: "${sdkHandler.sdkFolder}/platforms/android-$rootProject.ext.currentSdk/",
+    testCompile fileTree(dir: "${sdkHandler.sdkFolder}/platforms/android-$SupportConfig.CURRENT_SDK_VERSION/",
             include : "android.jar")
     testCompile fileTree(dir: "${new File(project(":room:runtime").buildDir, "libJar")}",
             include : "*.jar")
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
index 521c271..44800fc 100644
--- 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
@@ -204,7 +204,7 @@
 }
 
 // converts ? in Set< ? extends Foo> to Foo
-private fun TypeMirror.extendsBound(): TypeMirror? {
+fun TypeMirror.extendsBound(): TypeMirror? {
     return this.accept(object : SimpleTypeVisitor7<TypeMirror?, Void?>() {
         override fun visitWildcard(type: WildcardType, ignored: Void?): TypeMirror? {
             return type.extendsBound
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
index 367c926..5893e8f 100644
--- 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
@@ -46,6 +46,8 @@
     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 {
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
index fcf2e08..55a9bf9 100644
--- 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
@@ -38,15 +38,18 @@
 
 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>) {
+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())
+                emptyList(), false)
     }
 
     /**
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
index affa8c9..c5c5f17 100644
--- 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
@@ -28,16 +28,21 @@
 import javax.lang.model.type.TypeKind
 import javax.lang.model.type.TypeMirror
 
-class QueryVisitor(val original: String, val syntaxErrors: ArrayList<String>,
-                   statement: ParseTree) : SQLiteBaseVisitor<Void?>() {
-    val bindingExpressions = arrayListOf<TerminalNode>()
+@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
-    val tableNames = mutableSetOf<Table>()
-    val withClauseNames = mutableSetOf<String>()
-    val queryType: QueryType
+    private val tableNames = mutableSetOf<Table>()
+    private val withClauseNames = mutableSetOf<String>()
+    private val queryType: QueryType
 
     init {
-        queryType = (0..statement.childCount - 1).map {
+        queryType = (0 until statement.childCount).map {
             findQueryType(statement.getChild(it))
         }.filterNot { it == QueryType.UNKNOWN }.firstOrNull() ?: QueryType.UNKNOWN
 
@@ -78,11 +83,13 @@
     }
 
     fun createParsedQuery(): ParsedQuery {
-        return ParsedQuery(original,
-                queryType,
-                bindingExpressions.sortedBy { it.sourceInterval.a },
-                tableNames,
-                syntaxErrors)
+        return ParsedQuery(
+                original = original,
+                type = queryType,
+                inputs = bindingExpressions.sortedBy { it.sourceInterval.a },
+                tables = tableNames,
+                syntaxErrors = syntaxErrors,
+                runtimeQueryPlaceholder = forRuntimeQuery)
     }
 
     override fun visitCommon_table_expression(
@@ -129,9 +136,10 @@
             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?) {
+                override fun syntaxError(
+                        recognizer: Recognizer<*, *>, offendingSymbol: Any,
+                        line: Int, charPositionInLine: Int, msg: String,
+                        e: RecognitionException?) {
                     syntaxErrors.add(msg)
                 }
             })
@@ -141,7 +149,7 @@
                 if (statementList.isEmpty()) {
                     syntaxErrors.add(ParserErrors.NOT_ONE_QUERY)
                     return ParsedQuery(input, QueryType.UNKNOWN, emptyList(), emptySet(),
-                            listOf(ParserErrors.NOT_ONE_QUERY))
+                            listOf(ParserErrors.NOT_ONE_QUERY), false)
                 }
                 val statements = statementList.first().children
                         .filter { it is SQLiteParser.Sql_stmtContext }
@@ -149,15 +157,34 @@
                     syntaxErrors.add(ParserErrors.NOT_ONE_QUERY)
                 }
                 val statement = statements.first()
-                return QueryVisitor(input, syntaxErrors, statement).createParsedQuery()
+                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}"))
+                        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
+            )
+        }
     }
 }
 
@@ -181,6 +208,7 @@
     INTEGER,
     REAL,
     BLOB;
+
     fun getTypeMirrors(env: ProcessingEnvironment): List<TypeMirror>? {
         val typeUtils = env.typeUtils
         return when (this) {
@@ -219,7 +247,9 @@
 enum class Collate {
     BINARY,
     NOCASE,
-    RTRIM;
+    RTRIM,
+    LOCALIZED,
+    UNICODE;
 
     companion object {
         fun fromAnnotationValue(value: Int): Collate? {
@@ -227,6 +257,8 @@
                 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/processor/DaoProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DaoProcessor.kt
index e856a56..cc758eb 100644
--- 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
@@ -19,6 +19,7 @@
 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
@@ -42,7 +43,7 @@
 
     companion object {
         val PROCESSED_ANNOTATIONS = listOf(Insert::class, Delete::class, Query::class,
-                Update::class)
+                Update::class, RawQuery::class)
     }
 
     fun process(): Dao {
@@ -71,11 +72,14 @@
                     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)) {
+        val processorVerifier = if (element.hasAnnotation(SkipQueryVerification::class) ||
+                element.hasAnnotation(RawQuery::class)) {
             null
         } else {
             dbVerifier
@@ -89,6 +93,14 @@
                     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,
@@ -149,6 +161,7 @@
         return Dao(element = element,
                 type = declaredType,
                 queryMethods = queryMethods,
+                rawQueryMethods = rawQueryMethods,
                 insertionMethods = insertionMethods,
                 deletionMethods = deletionMethods,
                 updateMethods = updateMethods,
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
index 0b17d3c..88961e7 100644
--- 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
@@ -19,6 +19,7 @@
 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
@@ -34,6 +35,8 @@
     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>."
@@ -144,7 +147,8 @@
 
     val OBSERVABLE_QUERY_NOTHING_TO_OBSERVE = "Observable query return type (LiveData, Flowable" +
             " etc) can only be used with SELECT queries that directly or indirectly (via" +
-            " @Relation, for example) access at least one table."
+            " @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"
@@ -490,4 +494,9 @@
             " 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."
 }
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
new file mode 100644
index 0000000..6858ccc
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessor.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 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.Entity
+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 observedEntities = processObservedTables()
+        val query = SqlParser.rawQueryForTables(
+                observedEntities.map { it.tableName }.toSet())
+        // 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(),
+                observedEntities = observedEntities,
+                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(): List<Entity> {
+        val annotation = MoreElements
+                .getAnnotationMirror(executableElement,
+                        android.arch.persistence.room.RawQuery::class.java)
+                .orNull() ?: return emptyList()
+        val entityList = AnnotationMirrors.getAnnotationValue(annotation, "observedEntities")
+        return entityList
+                .toListOfClassTypes()
+                .map {
+                    EntityProcessor(
+                            baseContext = context,
+                            element = MoreTypes.asTypeElement(it)
+                    ).process()
+                }
+    }
+
+    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/ShortcutParameterProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ShortcutParameterProcessor.kt
index 6ccd12a..7fd6859 100644
--- 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
@@ -17,6 +17,7 @@
 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
@@ -60,7 +61,11 @@
 
         fun verifyAndPair(entityType: TypeMirror, isMultiple: Boolean): Pair<TypeMirror?, Boolean> {
             if (!MoreTypes.isType(entityType)) {
-                return Pair(null, isMultiple)
+                // 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)) {
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
index d64ef4b..4bd52cc 100644
--- 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
@@ -39,9 +39,9 @@
         val adapter = context.typeAdapterStore.findQueryResultAdapter(typeArg, query)
         val tableNames = ((adapter?.accessedTableNames() ?: emptyList()) +
                 query.tables.map { it.name }).toSet()
-        context.checker.check(!tableNames.isEmpty(),
-                declared.asElement(),
-                ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
+        if (tableNames.isEmpty()) {
+            context.logger.e(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
+        }
         return create(
                 typeArg = typeArg,
                 resultAdapter = adapter,
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
index d64300c..c465d4a 100644
--- 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
@@ -24,8 +24,8 @@
 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.DataSourceQueryResultBinderProvider
 import android.arch.persistence.room.solver.binderprovider.CursorQueryResultBinderProvider
+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
@@ -332,7 +332,7 @@
                 }
             }
 
-            if (rowAdapter != null && !(rowAdapterLogs?.hasErrors() ?: false)) {
+            if (rowAdapter != null && rowAdapterLogs?.hasErrors() != true) {
                 rowAdapterLogs?.writeTo(context.processingEnv)
                 return rowAdapter
             }
@@ -349,6 +349,21 @@
                 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
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
index fede566..7aa24cc 100644
--- 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
@@ -25,6 +25,7 @@
 
 class CursorQueryResultBinder : QueryResultBinder(NO_OP_RESULT_ADAPTER) {
     override fun convertAndReturn(roomSQLiteQueryVar: String,
+                                  canReleaseQuery: Boolean,
                                   dbField: FieldSpec,
                                   inTransaction: Boolean,
                                   scope: CodeGenScope) {
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
index f4f1d43..dc8540e 100644
--- 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
@@ -38,6 +38,7 @@
                                 adapter: QueryResultAdapter?)
     : BaseObservableQueryResultBinder(adapter) {
     override fun convertAndReturn(roomSQLiteQueryVar: String,
+                                  canReleaseQuery: Boolean,
                                   dbField: FieldSpec,
                                   inTransaction: Boolean,
                                   scope: CodeGenScope) {
@@ -55,7 +56,9 @@
                         dbField = dbField,
                         scope = scope)
             }.build())
-            addMethod(createFinalizeMethod(roomSQLiteQueryVar))
+            if (canReleaseQuery) {
+                addMethod(createFinalizeMethod(roomSQLiteQueryVar))
+            }
         }.build()
         scope.builder().apply {
             val tableNamesList = queryTableNames.joinToString(",") { "\"$it\"" }
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
index b9623c1..aa64b1b 100644
--- 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
@@ -28,6 +28,7 @@
  */
 class InstantQueryResultBinder(adapter: QueryResultAdapter?) : QueryResultBinder(adapter) {
     override fun convertAndReturn(roomSQLiteQueryVar: String,
+                                  canReleaseQuery: Boolean,
                                   dbField: FieldSpec,
                                   inTransaction: Boolean,
                                   scope: CodeGenScope) {
@@ -49,7 +50,9 @@
             }
             nextControlFlow("finally").apply {
                 addStatement("$L.close()", cursorVar)
-                addStatement("$L.release()", roomSQLiteQueryVar)
+                if (canReleaseQuery) {
+                    addStatement("$L.release()", roomSQLiteQueryVar)
+                }
             }
             endControlFlow()
         }
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
index 1191ae3..416b8b8 100644
--- 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
@@ -42,6 +42,7 @@
     @Suppress("JoinDeclarationAndAssignment")
     override fun convertAndReturn(
             roomSQLiteQueryVar: String,
+            canReleaseQuery: Boolean,
             dbField: FieldSpec,
             inTransaction: Boolean,
             scope: CodeGenScope
@@ -62,7 +63,9 @@
                     inTransaction = inTransaction,
                     scope = scope
             ))
-            addMethod(createFinalizeMethod(roomSQLiteQueryVar))
+            if (canReleaseQuery) {
+                addMethod(createFinalizeMethod(roomSQLiteQueryVar))
+            }
         }.build()
         scope.builder().apply {
             addStatement("return $L.getLiveData()", liveDataImpl)
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/LivePagedListQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/LivePagedListQueryResultBinder.kt
index ceb946e..25d8416 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/LivePagedListQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/LivePagedListQueryResultBinder.kt
@@ -33,6 +33,7 @@
     val typeName = tiledDataSourceQueryResultBinder.itemTypeName
     override fun convertAndReturn(
             roomSQLiteQueryVar: String,
+            canReleaseQuery: Boolean,
             dbField: FieldSpec,
             inTransaction: Boolean,
             scope: CodeGenScope
@@ -64,6 +65,7 @@
         val countedBinderScope = scope.fork()
         tiledDataSourceQueryResultBinder.convertAndReturn(
                 roomSQLiteQueryVar = roomSQLiteQueryVar,
+                canReleaseQuery = true,
                 dbField = dbField,
                 inTransaction = inTransaction,
                 scope = countedBinderScope)
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
index a013dc7..ee6e88b 100644
--- 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
@@ -38,49 +38,55 @@
  * <p>
  * The info comes from the query processor so we know about the order of columns in the result etc.
  */
-class PojoRowAdapter(context: Context, val info: QueryResultInfo,
-                     val pojo: Pojo, out: TypeMirror) : RowAdapter(out) {
+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<Field>(), { it })
+        val remainingFields = pojo.fields.mapTo(mutableListOf(), { it })
         val unusedColumns = arrayListOf<String>()
-        val 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
+        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)
             }
-        }.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()
         }
-        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)
-        }
-
         relationCollectors = RelationCollector.createCollectors(context, pojo.relations)
 
         mapping = Mapping(
@@ -105,9 +111,14 @@
         relationCollectors.forEach { it.writeInitCode(scope) }
         mapping.fieldsWithIndices = mapping.matchedFields.map {
             val indexVar = scope.getTmpVar("_cursorIndexOf${it.name.stripNonJava().capitalize()}")
-            scope.builder().addStatement("final $T $L = $L.getColumnIndexOrThrow($S)",
-                    TypeName.INT, indexVar, cursorVarName, it.columnName)
-            FieldWithIndex(field = it, indexVar = indexVar, alwaysExists = true)
+            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)
         }
     }
 
@@ -136,9 +147,10 @@
                 }
             }
 
-    data class Mapping(val matchedFields: List<Field>,
-                       val unusedColumns: List<String>,
-                       val unusedFields: List<Field>) {
+    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/QueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/QueryResultBinder.kt
index a77d97f..9fff325 100644
--- 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
@@ -31,8 +31,10 @@
      * receives the sql, bind args and adapter and generates the code that runs the query
      * and returns the result.
      */
-    abstract fun convertAndReturn(roomSQLiteQueryVar: String,
-                                  dbField: FieldSpec,
-                                  inTransaction: Boolean,
-                                  scope: CodeGenScope)
+    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/RxCallableQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/RxCallableQueryResultBinder.kt
index 2aba076..1bdd134 100644
--- 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
@@ -41,6 +41,7 @@
                                   adapter: QueryResultAdapter?)
     : QueryResultBinder(adapter) {
     override fun convertAndReturn(roomSQLiteQueryVar: String,
+                                  canReleaseQuery: Boolean,
                                   dbField: FieldSpec,
                                   inTransaction: Boolean,
                                   scope: CodeGenScope) {
@@ -50,6 +51,7 @@
                     typeName))
             addMethod(createCallMethod(
                     roomSQLiteQueryVar = roomSQLiteQueryVar,
+                    canReleaseQuery = canReleaseQuery,
                     dbField = dbField,
                     inTransaction = inTransaction,
                     scope = scope))
@@ -59,10 +61,11 @@
         }
     }
 
-    fun createCallMethod(roomSQLiteQueryVar: String,
-                         dbField: FieldSpec,
-                         inTransaction: Boolean,
-                         scope: CodeGenScope): MethodSpec {
+    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())
@@ -95,7 +98,9 @@
             }
             nextControlFlow("finally").apply {
                 addStatement("$L.close()", cursorVar)
-                addStatement("$L.release()", roomSQLiteQueryVar)
+                if (canReleaseQuery) {
+                    addStatement("$L.release()", roomSQLiteQueryVar)
+                }
             }
             endControlFlow()
             transactionWrapper?.endTransactionWithControlFlow()
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/TiledDataSourceQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/TiledDataSourceQueryResultBinder.kt
index 733fdee..19917ff 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/TiledDataSourceQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/TiledDataSourceQueryResultBinder.kt
@@ -38,6 +38,7 @@
     val typeName: ParameterizedTypeName = ParameterizedTypeName.get(
             RoomTypeNames.LIMIT_OFFSET_DATA_SOURCE, itemTypeName)
     override fun convertAndReturn(roomSQLiteQueryVar: String,
+                                  canReleaseQuery: Boolean,
                                   dbField: FieldSpec,
                                   inTransaction: Boolean,
                                   scope: CodeGenScope) {
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
index d0ef212..9afa44c 100644
--- 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
@@ -21,15 +21,18 @@
 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 insertionMethods: List<InsertionMethod>,
-               val deletionMethods: List<DeletionMethod>,
-               val updateMethods: List<UpdateMethod>,
-               val transactionMethods: List<TransactionMethod>,
-               val constructorParamType: TypeName?) {
+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")
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
index 02e83b3..2cffbf3 100644
--- 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
@@ -56,6 +56,12 @@
      * 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 }
@@ -71,6 +77,14 @@
 
     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/Entity.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Entity.kt
index 48592bd..b855f96 100644
--- 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
@@ -22,18 +22,30 @@
 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) {
+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)
     }
 
-    fun createTableQuery(tableName: String): String {
+    // 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)
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
index 838016e..43e0605 100644
--- 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
@@ -35,7 +35,7 @@
                  * embedded child of the main Pojo*/
                  val parent: EmbeddedField? = null,
                  // index might be removed when being merged into an Entity
-                 var indexed: Boolean = false) {
+                 var indexed: Boolean = false) : HasSchemaIdentity {
     lateinit var getter: FieldGetter
     lateinit var setter: FieldSetter
     // binds the field into a statement
@@ -47,6 +47,11 @@
     /** 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
      */
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
index 66cf3a0..833de97 100644
--- 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
@@ -26,7 +26,16 @@
                       val childFields: List<Field>,
                       val onDelete: ForeignKeyAction,
                       val onUpdate: ForeignKeyAction,
-                      val deferred: Boolean) {
+                      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)})" +
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
index 694c627..b4952cf 100644
--- 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
@@ -22,11 +22,17 @@
 /**
  * Represents a processed index.
  */
-data class Index(val name: String, val unique: Boolean, val fields: List<Field>) {
+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"
@@ -35,7 +41,7 @@
         }
         return """
             CREATE $uniqueSQL INDEX `$name`
-            ON `$tableName` (${fields.map { it.columnName }.joinToString(", ") { "`$it`"}})
+            ON `$tableName` (${fields.map { it.columnName }.joinToString(", ") { "`$it`" }})
             """.trimIndent().replace("\n", " ")
     }
 
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
index d1a2c21..1d76d40 100644
--- 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
@@ -22,7 +22,7 @@
  * Represents a PrimaryKey for an Entity.
  */
 data class PrimaryKey(val declaredIn: Element?, val fields: List<Field>,
-                      val autoGenerateId: Boolean) {
+                      val autoGenerateId: Boolean) : HasSchemaIdentity {
     companion object {
         val MISSING = PrimaryKey(null, emptyList(), false)
     }
@@ -36,4 +36,8 @@
 
     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/RawQueryMethod.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/RawQueryMethod.kt
new file mode 100644
index 0000000..ad1f10d
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/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 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 observedEntities: List<Entity>,
+        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/SchemaIdentityKey.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/SchemaIdentityKey.kt
new file mode 100644
index 0000000..06c9ff5
--- /dev/null
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/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 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/writer/ClassWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/ClassWriter.kt
index 8cf511f..ebd0adf 100644
--- 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
@@ -112,11 +112,11 @@
     abstract class SharedMethodSpec(val baseName: String) {
 
         abstract fun getUniqueKey(): String
-        abstract fun prepare(writer: ClassWriter, builder: MethodSpec.Builder)
+        abstract fun prepare(methodName: String, writer: ClassWriter, builder: MethodSpec.Builder)
 
         fun build(writer: ClassWriter, name: String): MethodSpec {
             val builder = MethodSpec.methodBuilder(name)
-            prepare(writer, builder)
+            prepare(name, writer, builder)
             return builder.build()
         }
     }
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
index 5125444..4e319a9 100644
--- 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
@@ -29,6 +29,7 @@
 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
@@ -55,6 +56,7 @@
 class DaoWriter(val dao: Dao, val processingEnv: ProcessingEnvironment)
     : ClassWriter(dao.typeName) {
     val declaredDao = MoreTypes.asDeclared(dao.element.asType())
+
     companion object {
         // TODO nothing prevents this from conflicting, we should fix.
         val dbField: FieldSpec = FieldSpec
@@ -111,6 +113,9 @@
             oneOffDeleteOrUpdateQueries.forEach {
                 addMethod(createDeleteOrUpdateQueryMethod(it))
             }
+            dao.rawQueryMethods.forEach {
+                addMethod(createRawQueryMethod(it))
+            }
         }
         return builder
     }
@@ -194,8 +199,9 @@
         return methodBuilder.build()
     }
 
-    private fun MethodSpec.Builder.addDelegateToSuperStatement(element: ExecutableElement,
-                                                               result: String?) {
+    private fun MethodSpec.Builder.addDelegateToSuperStatement(
+            element: ExecutableElement,
+            result: String?) {
         val params: MutableList<Any> = mutableListOf()
         val format = buildString {
             if (result != null) {
@@ -220,9 +226,10 @@
         addStatement(format, *params.toTypedArray())
     }
 
-    private fun createConstructor(dbParam: ParameterSpec,
-                                  shortcutMethods: List<PreparedStmtQuery>,
-                                  callSuper: Boolean): MethodSpec {
+    private fun createConstructor(
+            dbParam: ParameterSpec,
+            shortcutMethods: List<PreparedStmtQuery>,
+            callSuper: Boolean): MethodSpec {
         return MethodSpec.constructorBuilder().apply {
             addParameter(dbParam)
             addModifiers(PUBLIC)
@@ -250,6 +257,52 @@
         }.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 = 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))
@@ -451,14 +504,16 @@
         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 {
+    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)
@@ -477,8 +532,9 @@
      * 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) {
+    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
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
index a96e23c..b9d6aec 100644
--- 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
@@ -22,8 +22,8 @@
 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.Entity
 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
@@ -38,7 +38,7 @@
         return "generic_entity_converter_of_${entity.element.qualifiedName}"
     }
 
-    override fun prepare(writer: ClassWriter, builder: MethodSpec.Builder) {
+    override fun prepare(methodName: String, writer: ClassWriter, builder: MethodSpec.Builder) {
         builder.apply {
             val cursorParam = ParameterSpec
                     .builder(AndroidTypeNames.CURSOR, "cursor").build()
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
index 71f0510..ecd9b59 100644
--- 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
@@ -19,6 +19,7 @@
 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
@@ -34,7 +35,7 @@
 /**
  * Writes the method that fetches the relations of a POJO and assigns them into the given map.
  */
-class RelationCollectorMethodWriter(val collector: RelationCollector)
+class RelationCollectorMethodWriter(private val collector: RelationCollector)
     : ClassWriter.SharedMethodSpec(
         "fetchRelationship${collector.relation.entity.tableName.stripNonJava()}" +
                 "As${collector.relation.pojoTypeName.toString().stripNonJava()}") {
@@ -51,7 +52,7 @@
                 "-${relation.createLoadAllSql()}"
     }
 
-    override fun prepare(writer: ClassWriter, builder: MethodSpec.Builder) {
+    override fun prepare(methodName: String, writer: ClassWriter, builder: MethodSpec.Builder) {
         val scope = CodeGenScope(writer)
         val relation = collector.relation
 
@@ -74,6 +75,41 @@
                 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,
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
index 16fcd9c..d62cc1c 100644
--- 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
@@ -40,10 +40,10 @@
         scope.builder().apply {
             val sqliteConfigVar = scope.getTmpVar("_sqliteConfig")
             val callbackVar = scope.getTmpVar("_openCallback")
-            addStatement("final $T $L = new $T($N, $L, $S)",
+            addStatement("final $T $L = new $T($N, $L, $S, $S)",
                     SupportDbTypeNames.SQLITE_OPEN_HELPER_CALLBACK,
                     callbackVar, RoomTypeNames.OPEN_HELPER, configuration,
-                    createOpenCallback(scope), database.identityHash)
+                    createOpenCallback(scope), database.identityHash, database.legacyIdentityHash)
             // build configuration
             addStatement(
                     """
diff --git a/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java b/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java
index cfdc110..db2b450 100644
--- a/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java
+++ b/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java
@@ -28,7 +28,7 @@
             public void createAllTables(SupportSQLiteDatabase _db) {
                 _db.execSQL("CREATE TABLE IF NOT EXISTS `User` (`uid` INTEGER NOT NULL, `name` TEXT, `lastName` TEXT, `ageColumn` INTEGER NOT NULL, PRIMARY KEY(`uid`))");
                 _db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)");
-                _db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"6773601c5bcf94c71ee4eb0de04f21a4\")");
+                _db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"cd8098a1e968898879c194cef2dff8f7\")");
             }
 
             public void dropAllTables(SupportSQLiteDatabase _db) {
@@ -69,7 +69,7 @@
                             + " Found:\n" + _existingUser);
                 }
             }
-        }, "6773601c5bcf94c71ee4eb0de04f21a4");
+        }, "cd8098a1e968898879c194cef2dff8f7", "6773601c5bcf94c71ee4eb0de04f21a4");
         final SupportSQLiteOpenHelper.Configuration _sqliteConfig = SupportSQLiteOpenHelper.Configuration.builder(configuration.context)
                 .name(configuration.name)
                 .callback(_openCallback)
@@ -96,4 +96,4 @@
             }
         }
     }
-}
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..3eb629a
--- /dev/null
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessorTest.kt
@@ -0,0 +1,246 @@
+/*
+ * 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.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.observedEntities.size, `is`(1))
+            assertThat(
+                    query.observedEntities.first().typeName,
+                    `is`(COMMON.USER_TYPE_NAME as TypeName))
+        }.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.observedEntities, `is`(emptyList()))
+        }.failsToCompile()
+                .withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
+    }
+
+    @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.observedEntities, `is`(emptyList()))
+        }.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
+        )
+    }
+
+    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))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(Query::class, Dao::class, ColumnInfo::class,
+                                Entity::class, PrimaryKey::class, RawQueryMethod::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/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
index 60d97ed..a80e840 100644
--- 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
@@ -26,6 +26,12 @@
     @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)
 
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
index 77deca7..3f58eee 100644
--- 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
@@ -49,9 +49,9 @@
         booksDao.addPublishers(TestUtil.PUBLISHER)
         booksDao.addBooks(TestUtil.BOOK_1)
 
-        var expected = BookWithPublisher(TestUtil.BOOK_1.bookId, TestUtil.BOOK_1.title,
+        val expected = BookWithPublisher(TestUtil.BOOK_1.bookId, TestUtil.BOOK_1.title,
                 TestUtil.PUBLISHER)
-        var expectedList = ArrayList<BookWithPublisher>()
+        val expectedList = ArrayList<BookWithPublisher>()
         expectedList.add(expected)
 
         assertThat(database.booksDao().getBooksWithPublisher(),
@@ -153,4 +153,22 @@
         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))
+    }
 }
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
index 610afb2..98282ab 100644
--- 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
@@ -25,6 +25,7 @@
 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;
@@ -61,6 +62,7 @@
     public abstract SpecificDogDao getSpecificDogDao();
     public abstract WithClauseDao getWithClauseDao();
     public abstract FunnyNamedDao getFunnyNamedDao();
+    public abstract RawDao getRawDao();
 
     @SuppressWarnings("unused")
     public static class Converters {
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
new file mode 100644
index 0000000..b4469c0
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/RawDao.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.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
+    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 = User.class)
+    LiveData<User> getUserLiveData(String 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/migration/MigrationTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java
index 7fe2bc9..c850a4d 100644
--- 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
@@ -17,9 +17,13 @@
 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;
@@ -33,6 +37,7 @@
 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;
@@ -252,6 +257,95 @@
         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();
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
index f0285a0..7a2faf0 100644
--- 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
@@ -16,7 +16,7 @@
 
 package android.arch.persistence.room.integration.testapp.paging;
 
-import static android.test.MoreAsserts.assertEmpty;
+import static junit.framework.Assert.assertFalse;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -79,7 +79,7 @@
         List<User> initial = dataSource.loadRange(0, 10);
 
         assertThat(initial.get(0), is(users.get(0)));
-        assertEmpty(dataSource.loadRange(1, 10));
+        assertFalse(dataSource.loadRange(1, 10).iterator().hasNext());
     }
 
     @Test
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
new file mode 100644
index 0000000..7b0e933
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/CollationTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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.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
+    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
+    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/PojoTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/PojoTest.java
index b43e274..b1579fc 100644
--- 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
@@ -19,15 +19,15 @@
 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.runner.AndroidJUnit4;
-
 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;
@@ -35,6 +35,7 @@
 
 import java.util.Arrays;
 
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public class PojoTest {
     private UserDao mUserDao;
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
index f9ff4c2..c3ebfe9 100644
--- 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
@@ -35,6 +35,7 @@
 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;
@@ -188,4 +189,46 @@
                 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/RawQueryTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RawQueryTest.java
new file mode 100644
index 0000000..4aae4ea
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RawQueryTest.java
@@ -0,0 +1,196 @@
+/*
+ * 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 android.arch.core.executor.testing.CountingTaskExecutorRule;
+import android.arch.lifecycle.LiveData;
+import android.arch.persistence.db.SimpleSQLiteQuery;
+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.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() throws TimeoutException, InterruptedException {
+        LiveData<User> liveData = mRawDao.getUserLiveData("SELECT * FROM User WHERE mId = 3");
+        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));
+    }
+
+    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/TestDatabaseTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/TestDatabaseTest.java
index ec77561..e2525c4 100644
--- 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
@@ -21,6 +21,7 @@
 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;
@@ -44,6 +45,7 @@
     protected SpecificDogDao mSpecificDogDao;
     protected WithClauseDao mWithClauseDao;
     protected FunnyNamedDao mFunnyNamedDao;
+    protected RawDao mRawDao;
 
     @Before
     public void createDb() {
@@ -58,5 +60,6 @@
         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/vo/NameAndLastName.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/NameAndLastName.java
index 29e2554..a6e8223 100644
--- 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
@@ -33,4 +33,23 @@
     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/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
index 4ac9029..f131838 100644
--- 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
@@ -32,7 +32,7 @@
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class DatabaseBundle {
+public class DatabaseBundle implements SchemaEquality<DatabaseBundle> {
     @SerializedName("version")
     private int mVersion;
     @SerializedName("identityHash")
@@ -104,4 +104,10 @@
         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
index 8980a3b..d78ac35 100644
--- 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
@@ -16,6 +16,8 @@
 
 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;
@@ -35,7 +37,7 @@
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class EntityBundle {
+public class EntityBundle implements SchemaEquality<EntityBundle> {
 
     static final String NEW_TABLE_PREFIX = "_new_";
 
@@ -176,4 +178,15 @@
         }
         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
index eb73d81..5f74087 100644
--- 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
@@ -27,7 +27,7 @@
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class FieldBundle {
+public class FieldBundle implements SchemaEquality<FieldBundle> {
     @SerializedName("fieldPath")
     private String mFieldPath;
     @SerializedName("columnName")
@@ -59,4 +59,14 @@
     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
index d72cf8c..367dd74 100644
--- 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
@@ -28,7 +28,7 @@
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class ForeignKeyBundle {
+public class ForeignKeyBundle implements SchemaEquality<ForeignKeyBundle> {
     @SerializedName("table")
     private String mTable;
     @SerializedName("onDelete")
@@ -43,10 +43,10 @@
     /**
      * 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 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,
@@ -102,4 +102,18 @@
     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
index ba40618..e991316 100644
--- 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
@@ -28,7 +28,9 @@
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class IndexBundle {
+public class IndexBundle implements SchemaEquality<IndexBundle> {
+    // should match Index.kt
+    public static final String DEFAULT_PREFIX = "index_";
     @SerializedName("name")
     private String mName;
     @SerializedName("unique")
@@ -65,4 +67,25 @@
     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
index c16f967..820aa7e 100644
--- 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
@@ -28,7 +28,7 @@
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class PrimaryKeyBundle {
+public class PrimaryKeyBundle implements SchemaEquality<PrimaryKeyBundle> {
     @SerializedName("columnNames")
     private List<String> mColumnNames;
     @SerializedName("autoGenerate")
@@ -46,4 +46,9 @@
     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
index d6171aa..af35e6f 100644
--- 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
@@ -37,7 +37,7 @@
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class SchemaBundle {
+public class SchemaBundle implements SchemaEquality<SchemaBundle> {
 
     @SerializedName("formatVersion")
     private int mFormatVersion;
@@ -47,6 +47,7 @@
     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();
     }
@@ -104,4 +105,9 @@
         }
     }
 
+    @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
new file mode 100644
index 0000000..59ea4b0
--- /dev/null
+++ b/room/migration/src/main/java/android/arch/persistence/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 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
new file mode 100644
index 0000000..65a7572
--- /dev/null
+++ b/room/migration/src/main/java/android/arch/persistence/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 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/test/java/android/arch/persistence/room/migration/bundle/EntityBundleTest.java b/room/migration/src/test/java/android/arch/persistence/room/migration/bundle/EntityBundleTest.java
new file mode 100644
index 0000000..4b4df8b
--- /dev/null
+++ b/room/migration/src/test/java/android/arch/persistence/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 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
new file mode 100644
index 0000000..eac4477
--- /dev/null
+++ b/room/migration/src/test/java/android/arch/persistence/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 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
new file mode 100644
index 0000000..be1b81e
--- /dev/null
+++ b/room/migration/src/test/java/android/arch/persistence/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 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
new file mode 100644
index 0000000..aa7230f
--- /dev/null
+++ b/room/migration/src/test/java/android/arch/persistence/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 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
new file mode 100644
index 0000000..3b9e464
--- /dev/null
+++ b/room/migration/src/test/java/android/arch/persistence/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 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/runtime/api/current.txt b/room/runtime/api/current.txt
new file mode 100644
index 0000000..29cfa85
--- /dev/null
+++ b/room/runtime/api/current.txt
@@ -0,0 +1,90 @@
+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.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 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(java.lang.Integer...);
+    method public android.arch.persistence.room.RoomDatabase.Builder<T> openHelperFactory(android.arch.persistence.db.SupportSQLiteOpenHelper.Factory);
+  }
+
+  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 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/src/main/java/android/arch/persistence/room/DatabaseConfiguration.java b/room/runtime/src/main/java/android/arch/persistence/room/DatabaseConfiguration.java
index adf5d4d..42acc1d 100644
--- a/room/runtime/src/main/java/android/arch/persistence/room/DatabaseConfiguration.java
+++ b/room/runtime/src/main/java/android/arch/persistence/room/DatabaseConfiguration.java
@@ -23,6 +23,7 @@
 import android.support.annotation.RestrictTo;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * Configuration class for a {@link RoomDatabase}.
@@ -65,6 +66,11 @@
     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.
@@ -75,6 +81,8 @@
      * @param allowMainThreadQueries Whether to allow main thread reads/writes or not.
      * @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
      */
@@ -84,7 +92,8 @@
             @NonNull RoomDatabase.MigrationContainer migrationContainer,
             @Nullable List<RoomDatabase.Callback> callbacks,
             boolean allowMainThreadQueries,
-            boolean requireMigration) {
+            boolean requireMigration,
+            @Nullable Set<Integer> migrationNotRequiredFrom) {
         this.sqliteOpenHelperFactory = sqliteOpenHelperFactory;
         this.context = context;
         this.name = name;
@@ -92,5 +101,21 @@
         this.callbacks = callbacks;
         this.allowMainThreadQueries = allowMainThreadQueries;
         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/RoomDatabase.java b/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java
index 2a108f9..2f6b13d 100644
--- a/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java
+++ b/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java
@@ -35,7 +35,9 @@
 
 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;
@@ -52,6 +54,13 @@
 //@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;
@@ -326,7 +335,14 @@
         /**
          * Migrations, mapped by from-to pairs.
          */
-        private MigrationContainer mMigrationContainer;
+        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;
@@ -369,7 +385,15 @@
          * @return this
          */
         @NonNull
-        public Builder<T> addMigrations(@NonNull Migration... migrations) {
+        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;
         }
@@ -416,6 +440,36 @@
         }
 
         /**
+         * 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.
@@ -449,12 +503,28 @@
                 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, mRequireMigration);
+                            mCallbacks, mAllowMainThreadQueries, mRequireMigration,
+                            mMigrationsNotRequiredFrom);
             T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
             db.init(configuration);
             return 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
index 47279d6..aad6895 100644
--- a/room/runtime/src/main/java/android/arch/persistence/room/RoomOpenHelper.java
+++ b/room/runtime/src/main/java/android/arch/persistence/room/RoomOpenHelper.java
@@ -41,13 +41,20 @@
     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 identityHash, @NonNull String legacyHash) {
         super(delegate.version);
         mConfiguration = configuration;
         mDelegate = delegate;
         mIdentityHash = identityHash;
+        mLegacyHash = legacyHash;
     }
 
     @Override
@@ -78,14 +85,17 @@
             }
         }
         if (!migrated) {
-            if (mConfiguration == null || mConfiguration.requireMigration) {
+            if (mConfiguration != null && !mConfiguration.isMigrationRequiredFrom(oldVersion)) {
+                mDelegate.dropAllTables(db);
+                mDelegate.createAllTables(db);
+            } else {
                 throw new IllegalStateException("A migration from " + oldVersion + " to "
-                + newVersion + " is necessary. Please provide a Migration in the builder or call"
-                        + " fallbackToDestructiveMigration in the builder in which case Room will"
-                        + " re-create all of the tables.");
+                        + 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.");
             }
-            mDelegate.dropAllTables(db);
-            mDelegate.createAllTables(db);
         }
     }
 
@@ -115,7 +125,7 @@
         } finally {
             cursor.close();
         }
-        if (!mIdentityHash.equals(identityHash)) {
+        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.");
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
index 0728cca..33d96d8 100644
--- a/room/runtime/src/test/java/android/arch/persistence/room/BuilderTest.java
+++ b/room/runtime/src/test/java/android/arch/persistence/room/BuilderTest.java
@@ -30,6 +30,7 @@
 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;
@@ -110,13 +111,102 @@
     @Test
     public void skipMigration() {
         Context context = mock(Context.class);
+
         TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .fallbackToDestructiveMigration().build();
+                .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();
@@ -163,7 +253,7 @@
         }
 
         @Override
-        public void migrate(SupportSQLiteDatabase database) {
+        public void migrate(@NonNull SupportSQLiteDatabase database) {
         }
     }
 
diff --git a/room/rxjava2/api/current.txt b/room/rxjava2/api/current.txt
new file mode 100644
index 0000000..a7cffd3
--- /dev/null
+++ b/room/rxjava2/api/current.txt
@@ -0,0 +1,14 @@
+package android.arch.persistence.room {
+
+  public class EmptyResultSetException extends java.lang.RuntimeException {
+    ctor public EmptyResultSetException(java.lang.String);
+  }
+
+  public class RxRoom {
+    ctor public RxRoom();
+    method public static io.reactivex.Flowable<java.lang.Object> createFlowable(android.arch.persistence.room.RoomDatabase, java.lang.String...);
+    field public static final java.lang.Object NOTHING;
+  }
+
+}
+
diff --git a/room/testing/api/current.txt b/room/testing/api/current.txt
new file mode 100644
index 0000000..e93487f
--- /dev/null
+++ b/room/testing/api/current.txt
@@ -0,0 +1,13 @@
+package android.arch.persistence.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;
+  }
+
+}
+
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
index 2e93bbe..013dd37 100644
--- 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
@@ -143,9 +143,12 @@
         RoomDatabase.MigrationContainer container = new RoomDatabase.MigrationContainer();
         DatabaseConfiguration configuration = new DatabaseConfiguration(
                 mInstrumentation.getTargetContext(), name, mOpenFactory, container, null, true,
-                true);
+                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);
     }
@@ -186,9 +189,12 @@
         container.addMigrations(migrations);
         DatabaseConfiguration configuration = new DatabaseConfiguration(
                 mInstrumentation.getTargetContext(), name, mOpenFactory, container, null, true,
-                true);
+                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);
     }
diff --git a/samples/Support13Demos/src/main/AndroidManifest.xml b/samples/Support13Demos/src/main/AndroidManifest.xml
index af7fad2..6be99b6 100644
--- a/samples/Support13Demos/src/main/AndroidManifest.xml
+++ b/samples/Support13Demos/src/main/AndroidManifest.xml
@@ -77,13 +77,5 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".view.inputmethod.CommitContentSupport"
-                  android:label="@string/commit_content_support">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="com.example.android.supportv13.SUPPORT13_SAMPLE_CODE" />
-            </intent-filter>
-        </activity>
-
     </application>
 </manifest>
diff --git a/samples/Support13Demos/src/main/java/com/example/android/supportv13/Shakespeare.java b/samples/Support13Demos/src/main/java/com/example/android/supportv13/Shakespeare.java
deleted file mode 100644
index 01f8dc8..0000000
--- a/samples/Support13Demos/src/main/java/com/example/android/supportv13/Shakespeare.java
+++ /dev/null
@@ -1,223 +0,0 @@
-package com.example.android.supportv13;
-
-public final class Shakespeare {
-    /**
-     * Our data, part 1.
-     */
-    public static final String[] TITLES =
-    {
-            "Henry IV (1)",
-            "Henry V",
-            "Henry VIII",
-            "Richard II",
-            "Richard III",
-            "Merchant of Venice",
-            "Othello",
-            "King Lear"
-    };
-
-    /**
-     * Our data, part 2.
-     */
-    public static final String[] DIALOGUE =
-    {
-            "So shaken as we are, so wan with care," +
-            "Find we a time for frighted peace to pant," +
-            "And breathe short-winded accents of new broils" +
-            "To be commenced in strands afar remote." +
-            "No more the thirsty entrance of this soil" +
-            "Shall daub her lips with her own children's blood;" +
-            "Nor more shall trenching war channel her fields," +
-            "Nor bruise her flowerets with the armed hoofs" +
-            "Of hostile paces: those opposed eyes," +
-            "Which, like the meteors of a troubled heaven," +
-            "All of one nature, of one substance bred," +
-            "Did lately meet in the intestine shock" +
-            "And furious close of civil butchery" +
-            "Shall now, in mutual well-beseeming ranks," +
-            "March all one way and be no more opposed" +
-            "Against acquaintance, kindred and allies:" +
-            "The edge of war, like an ill-sheathed knife," +
-            "No more shall cut his master. Therefore, friends," +
-            "As far as to the sepulchre of Christ," +
-            "Whose soldier now, under whose blessed cross" +
-            "We are impressed and engaged to fight," +
-            "Forthwith a power of English shall we levy;" +
-            "Whose arms were moulded in their mothers' womb" +
-            "To chase these pagans in those holy fields" +
-            "Over whose acres walk'd those blessed feet" +
-            "Which fourteen hundred years ago were nail'd" +
-            "For our advantage on the bitter cross." +
-            "But this our purpose now is twelve month old," +
-            "And bootless 'tis to tell you we will go:" +
-            "Therefore we meet not now. Then let me hear" +
-            "Of you, my gentle cousin Westmoreland," +
-            "What yesternight our council did decree" +
-            "In forwarding this dear expedience.",
-
-            "Hear him but reason in divinity," +
-            "And all-admiring with an inward wish" +
-            "You would desire the king were made a prelate:" +
-            "Hear him debate of commonwealth affairs," +
-            "You would say it hath been all in all his study:" +
-            "List his discourse of war, and you shall hear" +
-            "A fearful battle render'd you in music:" +
-            "Turn him to any cause of policy," +
-            "The Gordian knot of it he will unloose," +
-            "Familiar as his garter: that, when he speaks," +
-            "The air, a charter'd libertine, is still," +
-            "And the mute wonder lurketh in men's ears," +
-            "To steal his sweet and honey'd sentences;" +
-            "So that the art and practic part of life" +
-            "Must be the mistress to this theoric:" +
-            "Which is a wonder how his grace should glean it," +
-            "Since his addiction was to courses vain," +
-            "His companies unletter'd, rude and shallow," +
-            "His hours fill'd up with riots, banquets, sports," +
-            "And never noted in him any study," +
-            "Any retirement, any sequestration" +
-            "From open haunts and popularity.",
-
-            "I come no more to make you laugh: things now," +
-            "That bear a weighty and a serious brow," +
-            "Sad, high, and working, full of state and woe," +
-            "Such noble scenes as draw the eye to flow," +
-            "We now present. Those that can pity, here" +
-            "May, if they think it well, let fall a tear;" +
-            "The subject will deserve it. Such as give" +
-            "Their money out of hope they may believe," +
-            "May here find truth too. Those that come to see" +
-            "Only a show or two, and so agree" +
-            "The play may pass, if they be still and willing," +
-            "I'll undertake may see away their shilling" +
-            "Richly in two short hours. Only they" +
-            "That come to hear a merry bawdy play," +
-            "A noise of targets, or to see a fellow" +
-            "In a long motley coat guarded with yellow," +
-            "Will be deceived; for, gentle hearers, know," +
-            "To rank our chosen truth with such a show" +
-            "As fool and fight is, beside forfeiting" +
-            "Our own brains, and the opinion that we bring," +
-            "To make that only true we now intend," +
-            "Will leave us never an understanding friend." +
-            "Therefore, for goodness' sake, and as you are known" +
-            "The first and happiest hearers of the town," +
-            "Be sad, as we would make ye: think ye see" +
-            "The very persons of our noble story" +
-            "As they were living; think you see them great," +
-            "And follow'd with the general throng and sweat" +
-            "Of thousand friends; then in a moment, see" +
-            "How soon this mightiness meets misery:" +
-            "And, if you can be merry then, I'll say" +
-            "A man may weep upon his wedding-day.",
-
-            "First, heaven be the record to my speech!" +
-            "In the devotion of a subject's love," +
-            "Tendering the precious safety of my prince," +
-            "And free from other misbegotten hate," +
-            "Come I appellant to this princely presence." +
-            "Now, Thomas Mowbray, do I turn to thee," +
-            "And mark my greeting well; for what I speak" +
-            "My body shall make good upon this earth," +
-            "Or my divine soul answer it in heaven." +
-            "Thou art a traitor and a miscreant," +
-            "Too good to be so and too bad to live," +
-            "Since the more fair and crystal is the sky," +
-            "The uglier seem the clouds that in it fly." +
-            "Once more, the more to aggravate the note," +
-            "With a foul traitor's name stuff I thy throat;" +
-            "And wish, so please my sovereign, ere I move," +
-            "What my tongue speaks my right drawn sword may prove.",
-
-            "Now is the winter of our discontent" +
-            "Made glorious summer by this sun of York;" +
-            "And all the clouds that lour'd upon our house" +
-            "In the deep bosom of the ocean buried." +
-            "Now are our brows bound with victorious wreaths;" +
-            "Our bruised arms hung up for monuments;" +
-            "Our stern alarums changed to merry meetings," +
-            "Our dreadful marches to delightful measures." +
-            "Grim-visaged war hath smooth'd his wrinkled front;" +
-            "And now, instead of mounting barded steeds" +
-            "To fright the souls of fearful adversaries," +
-            "He capers nimbly in a lady's chamber" +
-            "To the lascivious pleasing of a lute." +
-            "But I, that am not shaped for sportive tricks," +
-            "Nor made to court an amorous looking-glass;" +
-            "I, that am rudely stamp'd, and want love's majesty" +
-            "To strut before a wanton ambling nymph;" +
-            "I, that am curtail'd of this fair proportion," +
-            "Cheated of feature by dissembling nature," +
-            "Deformed, unfinish'd, sent before my time" +
-            "Into this breathing world, scarce half made up," +
-            "And that so lamely and unfashionable" +
-            "That dogs bark at me as I halt by them;" +
-            "Why, I, in this weak piping time of peace," +
-            "Have no delight to pass away the time," +
-            "Unless to spy my shadow in the sun" +
-            "And descant on mine own deformity:" +
-            "And therefore, since I cannot prove a lover," +
-            "To entertain these fair well-spoken days," +
-            "I am determined to prove a villain" +
-            "And hate the idle pleasures of these days." +
-            "Plots have I laid, inductions dangerous," +
-            "By drunken prophecies, libels and dreams," +
-            "To set my brother Clarence and the king" +
-            "In deadly hate the one against the other:" +
-            "And if King Edward be as true and just" +
-            "As I am subtle, false and treacherous," +
-            "This day should Clarence closely be mew'd up," +
-            "About a prophecy, which says that 'G'" +
-            "Of Edward's heirs the murderer shall be." +
-            "Dive, thoughts, down to my soul: here" +
-            "Clarence comes.",
-
-            "To bait fish withal: if it will feed nothing else," +
-            "it will feed my revenge. He hath disgraced me, and" +
-            "hindered me half a million; laughed at my losses," +
-            "mocked at my gains, scorned my nation, thwarted my" +
-            "bargains, cooled my friends, heated mine" +
-            "enemies; and what's his reason? I am a Jew. Hath" +
-            "not a Jew eyes? hath not a Jew hands, organs," +
-            "dimensions, senses, affections, passions? fed with" +
-            "the same food, hurt with the same weapons, subject" +
-            "to the same diseases, healed by the same means," +
-            "warmed and cooled by the same winter and summer, as" +
-            "a Christian is? If you prick us, do we not bleed?" +
-            "if you tickle us, do we not laugh? if you poison" +
-            "us, do we not die? and if you wrong us, shall we not" +
-            "revenge? If we are like you in the rest, we will" +
-            "resemble you in that. If a Jew wrong a Christian," +
-            "what is his humility? Revenge. If a Christian" +
-            "wrong a Jew, what should his sufferance be by" +
-            "Christian example? Why, revenge. The villany you" +
-            "teach me, I will execute, and it shall go hard but I" +
-            "will better the instruction.",
-
-            "Virtue! a fig! 'tis in ourselves that we are thus" +
-            "or thus. Our bodies are our gardens, to the which" +
-            "our wills are gardeners: so that if we will plant" +
-            "nettles, or sow lettuce, set hyssop and weed up" +
-            "thyme, supply it with one gender of herbs, or" +
-            "distract it with many, either to have it sterile" +
-            "with idleness, or manured with industry, why, the" +
-            "power and corrigible authority of this lies in our" +
-            "wills. If the balance of our lives had not one" +
-            "scale of reason to poise another of sensuality, the" +
-            "blood and baseness of our natures would conduct us" +
-            "to most preposterous conclusions: but we have" +
-            "reason to cool our raging motions, our carnal" +
-            "stings, our unbitted lusts, whereof I take this that" +
-            "you call love to be a sect or scion.",
-
-            "Blow, winds, and crack your cheeks! rage! blow!" +
-            "You cataracts and hurricanoes, spout" +
-            "Till you have drench'd our steeples, drown'd the cocks!" +
-            "You sulphurous and thought-executing fires," +
-            "Vaunt-couriers to oak-cleaving thunderbolts," +
-            "Singe my white head! And thou, all-shaking thunder," +
-            "Smite flat the thick rotundity o' the world!" +
-            "Crack nature's moulds, an germens spill at once," +
-            "That make ingrateful man!"
-    };
-}
diff --git a/samples/Support13Demos/src/main/java/com/example/android/supportv13/view/CheckableFrameLayout.java b/samples/Support13Demos/src/main/java/com/example/android/supportv13/view/CheckableFrameLayout.java
deleted file mode 100644
index e2383de..0000000
--- a/samples/Support13Demos/src/main/java/com/example/android/supportv13/view/CheckableFrameLayout.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.example.android.supportv13.view;
-
-import android.content.Context;
-import android.graphics.drawable.ColorDrawable;
-import android.util.AttributeSet;
-import android.widget.Checkable;
-import android.widget.FrameLayout;
-
-public class CheckableFrameLayout extends FrameLayout implements Checkable {
-    private boolean mChecked;
-
-    public CheckableFrameLayout(Context context) {
-        super(context);
-    }
-
-    public CheckableFrameLayout(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    public void setChecked(boolean checked) {
-        mChecked = checked;
-        setBackgroundDrawable(checked ? new ColorDrawable(0xff0000a0) : null);
-    }
-
-    @Override
-    public boolean isChecked() {
-        return mChecked;
-    }
-
-    @Override
-    public void toggle() {
-        setChecked(!mChecked);
-    }
-
-}
diff --git a/samples/Support13Demos/src/main/res/drawable-hdpi/alert_dialog_icon.png b/samples/Support13Demos/src/main/res/drawable-hdpi/alert_dialog_icon.png
deleted file mode 100755
index fe54477..0000000
--- a/samples/Support13Demos/src/main/res/drawable-hdpi/alert_dialog_icon.png
+++ /dev/null
Binary files differ
diff --git a/samples/Support13Demos/src/main/res/drawable-mdpi/alert_dialog_icon.png b/samples/Support13Demos/src/main/res/drawable-mdpi/alert_dialog_icon.png
deleted file mode 100644
index 0a7de04..0000000
--- a/samples/Support13Demos/src/main/res/drawable-mdpi/alert_dialog_icon.png
+++ /dev/null
Binary files differ
diff --git a/samples/Support13Demos/src/main/res/layout/simple_list_item_checkable_1.xml b/samples/Support13Demos/src/main/res/layout/simple_list_item_checkable_1.xml
deleted file mode 100644
index 84017a6..0000000
--- a/samples/Support13Demos/src/main/res/layout/simple_list_item_checkable_1.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<com.example.android.supportv4.view.CheckableFrameLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-    <TextView
-            android:id="@android:id/text1"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:textAppearance="?android:attr/textAppearanceLarge"
-            android:minHeight="?android:attr/listPreferredItemHeight"
-            android:gravity="center_vertical"
-    />
-</com.example.android.supportv4.view.CheckableFrameLayout>
diff --git a/samples/Support13Demos/src/main/res/values/colors.xml b/samples/Support13Demos/src/main/res/values/colors.xml
deleted file mode 100644
index e50c7a0..0000000
--- a/samples/Support13Demos/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2007 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources>
-    <drawable name="red">#7f00</drawable>
-    <drawable name="blue">#770000ff</drawable>
-    <drawable name="green">#7700ff00</drawable>
-    <drawable name="yellow">#77ffff00</drawable>
-</resources>
diff --git a/samples/Support13Demos/src/main/res/values/strings.xml b/samples/Support13Demos/src/main/res/values/strings.xml
index b0950b2..7c621e9 100644
--- a/samples/Support13Demos/src/main/res/values/strings.xml
+++ b/samples/Support13Demos/src/main/res/values/strings.xml
@@ -20,11 +20,6 @@
     <string name="hello_world"><b>Hello, <i>World!</i></b></string>
     <string name="retained">Retained state</string>
 
-    <string name="alert_dialog_two_buttons_title">
-        Lorem ipsum dolor sit aie consectetur adipiscing\nPlloaso mako nuto
-        siwuf cakso dodtos anr koop.
-    </string>
-
     <string name="fragment_nesting_pager_support">Fragment/Nesting Pager</string>
 
     <string name="fragment_nesting_state_pager_support">Fragment/Nesting State Pager</string>
@@ -34,9 +29,4 @@
     <string name="last">Last</string>
 
     <string name="fragment_state_pager_support">Fragment/State Pager</string>
-
-    <string name="action_bar_tabs_pager">Fragment/Action Bar Tabs Pager</string>
-
-    <string name="commit_content_support">View/Input Method/Commit Content</string>
-
 </resources>
diff --git a/samples/Support4Demos/src/main/AndroidManifest.xml b/samples/Support4Demos/src/main/AndroidManifest.xml
index 812d4e8..66f34dc 100644
--- a/samples/Support4Demos/src/main/AndroidManifest.xml
+++ b/samples/Support4Demos/src/main/AndroidManifest.xml
@@ -424,6 +424,14 @@
         </provider>
 <!-- END_INCLUDE(file_provider_declaration) -->
 
+        <activity android:name=".view.inputmethod.CommitContentSupport"
+                  android:label="@string/commit_content_support">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.example.android.supportv13.SUPPORT13_SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
         <!-- MediaBrowserCompat Sample -->
         <activity android:name=".media.MediaBrowserSupport"
             android:label="@string/media_browser_support">
diff --git a/samples/Support13Demos/src/main/java/com/example/android/supportv13/view/inputmethod/CommitContentSupport.java b/samples/Support4Demos/src/main/java/com/example/android/supportv4/view/inputmethod/CommitContentSupport.java
similarity index 93%
rename from samples/Support13Demos/src/main/java/com/example/android/supportv13/view/inputmethod/CommitContentSupport.java
rename to samples/Support4Demos/src/main/java/com/example/android/supportv4/view/inputmethod/CommitContentSupport.java
index 78d64cd..335fa76 100644
--- a/samples/Support13Demos/src/main/java/com/example/android/supportv13/view/inputmethod/CommitContentSupport.java
+++ b/samples/Support4Demos/src/main/java/com/example/android/supportv4/view/inputmethod/CommitContentSupport.java
@@ -11,22 +11,19 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package com.example.android.supportv13.view.inputmethod;
-
-import com.example.android.supportv13.R;
-
-import android.support.v13.view.inputmethod.EditorInfoCompat;
-import android.support.v13.view.inputmethod.InputConnectionCompat;
-import android.support.v13.view.inputmethod.InputContentInfoCompat;
+package com.example.android.supportv4.view.inputmethod;
 
 import android.app.Activity;
 import android.graphics.Color;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcelable;
+import android.support.v13.view.inputmethod.EditorInfoCompat;
+import android.support.v13.view.inputmethod.InputConnectionCompat;
+import android.support.v13.view.inputmethod.InputContentInfoCompat;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.inputmethod.EditorInfo;
@@ -36,14 +33,18 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import com.example.android.supportv4.R;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 
+/**
+ * Demo activity for using {@link InputConnectionCompat}.
+ */
 public class CommitContentSupport extends Activity {
     private static final String INPUT_CONTENT_INFO_KEY = "COMMIT_CONTENT_INPUT_CONTENT_INFO";
     private static final String COMMIT_CONTENT_FLAGS_KEY = "COMMIT_CONTENT_FLAGS";
-
-    private static String TAG = "CommitContentSupport";
+    private static final String TAG = "CommitContentSupport";
 
     private WebView mWebView;
     private TextView mLabel;
@@ -108,7 +109,7 @@
     }
 
     private boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags,
-            Bundle opts, String[] contentMimeTypes) {
+            String[] contentMimeTypes) {
         // Clear the temporary permission (if any).  See below about why we do this here.
         try {
             if (mCurrentInputContentInfo != null) {
@@ -211,14 +212,9 @@
                 final InputConnection ic = super.onCreateInputConnection(editorInfo);
                 EditorInfoCompat.setContentMimeTypes(editorInfo, mimeTypes);
                 final InputConnectionCompat.OnCommitContentListener callback =
-                        new InputConnectionCompat.OnCommitContentListener() {
-                            @Override
-                            public boolean onCommitContent(InputContentInfoCompat inputContentInfo,
-                                    int flags, Bundle opts) {
-                                return CommitContentSupport.this.onCommitContent(
-                                        inputContentInfo, flags, opts, mimeTypes);
-                            }
-                        };
+                        (inputContentInfo, flags, opts) ->
+                                CommitContentSupport.this.onCommitContent(
+                                        inputContentInfo, flags, mimeTypes);
                 return InputConnectionCompat.createWrapper(ic, editorInfo, callback);
             }
         };
diff --git a/samples/Support13Demos/src/main/res/layout/commit_content.xml b/samples/Support4Demos/src/main/res/layout/commit_content.xml
similarity index 100%
rename from samples/Support13Demos/src/main/res/layout/commit_content.xml
rename to samples/Support4Demos/src/main/res/layout/commit_content.xml
diff --git a/samples/Support4Demos/src/main/res/values/strings.xml b/samples/Support4Demos/src/main/res/values/strings.xml
index 83404e8..86adb94 100644
--- a/samples/Support4Demos/src/main/res/values/strings.xml
+++ b/samples/Support4Demos/src/main/res/values/strings.xml
@@ -241,4 +241,6 @@
     <string name="drawable_compat_no_tint">Not tint</string>
     <string name="drawable_compat_color_tint">Color tint</string>
     <string name="drawable_compat_color_list_tint">Color state list</string>
+
+    <string name="commit_content_support">View/Input Method/Commit Content</string>
 </resources>
diff --git a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/ListItemActivity.java b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/ListItemActivity.java
index 043a836..6594928 100644
--- a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/ListItemActivity.java
+++ b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/ListItemActivity.java
@@ -57,6 +57,7 @@
                 new SampleProvider(this), ListItemAdapter.BackgroundStyle.PANEL);
         mPagedListView.setAdapter(adapter);
         mPagedListView.setMaxPages(PagedListView.UNLIMITED_PAGES);
+        mPagedListView.setDividerVisibilityManager(adapter);
     }
 
     private static class SampleProvider extends ListItemProvider {
@@ -101,6 +102,12 @@
                     .build());
 
             mItems.add(new ListItem.Builder(mContext)
+                    .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+                    .withTitle("single line without a list divider")
+                    .withDividerHidden()
+                    .build());
+
+            mItems.add(new ListItem.Builder(mContext)
                     .withOnClickListener(mOnClickListener)
                     .withPrimaryActionEmptyIcon()
                     .withTitle("clickable single line with empty icon and end icon no divider")
@@ -114,6 +121,13 @@
                     .build());
 
             mItems.add(new ListItem.Builder(mContext)
+                    .withTitle("Subtitle-like line without a list divider")
+                    .withDividerHidden()
+                    .withViewBinder(viewHolder ->
+                            viewHolder.getTitle().setTextAppearance(R.style.CarListSubtitle))
+                    .build());
+
+            mItems.add(new ListItem.Builder(mContext)
                     .withPrimaryActionNoIcon()
                     .withTitle("single line with two actions and no divider")
                     .withActions("action 1", false,
@@ -213,6 +227,14 @@
                     .withBody("Only body - no title. " + mContext.getString(R.string.long_text))
                     .build());
 
+            mItems.add(new ListItem.Builder(mContext)
+                    .withTitle("Switch - initially unchecked")
+                    .withSwitch(false, true, (button, isChecked) -> {
+                        Toast.makeText(mContext,
+                                isChecked ? "checked" : "unchecked", Toast.LENGTH_SHORT).show();
+                    })
+                    .build());
+
             mListProvider = new ListItemProvider.ListProvider(mItems);
         }
 
diff --git a/samples/SupportCarDemos/src/main/res/values/styles.xml b/samples/SupportCarDemos/src/main/res/values/styles.xml
new file mode 100644
index 0000000..80074d8
--- /dev/null
+++ b/samples/SupportCarDemos/src/main/res/values/styles.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="CarListSubtitle" parent="CarTitle">
+        <item name="android:textColor">@color/car_highlight</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/samples/SupportCarDemos/src/main/res/values/themes.xml b/samples/SupportCarDemos/src/main/res/values/themes.xml
index 4b82ecd..068da60 100644
--- a/samples/SupportCarDemos/src/main/res/values/themes.xml
+++ b/samples/SupportCarDemos/src/main/res/values/themes.xml
@@ -18,14 +18,15 @@
     <!-- The main theme for all activities within the Car Demo. -->
     <style name="CarTheme" parent="android:Theme.Material.Light">
         <item name="android:windowBackground">@color/car_grey_50</item>
+        <item name="android:windowLightStatusBar">true</item>
         <item name="android:colorAccent">@color/car_yellow_500</item>
         <item name="android:colorPrimary">@color/car_highlight</item>
         <item name="android:colorPrimaryDark">@color/car_grey_300</item>
-        <item name="android:buttonStyle">@style/CarButton</item>
-        <item name="android:borderlessButtonStyle">@style/CarButton.Borderless</item>
+        <item name="android:buttonStyle">@style/Widget.Car.Button</item>
+        <item name="android:borderlessButtonStyle">@style/Widget.Car.Button.Borderless.Colored</item>
         <item name="android:progressBarStyleHorizontal">
-            @style/CarProgressBar.Horizontal
+            @style/Widget.Car.ProgressBar.Horizontal
         </item>
-        <item name="android:windowLightStatusBar">true</item>
+        <item name="android:colorControlActivated">@color/car_accent</item>
     </style>
 </resources>
diff --git a/tv-provider/src/main/java/android/support/media/tv/BasePreviewProgram.java b/tv-provider/src/main/java/android/support/media/tv/BasePreviewProgram.java
index eeaa5ea..816b1a1 100644
--- a/tv-provider/src/main/java/android/support/media/tv/BasePreviewProgram.java
+++ b/tv-provider/src/main/java/android/support/media/tv/BasePreviewProgram.java
@@ -15,6 +15,7 @@
  */
 package android.support.media.tv;
 
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
 import android.content.ContentValues;
@@ -39,6 +40,7 @@
  *
  * @hide
  */
+@RestrictTo(LIBRARY)
 public abstract class BasePreviewProgram extends BaseProgram {
     /**
      * @hide
diff --git a/tv-provider/src/main/java/android/support/media/tv/BaseProgram.java b/tv-provider/src/main/java/android/support/media/tv/BaseProgram.java
index 23b5cf9..4c7882d 100644
--- a/tv-provider/src/main/java/android/support/media/tv/BaseProgram.java
+++ b/tv-provider/src/main/java/android/support/media/tv/BaseProgram.java
@@ -15,6 +15,7 @@
  */
 package android.support.media.tv;
 
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
 import android.content.ContentValues;
@@ -37,6 +38,7 @@
  * {@link TvContractCompat}.
  * @hide
  */
+@RestrictTo(LIBRARY)
 public abstract class BaseProgram {
     /**
      * @hide
diff --git a/tv-provider/src/main/java/android/support/media/tv/TvContractCompat.java b/tv-provider/src/main/java/android/support/media/tv/TvContractCompat.java
index de4fd04..bd03bf1 100644
--- a/tv-provider/src/main/java/android/support/media/tv/TvContractCompat.java
+++ b/tv-provider/src/main/java/android/support/media/tv/TvContractCompat.java
@@ -2422,6 +2422,7 @@
         /** Canonical genres for TV programs. */
         public static final class Genres {
             /** @hide */
+            @RestrictTo(RestrictTo.Scope.LIBRARY)
             @StringDef({
                     FAMILY_KIDS,
                     SPORTS,
diff --git a/v13/api/27.0.0.ignore b/v13/api/27.0.0.ignore
new file mode 100644
index 0000000..c578d34
--- /dev/null
+++ b/v13/api/27.0.0.ignore
@@ -0,0 +1,5 @@
+05913a0
+0d6069f
+ddf372b
+e6a9e7f
+454dbc1
diff --git a/v13/api/current.txt b/v13/api/current.txt
index 1d9fdbf..fd72d5c 100644
--- a/v13/api/current.txt
+++ b/v13/api/current.txt
@@ -1,8 +1,7 @@
 package android.support.v13.app {
 
-  public class ActivityCompat extends android.support.v4.app.ActivityCompat {
-    ctor protected ActivityCompat();
-    method public static android.support.v13.view.DragAndDropPermissionsCompat requestDragAndDropPermissions(android.app.Activity, android.view.DragEvent);
+  public deprecated class ActivityCompat extends android.support.v4.app.ActivityCompat {
+    ctor protected deprecated ActivityCompat();
   }
 
   public deprecated class FragmentCompat {
@@ -68,59 +67,8 @@
 
 package android.support.v13.view {
 
-  public final class DragAndDropPermissionsCompat {
-    method public void release();
-  }
-
-  public class DragStartHelper {
-    ctor public DragStartHelper(android.view.View, android.support.v13.view.DragStartHelper.OnDragStartListener);
-    method public void attach();
-    method public void detach();
-    method public void getTouchPosition(android.graphics.Point);
-    method public boolean onLongClick(android.view.View);
-    method public boolean onTouch(android.view.View, android.view.MotionEvent);
-  }
-
-  public static abstract interface DragStartHelper.OnDragStartListener {
-    method public abstract boolean onDragStart(android.view.View, android.support.v13.view.DragStartHelper);
-  }
-
   public deprecated class ViewCompat extends android.support.v4.view.ViewCompat {
   }
 
 }
 
-package android.support.v13.view.inputmethod {
-
-  public final class EditorInfoCompat {
-    ctor public EditorInfoCompat();
-    method public static java.lang.String[] getContentMimeTypes(android.view.inputmethod.EditorInfo);
-    method public static void setContentMimeTypes(android.view.inputmethod.EditorInfo, java.lang.String[]);
-    field public static final int IME_FLAG_FORCE_ASCII = -2147483648; // 0x80000000
-    field public static final int IME_FLAG_NO_PERSONALIZED_LEARNING = 16777216; // 0x1000000
-  }
-
-  public final class InputConnectionCompat {
-    ctor public InputConnectionCompat();
-    method public static boolean commitContent(android.view.inputmethod.InputConnection, android.view.inputmethod.EditorInfo, android.support.v13.view.inputmethod.InputContentInfoCompat, int, android.os.Bundle);
-    method public static android.view.inputmethod.InputConnection createWrapper(android.view.inputmethod.InputConnection, android.view.inputmethod.EditorInfo, android.support.v13.view.inputmethod.InputConnectionCompat.OnCommitContentListener);
-    field public static int INPUT_CONTENT_GRANT_READ_URI_PERMISSION;
-  }
-
-  public static abstract interface InputConnectionCompat.OnCommitContentListener {
-    method public abstract boolean onCommitContent(android.support.v13.view.inputmethod.InputContentInfoCompat, int, android.os.Bundle);
-  }
-
-  public final class InputContentInfoCompat {
-    ctor public InputContentInfoCompat(android.net.Uri, android.content.ClipDescription, android.net.Uri);
-    method public android.net.Uri getContentUri();
-    method public android.content.ClipDescription getDescription();
-    method public android.net.Uri getLinkUri();
-    method public void releasePermission();
-    method public void requestPermission();
-    method public java.lang.Object unwrap();
-    method public static android.support.v13.view.inputmethod.InputContentInfoCompat wrap(java.lang.Object);
-  }
-
-}
-
diff --git a/v13/build.gradle b/v13/build.gradle
index 88e0bc0..9965d1d 100644
--- a/v13/build.gradle
+++ b/v13/build.gradle
@@ -1,4 +1,3 @@
-import static android.support.dependencies.DependenciesKt.*
 import android.support.LibraryGroups
 import android.support.LibraryVersions
 
@@ -9,11 +8,6 @@
 dependencies {
     api(project(":support-annotations"))
     api(project(":support-v4"))
-
-    androidTestImplementation(TEST_RUNNER)
-    androidTestImplementation(ESPRESSO_CORE)
-    androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
-    androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
 }
 
 android {
diff --git a/v13/java/android/support/v13/app/ActivityCompat.java b/v13/java/android/support/v13/app/ActivityCompat.java
index b0c3c30..7c1546a 100644
--- a/v13/java/android/support/v13/app/ActivityCompat.java
+++ b/v13/java/android/support/v13/app/ActivityCompat.java
@@ -16,32 +16,22 @@
 
 package android.support.v13.app;
 
-import android.app.Activity;
-import android.support.v13.view.DragAndDropPermissionsCompat;
-import android.view.DragEvent;
-
 /**
  * Helper for accessing features in {@link android.app.Activity} in a backwards compatible fashion.
+ *
+ * @deprecated Use {@link android.support.v4.app.ActivityCompat
+ * android.support.v4.app.ActivityCompat}.
  */
+@Deprecated
 public class ActivityCompat extends android.support.v4.app.ActivityCompat {
-
-    /**
-     * Create {@link DragAndDropPermissionsCompat} object bound to this activity and controlling
-     * the access permissions for content URIs associated with the {@link android.view.DragEvent}.
-     * @param dragEvent Drag event to request permission for
-     * @return The {@link DragAndDropPermissionsCompat} object used to control access to the content
-     * URIs. {@code null} if no content URIs are associated with the event or if permissions could
-     * not be granted.
-     */
-    public static DragAndDropPermissionsCompat requestDragAndDropPermissions(Activity activity,
-                                                                             DragEvent dragEvent) {
-        return DragAndDropPermissionsCompat.request(activity, dragEvent);
-    }
-
     /**
      * This class should not be instantiated, but the constructor must be
      * visible for the class to be extended.
+     *
+     * @deprecated Use {@link android.support.v4.app.ActivityCompat
+     * android.support.v4.app.ActivityCompat}.
      */
+    @Deprecated
     protected ActivityCompat() {
         // Not publicly instantiable, but may be extended.
     }
diff --git a/v13/tests/AndroidManifest.xml b/v13/tests/AndroidManifest.xml
deleted file mode 100644
index 3097555..0000000
--- a/v13/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-   Copyright (C) 2016 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-        http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-  -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.support.v13.test">
-    <uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
-
-    <application>
-        <activity android:name="android.support.v13.app.FragmentCompatTestActivity" />
-        <activity android:name="android.support.v13.view.DragStartHelperTestActivity"/>
-    </application>
-
-</manifest>
diff --git a/v13/tests/NO_DOCS b/v13/tests/NO_DOCS
deleted file mode 100644
index 092a39c..0000000
--- a/v13/tests/NO_DOCS
+++ /dev/null
@@ -1,17 +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.
-
-Having this file, named NO_DOCS, in a directory will prevent
-Android javadocs from being generated for java files under
-the directory. This is especially useful for test projects.
diff --git a/v13/tests/java/android/support/v13/app/FragmentCompatTest.java b/v13/tests/java/android/support/v13/app/FragmentCompatTest.java
deleted file mode 100644
index 34b01f1..0000000
--- a/v13/tests/java/android/support/v13/app/FragmentCompatTest.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.support.v13.app;
-
-import static org.mockito.AdditionalMatchers.aryEq;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.same;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-
-import android.Manifest;
-import android.app.Activity;
-import android.app.Fragment;
-import android.os.Build;
-import android.support.annotation.NonNull;
-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.v13.app.FragmentCompat.PermissionCompatDelegate;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.HONEYCOMB)
-public class FragmentCompatTest {
-    @Rule
-    public ActivityTestRule<FragmentCompatTestActivity> mActivityRule =
-            new ActivityTestRule<>(FragmentCompatTestActivity.class);
-
-    private Activity mActivity;
-    private TestFragment mFragment;
-
-    @Before
-    public void setup() throws Throwable {
-        mActivity = mActivityRule.getActivity();
-        mFragment = attachTestFragment();
-    }
-
-    @SmallTest
-    @Test
-    public void testFragmentCompatDelegate() {
-        FragmentCompat.PermissionCompatDelegate delegate = mock(PermissionCompatDelegate.class);
-
-        // First test setting the delegate
-        FragmentCompat.setPermissionCompatDelegate(delegate);
-
-        FragmentCompat.requestPermissions(mFragment, new String[]{
-                Manifest.permission.ACCESS_FINE_LOCATION}, 42);
-        verify(delegate).requestPermissions(same(mFragment),
-                aryEq(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}), eq(42));
-
-        // Now test clearing the delegate
-        FragmentCompat.setPermissionCompatDelegate(null);
-
-        FragmentCompat.requestPermissions(mFragment, new String[]{
-                Manifest.permission.ACCESS_FINE_LOCATION}, 42);
-
-        verifyNoMoreInteractions(delegate);
-    }
-
-    private TestFragment attachTestFragment() throws Throwable {
-        final TestFragment fragment = new TestFragment();
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.getFragmentManager().beginTransaction()
-                        .add(fragment, null)
-                        .addToBackStack(null)
-                        .commitAllowingStateLoss();
-                mActivity.getFragmentManager().executePendingTransactions();
-            }
-        });
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        return fragment;
-    }
-
-    /**
-     * Empty class to satisfy java class dependency.
-     */
-    public static class TestFragment extends Fragment implements
-            FragmentCompat.OnRequestPermissionsResultCallback {
-        @Override
-        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
-                @NonNull int[] grantResults) {}
-    }
-}
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatButton.java b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatButton.java
index 478d42d..fa35e21 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatButton.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatButton.java
@@ -32,6 +32,7 @@
 import android.support.v4.widget.TextViewCompat;
 import android.support.v7.appcompat.R;
 import android.util.AttributeSet;
+import android.view.ActionMode;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.Button;
@@ -358,4 +359,13 @@
             mTextHelper.setAllCaps(allCaps);
         }
     }
+
+    /**
+     * See
+     * {@link TextViewCompat#setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)}
+     */
+    @Override
+    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
+        TextViewCompat.setCustomSelectionActionModeCallback(this, actionModeCallback);
+    }
 }
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatCheckedTextView.java b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatCheckedTextView.java
index dca409c..415453d 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatCheckedTextView.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatCheckedTextView.java
@@ -18,8 +18,10 @@
 
 import android.content.Context;
 import android.support.annotation.DrawableRes;
+import android.support.v4.widget.TextViewCompat;
 import android.support.v7.content.res.AppCompatResources;
 import android.util.AttributeSet;
+import android.view.ActionMode;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.widget.CheckedTextView;
@@ -87,4 +89,13 @@
         return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs),
                 outAttrs, this);
     }
+
+    /**
+     * See
+     * {@link TextViewCompat#setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)}
+     */
+    @Override
+    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
+        TextViewCompat.setCustomSelectionActionModeCallback(this, actionModeCallback);
+    }
 }
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatEditText.java b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatEditText.java
index 6831fcb..c4f8c20 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatEditText.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatEditText.java
@@ -26,8 +26,10 @@
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
 import android.support.v4.view.TintableBackgroundView;
+import android.support.v4.widget.TextViewCompat;
 import android.support.v7.appcompat.R;
 import android.util.AttributeSet;
+import android.view.ActionMode;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.widget.EditText;
@@ -167,4 +169,13 @@
         return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs),
                 outAttrs, this);
     }
+
+    /**
+     * See
+     * {@link TextViewCompat#setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)}
+     */
+    @Override
+    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
+        TextViewCompat.setCustomSelectionActionModeCallback(this, actionModeCallback);
+    }
 }
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatTextView.java b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatTextView.java
index d813277..40ca778 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatTextView.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatTextView.java
@@ -31,6 +31,7 @@
 import android.support.v4.widget.TextViewCompat;
 import android.support.v7.appcompat.R;
 import android.util.AttributeSet;
+import android.view.ActionMode;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.widget.TextView;
@@ -369,4 +370,13 @@
         return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs),
                 outAttrs, this);
     }
+
+    /**
+     * See
+     * {@link TextViewCompat#setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)}
+     */
+    @Override
+    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
+        TextViewCompat.setCustomSelectionActionModeCallback(this, actionModeCallback);
+    }
 }
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/ButtonBarLayout.java b/v7/appcompat/src/main/java/android/support/v7/widget/ButtonBarLayout.java
index b47a568..f4bbc6c 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/ButtonBarLayout.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/ButtonBarLayout.java
@@ -35,9 +35,6 @@
  */
 @RestrictTo(LIBRARY_GROUP)
 public class ButtonBarLayout extends LinearLayout {
-    /** Minimum screen height required for button stacking. */
-    private static final int ALLOW_STACKING_MIN_HEIGHT_DP = 320;
-
     /** Amount of the second button to "peek" above the fold when stacked. */
     private static final int PEEK_BUTTON_DP = 16;
 
@@ -50,11 +47,8 @@
 
     public ButtonBarLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
-        final boolean allowStackingDefault =
-                getResources().getConfiguration().screenHeightDp >= ALLOW_STACKING_MIN_HEIGHT_DP;
         final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout);
-        mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking,
-                allowStackingDefault);
+        mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, true);
         ta.recycle();
     }
 
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/ContentFrameLayout.java b/v7/appcompat/src/main/java/android/support/v7/widget/ContentFrameLayout.java
index 1100280..f777901 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/ContentFrameLayout.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/ContentFrameLayout.java
@@ -16,6 +16,7 @@
 
 package android.support.v7.widget;
 
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 import static android.view.View.MeasureSpec.AT_MOST;
 import static android.view.View.MeasureSpec.EXACTLY;
@@ -33,6 +34,7 @@
 /**
  * @hide
  */
+@RestrictTo(LIBRARY)
 public class ContentFrameLayout extends FrameLayout {
 
     public interface OnAttachListener {
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/DialogTitle.java b/v7/appcompat/src/main/java/android/support/v7/widget/DialogTitle.java
index 313d748..99e5924 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/DialogTitle.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/DialogTitle.java
@@ -21,10 +21,12 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.support.annotation.RestrictTo;
+import android.support.v4.widget.TextViewCompat;
 import android.support.v7.appcompat.R;
 import android.text.Layout;
 import android.util.AttributeSet;
 import android.util.TypedValue;
+import android.view.ActionMode;
 import android.widget.TextView;
 
 /**
@@ -78,4 +80,13 @@
             }
         }
     }
+
+    /**
+     * See
+     * {@link TextViewCompat#setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)}
+     */
+    @Override
+    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
+        TextViewCompat.setCustomSelectionActionModeCallback(this, actionModeCallback);
+    }
 }
\ No newline at end of file
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/LinearLayoutCompat.java b/v7/appcompat/src/main/java/android/support/v7/widget/LinearLayoutCompat.java
index f071ae4..ef68896 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/LinearLayoutCompat.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/LinearLayoutCompat.java
@@ -16,6 +16,7 @@
 
 package android.support.v7.widget;
 
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
 import android.content.Context;
@@ -559,6 +560,7 @@
      * @return true if there should be a divider before the child at childIndex
      * @hide Pending API consideration. Currently only used internally by the system.
      */
+    @RestrictTo(LIBRARY)
     protected boolean hasDividerBeforeChildAt(int childIndex) {
         if (childIndex == 0) {
             return (mShowDividers & SHOW_DIVIDER_BEGINNING) != 0;
diff --git a/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTest.java b/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTest.java
index 7288968..7354037 100644
--- a/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTest.java
+++ b/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTest.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.LargeTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
@@ -33,6 +34,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public class MediaRouteChooserDialogTest {
 
diff --git a/v7/preference/lint-baseline.xml b/v7/preference/lint-baseline.xml
index 2b0d510..1f3ddab 100644
--- a/v7/preference/lint-baseline.xml
+++ b/v7/preference/lint-baseline.xml
@@ -1,5 +1,49 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 3.0.0-alpha9">
+<issues format="4" by="lint 3.0.0">
+
+    <issue
+        id="NewApi"
+        message="`@android:id/icon_frame` requires API level 24 (current min is 14)"
+        errorLine1="        android:id=&quot;@android:id/icon_frame&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="res/layout-v7/expand_button.xml"
+            line="31"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="`?android:attr/textAppearanceListItemSecondary` requires API level 21 (current min is 14)"
+        errorLine1="            android:textAppearance=&quot;?android:attr/textAppearanceListItemSecondary&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="res/layout-v7/expand_button.xml"
+            line="69"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Using theme references in XML drawables requires API level 21 (current min is 14)"
+        errorLine1="        android:tint=&quot;?android:attr/colorAccent&quot;>"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="res/drawable/ic_arrow_down_24dp.xml"
+            line="22"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="`@android:id/list_container` requires API level 24 (current min is 14)"
+        errorLine1="        android:id=&quot;@android:id/list_container&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="res/layout/preference_list_fragment.xml"
+            line="24"
+            column="9"/>
+    </issue>
 
     <issue
         id="Suspicious0dp"
diff --git a/v7/preference/src/main/java/android/support/v7/preference/Preference.java b/v7/preference/src/main/java/android/support/v7/preference/Preference.java
index fa8461d..88262cd 100644
--- a/v7/preference/src/main/java/android/support/v7/preference/Preference.java
+++ b/v7/preference/src/main/java/android/support/v7/preference/Preference.java
@@ -16,6 +16,7 @@
 
 package android.support.v7.preference;
 
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
 import android.content.Context;
@@ -1297,6 +1298,7 @@
      * preference was removed, modified, and re-added to a {@link PreferenceGroup}
      * @hide
      */
+    @RestrictTo(LIBRARY)
     public final boolean wasDetached() {
         return mWasDetached;
     }
@@ -1305,6 +1307,7 @@
      * Clears the {@link #wasDetached()} status
      * @hide
      */
+    @RestrictTo(LIBRARY)
     public final void clearWasDetached() {
         mWasDetached = false;
     }
diff --git a/v7/recyclerview/src/main/java/android/support/v7/widget/StaggeredGridLayoutManager.java b/v7/recyclerview/src/main/java/android/support/v7/widget/StaggeredGridLayoutManager.java
index 55fb14e..4e560b4 100644
--- a/v7/recyclerview/src/main/java/android/support/v7/widget/StaggeredGridLayoutManager.java
+++ b/v7/recyclerview/src/main/java/android/support/v7/widget/StaggeredGridLayoutManager.java
@@ -16,6 +16,7 @@
 
 package android.support.v7.widget;
 
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_HEAD;
 import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_TAIL;
@@ -2069,6 +2070,7 @@
 
     /** @hide */
     @Override
+    @RestrictTo(LIBRARY)
     public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state,
             LayoutPrefetchRegistry layoutPrefetchRegistry) {
         /* This method uses the simplifying assumption that the next N items (where N = span count)
diff --git a/viewpager2/src/androidTest/java/androidx/widget/viewpager2/tests/ViewPager2Tests.java b/viewpager2/src/androidTest/java/androidx/widget/viewpager2/tests/ViewPager2Tests.java
index 7f5432b..45b42aa 100644
--- a/viewpager2/src/androidTest/java/androidx/widget/viewpager2/tests/ViewPager2Tests.java
+++ b/viewpager2/src/androidTest/java/androidx/widget/viewpager2/tests/ViewPager2Tests.java
@@ -23,6 +23,7 @@
 import static android.support.test.espresso.matcher.ViewMatchers.withText;
 import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
 import static android.support.v7.widget.RecyclerView.SCROLL_STATE_SETTLING;
+import static android.view.View.OVER_SCROLL_NEVER;
 
 import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.equalTo;
@@ -30,6 +31,7 @@
 
 import android.content.Context;
 import android.graphics.Color;
+import android.os.Build;
 import android.support.annotation.NonNull;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.espresso.IdlingRegistry;
@@ -105,6 +107,11 @@
     public void rendersAndHandlesSwiping() throws Throwable {
         final int pageCount = sColors.length;
 
+        if (Build.VERSION.SDK_INT < 16) { // TODO(b/71500143): remove temporary workaround
+            RecyclerView mRecyclerView = (RecyclerView) mViewPager.getChildAt(0);
+            mRecyclerView.setOverScrollMode(OVER_SCROLL_NEVER);
+        }
+
         onView(withId(mViewPager.getId())).check(matches(isDisplayed()));
         mActivityTestRule.runOnUiThread(new Runnable() {
             @Override