Merge "Use TestLifecycleOwner in all tests" into androidx-master-dev
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index f9f61ec..b8c81ee 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -12,6 +12,7 @@
   </component>
   <component name="VcsDirectoryMappings">
     <mapping directory="$PROJECT_DIR$/../../external/doclava" vcs="Git" />
+    <mapping directory="$PROJECT_DIR$/../../external/icing" vcs="Git" />
     <mapping directory="$PROJECT_DIR$/../../external/webview_support_interfaces" vcs="Git" />
     <mapping directory="$PROJECT_DIR$" vcs="Git" />
   </component>
diff --git a/animation/animation/src/main/java/androidx/animation/AnimatorSet.java b/animation/animation/src/main/java/androidx/animation/AnimatorSet.java
index 327c99f..2e2601c 100644
--- a/animation/animation/src/main/java/androidx/animation/AnimatorSet.java
+++ b/animation/animation/src/main/java/androidx/animation/AnimatorSet.java
@@ -1822,9 +1822,10 @@
         void setPlayTime(long playTime, boolean inReverse) {
             // Clamp the play time
             if (getTotalDuration() != DURATION_INFINITE) {
-                mPlayTime = Math.min(playTime, getTotalDuration() - mStartDelay);
+                mPlayTime = Math.max(0, Math.min(playTime, getTotalDuration() - mStartDelay));
+            } else {
+                mPlayTime = Math.max(0, playTime);
             }
-            mPlayTime = Math.max(0, mPlayTime);
             mSeekingInReverse = inReverse;
         }
 
diff --git a/animation/integration-tests/testapp/src/androidTest/java/androidx/animation/AnimatorSetTest.java b/animation/integration-tests/testapp/src/androidTest/java/androidx/animation/AnimatorSetTest.java
index 3c1ea69..a9b3142 100644
--- a/animation/integration-tests/testapp/src/androidTest/java/androidx/animation/AnimatorSetTest.java
+++ b/animation/integration-tests/testapp/src/androidTest/java/androidx/animation/AnimatorSetTest.java
@@ -548,11 +548,13 @@
         set.play(a1).before(a3);
 
         set.setCurrentPlayTime(50);
+        assertEquals(50L, set.getCurrentPlayTime());
         assertEquals(50f, (Float) a1.getAnimatedValue(), EPSILON);
         assertEquals(100f, (Float) a2.getAnimatedValue(), EPSILON);
         assertEquals(100f, (Float) a3.getAnimatedValue(), EPSILON);
 
         set.setCurrentPlayTime(100);
+        assertEquals(100L, set.getCurrentPlayTime());
         assertEquals(100f, (Float) a1.getAnimatedValue(), EPSILON);
         assertEquals(100f, (Float) a2.getAnimatedValue(), EPSILON);
         assertEquals(100f, (Float) a3.getAnimatedValue(), EPSILON);
@@ -560,6 +562,7 @@
         // Seek to the 1st iteration of the infinite repeat animators, and they should have the
         // same value.
         set.setCurrentPlayTime(180);
+        assertEquals(180L, set.getCurrentPlayTime());
         assertEquals(100f, (Float) a1.getAnimatedValue(), EPSILON);
         assertEquals(180f, (Float) a2.getAnimatedValue(), EPSILON);
         assertEquals(180f, (Float) a3.getAnimatedValue(), EPSILON);
@@ -567,6 +570,7 @@
         // Seek to the 2nd iteration of the infinite repeat animators, and they should have
         // different values as they have different repeat mode.
         set.setCurrentPlayTime(280);
+        assertEquals(280L, set.getCurrentPlayTime());
         assertEquals(100f, (Float) a1.getAnimatedValue(), EPSILON);
         assertEquals(180f, (Float) a2.getAnimatedValue(), EPSILON);
         assertEquals(120f, (Float) a3.getAnimatedValue(), EPSILON);
diff --git a/appsearch/appsearch/build.gradle b/appsearch/appsearch/build.gradle
index 46bf1ae..c4eddcb 100644
--- a/appsearch/appsearch/build.gradle
+++ b/appsearch/appsearch/build.gradle
@@ -14,24 +14,33 @@
  * limitations under the License.
  */
 
-import static androidx.build.dependencies.DependenciesKt.*
+
 import androidx.build.LibraryGroups
-import androidx.build.LibraryVersions
 import androidx.build.Publish
 
+import static androidx.build.dependencies.DependenciesKt.*
+
 plugins {
-    id("AndroidXPlugin")
-    id("com.android.library")
+    id('AndroidXPlugin')
+    id('com.android.library')
 }
 
 dependencies {
-    /* None */
+    api('androidx.annotation:annotation:1.1.0')
+
+    implementation project(':icing')
+    implementation('androidx.collection:collection:1.1.0')
+
+    androidTestImplementation(ANDROIDX_TEST_CORE)
+    androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+    androidTestImplementation(ANDROIDX_TEST_RULES)
+    androidTestImplementation(TRUTH)
 }
 
 androidx {
-    name = "AndroidX AppSearch"
+    name = 'AndroidX AppSearch'
     publish = Publish.SNAPSHOT_AND_RELEASE
     mavenGroup = LibraryGroups.APPSEARCH
-    inceptionYear = "2019"
-    description = "AndroidX AppSearch - App Indexing (Icing)"
+    inceptionYear = '2019'
+    description = 'AndroidX AppSearch - App Indexing'
 }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/impl/FakeIcingTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/impl/FakeIcingTest.java
new file mode 100644
index 0000000..25a1530
--- /dev/null
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/impl/FakeIcingTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.appsearch.impl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.google.android.icing.proto.DocumentProto;
+import com.google.android.icing.proto.PropertyProto;
+import com.google.android.icing.proto.SearchResultProto;
+import com.google.android.icing.proto.StatusProto;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class FakeIcingTest {
+    @Test
+    public void query() {
+        FakeIcing icing = new FakeIcing();
+        icing.put(createDoc("uri:cat", "The cat said meow"));
+        icing.put(createDoc("uri:dog", "The dog said woof"));
+
+        assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat");
+        assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog");
+        assertThat(queryGetUris(icing, "fred")).isEmpty();
+    }
+
+    @Test
+    public void queryNorm() {
+        FakeIcing icing = new FakeIcing();
+        icing.put(createDoc("uri:cat", "The cat said meow"));
+        icing.put(createDoc("uri:dog", "The dog said woof"));
+
+        assertThat(queryGetUris(icing, "the")).containsExactly("uri:cat", "uri:dog");
+        assertThat(queryGetUris(icing, "The")).containsExactly("uri:cat", "uri:dog");
+        assertThat(queryGetUris(icing, "tHe")).containsExactly("uri:cat", "uri:dog");
+    }
+
+    @Test
+    public void get() {
+        DocumentProto cat = createDoc("uri:cat", "The cat said meow");
+        FakeIcing icing = new FakeIcing();
+        icing.put(cat);
+        assertThat(icing.get("uri:cat")).isEqualTo(cat);
+    }
+
+    @Test
+    public void replace() {
+        DocumentProto cat = createDoc("uri:cat", "The cat said meow");
+        DocumentProto dog = createDoc("uri:dog", "The dog said woof");
+
+        FakeIcing icing = new FakeIcing();
+        icing.put(cat);
+        icing.put(dog);
+
+        assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat");
+        assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog");
+        assertThat(icing.get("uri:cat")).isEqualTo(cat);
+
+        // Replace
+        DocumentProto cat2 = createDoc("uri:cat", "The cat said purr");
+        DocumentProto bird = createDoc("uri:bird", "The cat said tweet");
+        icing.put(cat2);
+        icing.put(bird);
+
+        assertThat(queryGetUris(icing, "meow")).isEmpty();
+        assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog", "uri:bird");
+        assertThat(icing.get("uri:cat")).isEqualTo(cat2);
+    }
+
+    @Test
+    public void delete() {
+        DocumentProto cat = createDoc("uri:cat", "The cat said meow");
+        DocumentProto dog = createDoc("uri:dog", "The dog said woof");
+
+        FakeIcing icing = new FakeIcing();
+        icing.put(cat);
+        icing.put(dog);
+
+        assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat");
+        assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog");
+        assertThat(icing.get("uri:cat")).isEqualTo(cat);
+
+        // Delete
+        icing.delete("uri:cat");
+        icing.delete("uri:notreal");
+
+        assertThat(queryGetUris(icing, "meow")).isEmpty();
+        assertThat(queryGetUris(icing, "said")).containsExactly("uri:dog");
+        assertThat(icing.get("uri:cat")).isNull();
+    }
+
+    private static DocumentProto createDoc(String uri, String body) {
+        return DocumentProto.newBuilder()
+                .setUri(uri)
+                .addProperties(PropertyProto.newBuilder().addStringValues(body))
+                .build();
+    }
+
+    private static List<String> queryGetUris(FakeIcing icing, String term) {
+        List<String> uris = new ArrayList<>();
+        SearchResultProto results = icing.query(term);
+        assertThat(results.getStatus().getCode()).isEqualTo(StatusProto.Code.OK);
+        for (SearchResultProto.ResultProto result : results.getResultsList()) {
+            uris.add(result.getDocument().getUri());
+        }
+        return uris;
+    }
+}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/impl/FakeIcing.java b/appsearch/appsearch/src/main/java/androidx/appsearch/impl/FakeIcing.java
new file mode 100644
index 0000000..1446e26
--- /dev/null
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/impl/FakeIcing.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appsearch.impl;
+
+import android.util.SparseArray;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.collection.ArrayMap;
+import androidx.collection.ArraySet;
+
+import com.google.android.icing.proto.DocumentProto;
+import com.google.android.icing.proto.PropertyProto;
+import com.google.android.icing.proto.SearchResultProto;
+import com.google.android.icing.proto.StatusProto;
+
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Fake in-memory implementation of the Icing key-value store and reverse index.
+ * <p>
+ * Currently, only queries by single exact term are supported. There is no support for persistence,
+ * namespaces, i18n tokenization, or schema.
+ */
+public class FakeIcing {
+    private final AtomicInteger mNextDocId = new AtomicInteger();
+    private final Map<String, Integer> mUriToDocIdMap = new ArrayMap<>();
+    /** Array of Documents where index into the array is the docId. */
+    private final SparseArray<DocumentProto> mDocStore = new SparseArray<>();
+    /** Map of term to posting-list (the set of DocIds containing that term). */
+    private final Map<String, Set<Integer>> mIndex = new ArrayMap<>();
+
+    /**
+     * Inserts a document into the index.
+     *
+     * @param document The document to insert.
+     */
+    public void put(@NonNull DocumentProto document) {
+        String uri = document.getUri();
+
+        // Update mDocIdMap
+        Integer docId = mUriToDocIdMap.get(uri);
+        if (docId != null) {
+            // Delete the old doc
+            mDocStore.remove(docId);
+        }
+
+        // Allocate a new docId
+        docId = mNextDocId.getAndIncrement();
+        mUriToDocIdMap.put(uri, docId);
+
+        // Update mDocStore
+        mDocStore.put(docId, document);
+
+        // Update mIndex
+        indexDocument(docId, document);
+    }
+
+    /**
+     * Retrieves a document from the index.
+     *
+     * @param uri The URI of the document to retrieve.
+     * @return The body of the document, or {@code null} if no such document exists.
+     */
+    @Nullable
+    public DocumentProto get(@NonNull String uri) {
+        Integer docId = mUriToDocIdMap.get(uri);
+        if (docId == null) {
+            return null;
+        }
+        return mDocStore.get(docId);
+    }
+
+    /**
+     * Returns documents containing all words in the given query string.
+     *
+     * @param queryExpression A set of words to search for. They will be implicitly AND-ed together.
+     *     No operators are supported.
+     * @return A {@link SearchResultProto} containing the matching documents, which may have no
+     *   results if no documents match.
+     */
+    @NonNull
+    public SearchResultProto query(@NonNull String queryExpression) {
+        String[] terms = normalizeString(queryExpression).split("\\s+", -1);
+        SearchResultProto.Builder results = SearchResultProto.newBuilder()
+                .setStatus(StatusProto.newBuilder().setCode(StatusProto.Code.OK));
+        if (terms.length == 0) {
+            return results.build();
+        }
+        Set<Integer> docIds = mIndex.get(terms[0]);
+        if (docIds == null || docIds.isEmpty()) {
+            return results.build();
+        }
+        for (int i = 1; i < terms.length; i++) {
+            Set<Integer> termDocIds = mIndex.get(terms[i]);
+            if (termDocIds == null) {
+                return results.build();
+            }
+            docIds.retainAll(termDocIds);
+            if (docIds.isEmpty()) {
+                return results.build();
+            }
+        }
+        for (int docId : docIds) {
+            DocumentProto document = mDocStore.get(docId);
+            if (document != null) {
+                results.addResults(
+                        SearchResultProto.ResultProto.newBuilder().setDocument(document));
+            }
+        }
+        return results.build();
+    }
+
+    /**
+     * Deletes a document by its URI.
+     *
+     * @param uri The URI of the document to be deleted.
+     * @return Whether deletion was successful.
+     */
+    public boolean delete(@NonNull String uri) {
+        // Update mDocIdMap
+        Integer docId = mUriToDocIdMap.get(uri);
+        if (docId != null) {
+            // Delete the old doc
+            mDocStore.remove(docId);
+            mUriToDocIdMap.remove(uri);
+            return true;
+        }
+        return false;
+    }
+
+    /** Deletes all documents having the given namespace. */
+    public void deleteByNamespace(@NonNull String namespace) {
+        for (int i = 0; i < mDocStore.size(); i++) {
+            DocumentProto document = mDocStore.valueAt(i);
+            if (namespace.equals(document.getNamespace())) {
+                mDocStore.removeAt(i);
+                mUriToDocIdMap.remove(document.getUri());
+                i--;
+            }
+        }
+    }
+
+    /**
+     * Deletes all documents having the given type.
+     *
+     * @return true if any documents were deleted.
+     */
+    public boolean deleteByType(@NonNull String type) {
+        boolean deletedAny = false;
+        for (int i = 0; i < mDocStore.size(); i++) {
+            DocumentProto document = mDocStore.valueAt(i);
+            if (type.equals(document.getSchema())) {
+                mDocStore.removeAt(i);
+                mUriToDocIdMap.remove(document.getUri());
+                i--;
+                deletedAny = true;
+            }
+        }
+        return deletedAny;
+    }
+
+    private void indexDocument(int docId, DocumentProto document) {
+        for (PropertyProto property : document.getPropertiesList()) {
+            for (String stringValue : property.getStringValuesList()) {
+                String[] words = normalizeString(stringValue).split("\\s+", -1);
+                for (String word : words) {
+                    indexTerm(docId, word);
+                }
+            }
+            for (Long longValue : property.getInt64ValuesList()) {
+                indexTerm(docId, longValue.toString());
+            }
+            for (Double doubleValue : property.getDoubleValuesList()) {
+                indexTerm(docId, doubleValue.toString());
+            }
+            for (Boolean booleanValue : property.getBooleanValuesList()) {
+                indexTerm(docId, booleanValue.toString());
+            }
+            // Intentionally skipping bytes values
+            for (DocumentProto documentValue : property.getDocumentValuesList()) {
+                indexDocument(docId, documentValue);
+            }
+        }
+    }
+
+    private void indexTerm(int docId, String term) {
+        Set<Integer> postingList = mIndex.get(term);
+        if (postingList == null) {
+            postingList = new ArraySet<>();
+            mIndex.put(term, postingList);
+        }
+        postingList.add(docId);
+    }
+
+    /** Strips out punctuation and converts to lowercase. */
+    private static String normalizeString(String input) {
+        return input.replaceAll("\\p{P}", "").toLowerCase(Locale.getDefault());
+    }
+}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/impl/package-info.java b/appsearch/appsearch/src/main/java/androidx/appsearch/impl/package-info.java
new file mode 100644
index 0000000..68c3b98
--- /dev/null
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/impl/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+package androidx.appsearch.impl;
+
+import androidx.annotation.RestrictTo;
diff --git a/benchmark/gradle-plugin/build.gradle b/benchmark/gradle-plugin/build.gradle
index 36413cd..c357a51 100644
--- a/benchmark/gradle-plugin/build.gradle
+++ b/benchmark/gradle-plugin/build.gradle
@@ -34,7 +34,7 @@
 dependencies {
     implementation findGradleKotlinDsl()
     implementation gradleApi()
-    implementation("com.android.tools.build:gradle:3.6.0-rc03")
+    implementation("com.android.tools.build:gradle:3.6.0")
     implementation(KOTLIN_STDLIB)
 
     testImplementation gradleTestKit()
diff --git a/buildSrc/build_dependencies.gradle b/buildSrc/build_dependencies.gradle
index 53a58fd..bc8dba6 100644
--- a/buildSrc/build_dependencies.gradle
+++ b/buildSrc/build_dependencies.gradle
@@ -25,8 +25,8 @@
     build_versions.lint = "27.0.0-alpha01"
 } else {
     build_versions.kotlin = "1.3.60"
-    build_versions.agp = '3.6.0-rc03'
-    build_versions.lint = '26.6.0-rc03'
+    build_versions.agp = '3.6.0'
+    build_versions.lint = '26.6.0'
 }
 
 build_versions.dokka = '0.9.17-g007'
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index 35b20de..cfb5aa4 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -45,10 +45,10 @@
     ignore(LibraryGroups.CAMERA.group, "camera-testing")
     ignore(LibraryGroups.CAMERA.group, "camera-extensions-stub")
     ignore(LibraryGroups.CAMERA.group, "camera-testlib-extensions")
-    prebuilts(LibraryGroups.CAMERA, "camera-view", "1.0.0-alpha07")
-    prebuilts(LibraryGroups.CAMERA, "camera-extensions", "1.0.0-alpha07")
+    prebuilts(LibraryGroups.CAMERA, "camera-view", "1.0.0-alpha08")
+    prebuilts(LibraryGroups.CAMERA, "camera-extensions", "1.0.0-alpha08")
             .addStubs("camera/camera-extensions-stub/camera-extensions-stub.jar")
-    prebuilts(LibraryGroups.CAMERA, "1.0.0-alpha10")
+    prebuilts(LibraryGroups.CAMERA, "1.0.0-beta01")
     ignore(LibraryGroups.CAR.group, "car-moderator")
     prebuilts(LibraryGroups.CAR, "car", "1.0.0-alpha7")
             .addStubs("car/stubs/android.car.jar")
diff --git a/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaSourceDocs.kt b/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaSourceDocs.kt
index b1eee52..d115043 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaSourceDocs.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaSourceDocs.kt
@@ -122,6 +122,7 @@
             it.classpath = project.files(it.classpath).plus(project.files(inputs.bootClasspath))
                 .plus(inputs.dependencyClasspath)
             it.dependsOn(inputs.dependencyClasspath)
+            it.dependsOn(inputs.sourcePaths)
         }
     }
 }
diff --git a/buildSrc/src/main/kotlin/androidx/build/java/JavaCompileInputs.kt b/buildSrc/src/main/kotlin/androidx/build/java/JavaCompileInputs.kt
index 30701b5..eb807f6 100644
--- a/buildSrc/src/main/kotlin/androidx/build/java/JavaCompileInputs.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/java/JavaCompileInputs.kt
@@ -83,6 +83,7 @@
             // collection of all the source files inside '*main' source sets. I.e, given a module
             // with a common and Android source set, this will look inside commonMain and
             // androidMain.
+            val taskDependencies = mutableListOf<Any>(variant.javaCompileProvider)
             val sourceFiles = project.multiplatformExtension?.run {
                 sourceSets
                     .filter { it.name.contains("main", ignoreCase = true) }
@@ -90,15 +91,17 @@
                     .also { require(it.isNotEmpty()) }
             } ?: variant
                 .getSourceFolders(SourceKind.JAVA)
-                .map { folder -> folder.dir }
+                .map { folder ->
+                    for (builtBy in folder.builtBy) {
+                        taskDependencies.add(builtBy)
+                    }
+                    folder.dir
+                }
 
             val sourceCollection = project.files(sourceFiles)
-            // Inform Gradle which task must be run for all of the sources to exist
-            // For the moment, aidlCompileProvider is sufficient, but if in the future there
-            // are other tasks that generate sources used by our java compile tasks then we will
-            // need to either add those tasks too or switch this back to javaCompileProvider
-            // (which runs more slowly because it will also compile the generated .java files too)
-            sourceCollection.builtBy(variant.aidlCompileProvider)
+            for (dep in taskDependencies) {
+                sourceCollection.builtBy(dep)
+            }
             return sourceCollection
         }
     }
diff --git a/buildSrc/src/main/kotlin/androidx/build/studio/StudioVersions.kt b/buildSrc/src/main/kotlin/androidx/build/studio/StudioVersions.kt
index abd94c5..d1d56f1 100644
--- a/buildSrc/src/main/kotlin/androidx/build/studio/StudioVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/studio/StudioVersions.kt
@@ -51,9 +51,9 @@
 }
 
 private object RootStudioVersions : StudioVersions() {
-    override val studioVersion = "3.6.0.20"
+    override val studioVersion = "3.6.0.21"
     override val ideaMajorVersion = "192"
-    override val studioBuildNumber = "6186006"
+    override val studioBuildNumber = "6200805"
 }
 
 private object UiStudioVersions : StudioVersions() {
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/PreviewTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/PreviewTest.java
index 761393a..6638b9b 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/PreviewTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/PreviewTest.java
@@ -36,24 +36,21 @@
 import android.view.Surface;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.camera.core.CameraInfoUnavailableException;
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.CameraX;
 import androidx.camera.core.CameraXConfig;
 import androidx.camera.core.Preview;
 import androidx.camera.core.SurfaceRequest;
-import androidx.camera.core.impl.CameraControlInternal;
-import androidx.camera.core.impl.CaptureConfig;
-import androidx.camera.core.impl.SessionConfig;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.camera.testing.CameraUtil;
 import androidx.camera.testing.SurfaceTextureProvider;
-import androidx.camera.testing.fakes.FakeCameraControl;
 import androidx.camera.testing.fakes.FakeLifecycleOwner;
+import androidx.core.util.Consumer;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
-import androidx.test.filters.Suppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.GrantPermissionRule;
 
@@ -63,10 +60,13 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.List;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
 import java.util.concurrent.Semaphore;
+import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 
 @LargeTest
 @RunWith(AndroidJUnit4.class)
@@ -76,49 +76,29 @@
     public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(
             Manifest.permission.CAMERA);
 
+    private static final String ANY_THREAD_NAME = "any-thread-name";
     private static final Size GUARANTEED_RESOLUTION = new Size(640, 480);
-    private static final Preview.SurfaceProvider MOCK_PREVIEW_SURFACE_PROVIDER =
-            mock(Preview.SurfaceProvider.class);
 
     private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
-
+    private final CameraSelector mCameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;
     private Preview.Builder mDefaultBuilder;
-
-    private CameraSelector mCameraSelector;
     private FakeLifecycleOwner mLifecycleOwner;
     private Size mPreviewResolution;
     private Semaphore mSurfaceFutureSemaphore;
-    private Semaphore mSaveToReleaseSemaphore;
-    private Preview.SurfaceProvider mSurfaceProviderWithFrameAvailableListener =
-            createSurfaceTextureProvider(new SurfaceTextureProvider.SurfaceTextureCallback() {
-                @Override
-                public void onSurfaceTextureReady(@NonNull SurfaceTexture surfaceTexture,
-                        @NonNull Size resolution) {
-                    mPreviewResolution = resolution;
-                    surfaceTexture.setOnFrameAvailableListener(
-                            surfaceTexture1 -> mSurfaceFutureSemaphore.release());
-                }
-
-                @Override
-                public void onSafeToRelease(@NonNull SurfaceTexture surfaceTexture) {
-                    surfaceTexture.release();
-                    mSaveToReleaseSemaphore.release();
-                }
-            });
-
+    private Semaphore mSafeToReleaseSemaphore;
 
     @Before
     public void setUp() throws ExecutionException, InterruptedException {
         assumeTrue(CameraUtil.deviceHasCamera());
-        Context context = ApplicationProvider.getApplicationContext();
+
+        final Context context = ApplicationProvider.getApplicationContext();
         CameraXConfig cameraXConfig = Camera2Config.defaultConfig();
         CameraX.initialize(context, cameraXConfig).get();
 
         // init CameraX before creating Preview to get preview size with CameraX's context
         mDefaultBuilder = Preview.Builder.fromConfig(Preview.DEFAULT_CONFIG.getConfig(null));
         mSurfaceFutureSemaphore = new Semaphore(/*permits=*/ 0);
-        mSaveToReleaseSemaphore = new Semaphore(/*permits=*/ 0);
-        mCameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;
+        mSafeToReleaseSemaphore = new Semaphore(/*permits=*/ 0);
         mLifecycleOwner = new FakeLifecycleOwner();
     }
 
@@ -134,32 +114,31 @@
 
     @Test
     public void surfaceProvider_isUsedAfterSetting() {
-        Preview.SurfaceProvider surfaceProvider = mock(Preview.SurfaceProvider.class);
+        final Preview.SurfaceProvider surfaceProvider = mock(Preview.SurfaceProvider.class);
         doAnswer(args -> ((SurfaceRequest) args.getArgument(0)).willNotProvideSurface()).when(
                 surfaceProvider).onSurfaceRequested(
                 any(SurfaceRequest.class));
 
         mInstrumentation.runOnMainSync(() -> {
-            Preview preview = mDefaultBuilder.build();
+            final Preview preview = mDefaultBuilder.build();
             preview.setSurfaceProvider(surfaceProvider);
 
             CameraX.bindToLifecycle(mLifecycleOwner, mCameraSelector, preview);
             mLifecycleOwner.startAndResume();
         });
 
-        verify(surfaceProvider, timeout(3000)).onSurfaceRequested(
-                any(SurfaceRequest.class));
+        verify(surfaceProvider, timeout(3000)).onSurfaceRequested(any(SurfaceRequest.class));
     }
 
     @Test
     public void previewDetached_onSafeToReleaseCalled() throws InterruptedException {
         // Arrange.
-        Preview preview = new Preview.Builder().build();
+        final Preview preview = new Preview.Builder().build();
 
         // Act.
         mInstrumentation.runOnMainSync(() -> {
             preview.setSurfaceProvider(CameraXExecutors.mainThreadExecutor(),
-                    mSurfaceProviderWithFrameAvailableListener);
+                    getSurfaceProvider(null));
             CameraX.bindToLifecycle(mLifecycleOwner, mCameraSelector, preview);
         });
 
@@ -167,20 +146,21 @@
 
         // Wait until preview gets frame.
         assertThat(mSurfaceFutureSemaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue();
+
         // Destroy lifecycle to trigger release.
         mLifecycleOwner.pauseAndStop();
         mLifecycleOwner.destroy();
 
         // Assert.
-        assertThat(mSaveToReleaseSemaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mSafeToReleaseSemaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue();
     }
 
     @Test
-    public void setPreviewSurfaceProviderBeforeBind_getsFrame() throws InterruptedException {
+    public void setSurfaceProviderBeforeBind_getsFrame() throws InterruptedException {
         mInstrumentation.runOnMainSync(() -> {
             // Arrange.
-            Preview preview = mDefaultBuilder.build();
-            preview.setSurfaceProvider(mSurfaceProviderWithFrameAvailableListener);
+            final Preview preview = mDefaultBuilder.build();
+            preview.setSurfaceProvider(getSurfaceProvider(null));
 
             // Act.
             CameraX.bindToLifecycle(mLifecycleOwner, mCameraSelector, preview);
@@ -191,16 +171,36 @@
         assertThat(mSurfaceFutureSemaphore.tryAcquire(10, TimeUnit.SECONDS)).isTrue();
     }
 
-    @Suppress // TODO(b/143703289): Remove suppression once provider can be set after bind
     @Test
-    public void setPreviewSurfaceProviderAfterBind_getsFrame() throws InterruptedException {
+    public void setSurfaceProviderBeforeBind_providesSurfaceOnWorkerExecutorThread()
+            throws InterruptedException {
+        final AtomicReference<String> threadName = new AtomicReference<>();
+
+        mInstrumentation.runOnMainSync(() -> {
+            // Arrange.
+            final Preview preview = mDefaultBuilder.build();
+            preview.setSurfaceProvider(getWorkExecutorWithNamedThread(),
+                    getSurfaceProvider(threadName::set));
+
+            // Act.
+            CameraX.bindToLifecycle(mLifecycleOwner, mCameraSelector, preview);
+            mLifecycleOwner.startAndResume();
+        });
+
+        // Assert.
+        assertThat(mSurfaceFutureSemaphore.tryAcquire(10, TimeUnit.SECONDS)).isTrue();
+        assertThat(threadName.get()).isEqualTo(ANY_THREAD_NAME);
+    }
+
+    @Test
+    public void setSurfaceProviderAfterBind_getsFrame() throws InterruptedException {
         mInstrumentation.runOnMainSync(() -> {
             // Arrange.
             Preview preview = mDefaultBuilder.build();
             CameraX.bindToLifecycle(mLifecycleOwner, mCameraSelector, preview);
 
             // Act.
-            preview.setSurfaceProvider(mSurfaceProviderWithFrameAvailableListener);
+            preview.setSurfaceProvider(getSurfaceProvider(null));
             mLifecycleOwner.startAndResume();
         });
 
@@ -209,6 +209,27 @@
     }
 
     @Test
+    public void setSurfaceProviderAfterBind_providesSurfaceOnWorkerExecutorThread()
+            throws InterruptedException {
+        final AtomicReference<String> threadName = new AtomicReference<>();
+
+        mInstrumentation.runOnMainSync(() -> {
+            // Arrange.
+            Preview preview = mDefaultBuilder.build();
+            CameraX.bindToLifecycle(mLifecycleOwner, mCameraSelector, preview);
+
+            // Act.
+            preview.setSurfaceProvider(getWorkExecutorWithNamedThread(),
+                    getSurfaceProvider(threadName::set));
+            mLifecycleOwner.startAndResume();
+        });
+
+        // Assert.
+        assertThat(mSurfaceFutureSemaphore.tryAcquire(10, TimeUnit.SECONDS)).isTrue();
+        assertThat(threadName.get()).isEqualTo(ANY_THREAD_NAME);
+    }
+
+    @Test
     public void canSupportGuaranteedSize()
             throws InterruptedException, CameraInfoUnavailableException {
         // CameraSelector.LENS_FACING_FRONT/LENS_FACING_BACK are defined as constant int 0 and 1.
@@ -230,8 +251,7 @@
                     isRotateNeeded ? Surface.ROTATION_90 : Surface.ROTATION_0).build();
 
             mInstrumentation.runOnMainSync(() -> {
-                preview.setSurfaceProvider(
-                        mSurfaceProviderWithFrameAvailableListener);
+                preview.setSurfaceProvider(getSurfaceProvider(null));
                 CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(
                         lensFacing).build();
                 CameraX.bindToLifecycle(mLifecycleOwner, cameraSelector, preview);
@@ -254,15 +274,28 @@
         }
     }
 
-    private CameraControlInternal getFakeCameraControl() {
-        return new FakeCameraControl(new CameraControlInternal.ControlUpdateCallback() {
+    private Executor getWorkExecutorWithNamedThread() {
+        final ThreadFactory threadFactory = runnable -> new Thread(runnable, ANY_THREAD_NAME);
+        return Executors.newSingleThreadExecutor(threadFactory);
+    }
+
+    private Preview.SurfaceProvider getSurfaceProvider(
+            @Nullable final Consumer<String> threadNameConsumer) {
+        return createSurfaceTextureProvider(new SurfaceTextureProvider.SurfaceTextureCallback() {
             @Override
-            public void onCameraControlUpdateSessionConfig(@NonNull SessionConfig sessionConfig) {
+            public void onSurfaceTextureReady(@NonNull SurfaceTexture surfaceTexture,
+                    @NonNull Size resolution) {
+                if (threadNameConsumer != null) {
+                    threadNameConsumer.accept(Thread.currentThread().getName());
+                }
+                mPreviewResolution = resolution;
+                surfaceTexture.setOnFrameAvailableListener(st -> mSurfaceFutureSemaphore.release());
             }
 
             @Override
-            public void onCameraControlCaptureRequests(
-                    @NonNull List<CaptureConfig> captureConfigs) {
+            public void onSafeToRelease(@NonNull SurfaceTexture surfaceTexture) {
+                surfaceTexture.release();
+                mSafeToReleaseSemaphore.release();
             }
         });
     }
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/UseCaseCombinationTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/UseCaseCombinationTest.java
index 44ed340..376d434 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/UseCaseCombinationTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/UseCaseCombinationTest.java
@@ -64,8 +64,7 @@
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public final class UseCaseCombinationTest {
-    private static final CameraSelector DEFAULT_SELECTOR =
-            new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build();
+    private static final CameraSelector DEFAULT_SELECTOR = CameraSelector.DEFAULT_BACK_CAMERA;
     private final MutableLiveData<Long> mAnalysisResult = new MutableLiveData<>();
     @Rule
     public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(
@@ -73,31 +72,16 @@
     private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
     private Semaphore mSemaphore;
     private FakeLifecycleOwner mLifecycle;
-    private ImageCapture mImageCapture;
-    private ImageAnalysis mImageAnalysis;
-    private Preview mPreview;
-    private ImageAnalysis.Analyzer mImageAnalyzer;
-
-    private Observer<Long> createCountIncrementingObserver() {
-        return new Observer<Long>() {
-            @Override
-            public void onChanged(Long value) {
-                mSemaphore.release();
-            }
-        };
-    }
 
     @Before
     public void setUp() {
         assumeTrue(CameraUtil.deviceHasCamera());
 
-        Context context = ApplicationProvider.getApplicationContext();
-        CameraXConfig config = Camera2Config.defaultConfig();
-
+        final Context context = ApplicationProvider.getApplicationContext();
+        final CameraXConfig config = Camera2Config.defaultConfig();
         CameraX.initialize(context, config);
 
         mLifecycle = new FakeLifecycleOwner();
-
         mSemaphore = new Semaphore(0);
     }
 
@@ -114,37 +98,12 @@
      */
     @Test
     public void previewCombinesImageCapture() throws InterruptedException {
-        initPreview();
-        initImageCapture();
-        mInstrumentation.runOnMainSync(() -> {
-            mPreview.setSurfaceProvider(createSurfaceTextureProvider(
-                    new SurfaceTextureProvider.SurfaceTextureCallback() {
-                        boolean mIsSurfaceTextureReleased = false;
-                        Object mIsSurfaceTextureReleasedLock = new Object();
-                        @Override
-                        public void onSurfaceTextureReady(@NonNull SurfaceTexture surfaceTexture,
-                                @NonNull Size resolution) {
-                            surfaceTexture.attachToGLContext(GLUtil.getTexIdFromGLContext());
-                            surfaceTexture.setOnFrameAvailableListener(
-                                    surfaceTexture1 -> {
-                                        synchronized (mIsSurfaceTextureReleasedLock) {
-                                            if (!mIsSurfaceTextureReleased) {
-                                                surfaceTexture.updateTexImage();
-                                            }
-                                        }
-                                        mSemaphore.release();
-                                    });
-                        }
+        final Preview preview = initPreview();
+        final ImageCapture imageCapture = initImageCapture();
 
-                        @Override
-                        public void onSafeToRelease(@NonNull SurfaceTexture surfaceTexture) {
-                            synchronized (mIsSurfaceTextureReleasedLock) {
-                                mIsSurfaceTextureReleased = true;
-                                surfaceTexture.release();
-                            }
-                        }
-                    }));
-            CameraX.bindToLifecycle(mLifecycle, DEFAULT_SELECTOR, mPreview, mImageCapture);
+        mInstrumentation.runOnMainSync(() -> {
+            preview.setSurfaceProvider(getSurfaceProvider(true));
+            CameraX.bindToLifecycle(mLifecycle, DEFAULT_SELECTOR, preview, imageCapture);
             mLifecycle.startAndResume();
         });
 
@@ -152,8 +111,8 @@
         mSemaphore.acquire(10);
 
         assertThat(mLifecycle.getObserverCount()).isEqualTo(2);
-        assertThat(CameraX.isBound(mPreview)).isTrue();
-        assertThat(CameraX.isBound(mImageCapture)).isTrue();
+        assertThat(CameraX.isBound(preview)).isTrue();
+        assertThat(CameraX.isBound(imageCapture)).isTrue();
     }
 
     /**
@@ -161,72 +120,108 @@
      */
     @Test
     public void previewCombinesImageAnalysis() throws InterruptedException {
-        initImageAnalysis();
-        initPreview();
+        final Preview preview = initPreview();
+        final ImageAnalysis imageAnalysis = initImageAnalysis();
 
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                CameraX.bindToLifecycle(mLifecycle, DEFAULT_SELECTOR, mPreview, mImageAnalysis);
-                mImageAnalysis.setAnalyzer(CameraXExecutors.mainThreadExecutor(), mImageAnalyzer);
-                mAnalysisResult.observe(mLifecycle,
-                        createCountIncrementingObserver());
-                mLifecycle.startAndResume();
-            }
+        mInstrumentation.runOnMainSync(() -> {
+            CameraX.bindToLifecycle(mLifecycle, DEFAULT_SELECTOR, preview, imageAnalysis);
+            preview.setSurfaceProvider(getSurfaceProvider(false));
+            imageAnalysis.setAnalyzer(CameraXExecutors.mainThreadExecutor(), initImageAnalyzer());
+            mAnalysisResult.observe(mLifecycle, createCountIncrementingObserver());
+            mLifecycle.startAndResume();
         });
 
         // Wait for 10 frames to be analyzed.
         mSemaphore.acquire(10);
 
-        assertThat(CameraX.isBound(mPreview)).isTrue();
-        assertThat(CameraX.isBound(mImageAnalysis)).isTrue();
+        assertThat(CameraX.isBound(preview)).isTrue();
+        assertThat(CameraX.isBound(imageAnalysis)).isTrue();
     }
 
-    /**
-     * Test Combination: Preview + ImageAnalysis + ImageCapture
-     */
+    /** Test Combination: Preview + ImageAnalysis + ImageCapture */
     @Test
     public void previewCombinesImageAnalysisAndImageCapture() throws InterruptedException {
-        initPreview();
-        initImageAnalysis();
-        initImageCapture();
+        final Preview preview = initPreview();
+        final ImageAnalysis imageAnalysis = initImageAnalysis();
+        final ImageCapture imageCapture = initImageCapture();
 
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                CameraX.bindToLifecycle(mLifecycle, DEFAULT_SELECTOR, mPreview, mImageAnalysis,
-                        mImageCapture);
-                mImageAnalysis.setAnalyzer(CameraXExecutors.mainThreadExecutor(), mImageAnalyzer);
-                mAnalysisResult.observe(mLifecycle,
-                        createCountIncrementingObserver());
-                mLifecycle.startAndResume();
-            }
+        mInstrumentation.runOnMainSync(() -> {
+            CameraX.bindToLifecycle(mLifecycle, DEFAULT_SELECTOR, preview, imageAnalysis,
+                    imageCapture);
+            preview.setSurfaceProvider(getSurfaceProvider(false));
+            imageAnalysis.setAnalyzer(CameraXExecutors.mainThreadExecutor(), initImageAnalyzer());
+            mAnalysisResult.observe(mLifecycle, createCountIncrementingObserver());
+            mLifecycle.startAndResume();
         });
 
         // Wait for 10 frames to be analyzed.
         mSemaphore.acquire(10);
 
         assertThat(mLifecycle.getObserverCount()).isEqualTo(3);
-        assertThat(CameraX.isBound(mPreview)).isTrue();
-        assertThat(CameraX.isBound(mImageAnalysis)).isTrue();
-        assertThat(CameraX.isBound(mImageCapture)).isTrue();
+        assertThat(CameraX.isBound(preview)).isTrue();
+        assertThat(CameraX.isBound(imageAnalysis)).isTrue();
+        assertThat(CameraX.isBound(imageCapture)).isTrue();
     }
 
-    private void initImageAnalysis() {
-        mImageAnalyzer = (image) -> {
-            mAnalysisResult.postValue(image.getImageInfo().getTimestamp());
-            image.close();
-        };
-        mImageAnalysis = new ImageAnalysis.Builder()
+    private Preview initPreview() {
+        return new Preview.Builder().setTargetName("Preview").build();
+    }
+
+    private ImageAnalysis initImageAnalysis() {
+        return new ImageAnalysis.Builder()
                 .setTargetName("ImageAnalysis")
                 .build();
     }
 
-    private void initImageCapture() {
-        mImageCapture = new ImageCapture.Builder().build();
+    private ImageAnalysis.Analyzer initImageAnalyzer() {
+        return (image) -> {
+            mAnalysisResult.postValue(image.getImageInfo().getTimestamp());
+            image.close();
+        };
     }
 
-    private void initPreview() {
-        mPreview = new Preview.Builder().setTargetName("Preview").build();
+    private ImageCapture initImageCapture() {
+        return new ImageCapture.Builder().build();
+    }
+
+    private Observer<Long> createCountIncrementingObserver() {
+        return value -> mSemaphore.release();
+    }
+
+    @NonNull
+    private Preview.SurfaceProvider getSurfaceProvider(final boolean addFrameListener) {
+        return createSurfaceTextureProvider(
+                new SurfaceTextureProvider.SurfaceTextureCallback() {
+                    final Object mIsSurfaceTextureReleasedLock = new Object();
+                    boolean mIsSurfaceTextureReleased = false;
+
+                    @Override
+                    public void onSurfaceTextureReady(@NonNull SurfaceTexture surfaceTexture,
+                            @NonNull Size resolution) {
+                        if (addFrameListener) {
+                            surfaceTexture.attachToGLContext(GLUtil.getTexIdFromGLContext());
+                            surfaceTexture.setOnFrameAvailableListener(st -> {
+                                synchronized (mIsSurfaceTextureReleasedLock) {
+                                    if (!mIsSurfaceTextureReleased) {
+                                        surfaceTexture.updateTexImage();
+                                    }
+                                }
+                                mSemaphore.release();
+                            });
+                        }
+                    }
+
+                    @Override
+                    public void onSafeToRelease(@NonNull SurfaceTexture surfaceTexture) {
+                        if (addFrameListener) {
+                            synchronized (mIsSurfaceTextureReleasedLock) {
+                                mIsSurfaceTextureReleased = true;
+                                surfaceTexture.release();
+                            }
+                        } else {
+                            surfaceTexture.release();
+                        }
+                    }
+                });
     }
 }
diff --git a/camera/camera-core/api/1.0.0-beta01.txt b/camera/camera-core/api/1.0.0-beta01.txt
index 3352365..4e20a67 100644
--- a/camera/camera-core/api/1.0.0-beta01.txt
+++ b/camera/camera-core/api/1.0.0-beta01.txt
@@ -252,11 +252,18 @@
   public final class SurfaceRequest {
     method public void addRequestCancellationListener(java.util.concurrent.Executor, Runnable);
     method public android.util.Size getResolution();
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> provideSurface(android.view.Surface);
+    method public void provideSurface(android.view.Surface, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceRequest.Result!>);
     method public boolean willNotProvideSurface();
   }
 
-  public static final class SurfaceRequest.RequestCancelledException extends java.lang.RuntimeException {
+  public abstract static class SurfaceRequest.Result {
+    method public abstract int getResultCode();
+    method public abstract android.view.Surface getSurface();
+    field public static final int RESULT_INVALID_SURFACE = 2; // 0x2
+    field public static final int RESULT_REQUEST_CANCELLED = 1; // 0x1
+    field public static final int RESULT_SURFACE_ALREADY_PROVIDED = 3; // 0x3
+    field public static final int RESULT_SURFACE_USED_SUCCESSFULLY = 0; // 0x0
+    field public static final int RESULT_WILL_NOT_PROVIDE_SURFACE = 4; // 0x4
   }
 
   public class TorchState {
diff --git a/camera/camera-core/api/current.txt b/camera/camera-core/api/current.txt
index 3352365..4e20a67 100644
--- a/camera/camera-core/api/current.txt
+++ b/camera/camera-core/api/current.txt
@@ -252,11 +252,18 @@
   public final class SurfaceRequest {
     method public void addRequestCancellationListener(java.util.concurrent.Executor, Runnable);
     method public android.util.Size getResolution();
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> provideSurface(android.view.Surface);
+    method public void provideSurface(android.view.Surface, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceRequest.Result!>);
     method public boolean willNotProvideSurface();
   }
 
-  public static final class SurfaceRequest.RequestCancelledException extends java.lang.RuntimeException {
+  public abstract static class SurfaceRequest.Result {
+    method public abstract int getResultCode();
+    method public abstract android.view.Surface getSurface();
+    field public static final int RESULT_INVALID_SURFACE = 2; // 0x2
+    field public static final int RESULT_REQUEST_CANCELLED = 1; // 0x1
+    field public static final int RESULT_SURFACE_ALREADY_PROVIDED = 3; // 0x3
+    field public static final int RESULT_SURFACE_USED_SUCCESSFULLY = 0; // 0x0
+    field public static final int RESULT_WILL_NOT_PROVIDE_SURFACE = 4; // 0x4
   }
 
   public class TorchState {
diff --git a/camera/camera-core/api/public_plus_experimental_1.0.0-beta01.txt b/camera/camera-core/api/public_plus_experimental_1.0.0-beta01.txt
index 738ab65..a18430b 100644
--- a/camera/camera-core/api/public_plus_experimental_1.0.0-beta01.txt
+++ b/camera/camera-core/api/public_plus_experimental_1.0.0-beta01.txt
@@ -256,11 +256,18 @@
   public final class SurfaceRequest {
     method public void addRequestCancellationListener(java.util.concurrent.Executor, Runnable);
     method public android.util.Size getResolution();
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> provideSurface(android.view.Surface);
+    method public void provideSurface(android.view.Surface, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceRequest.Result!>);
     method public boolean willNotProvideSurface();
   }
 
-  public static final class SurfaceRequest.RequestCancelledException extends java.lang.RuntimeException {
+  public abstract static class SurfaceRequest.Result {
+    method public abstract int getResultCode();
+    method public abstract android.view.Surface getSurface();
+    field public static final int RESULT_INVALID_SURFACE = 2; // 0x2
+    field public static final int RESULT_REQUEST_CANCELLED = 1; // 0x1
+    field public static final int RESULT_SURFACE_ALREADY_PROVIDED = 3; // 0x3
+    field public static final int RESULT_SURFACE_USED_SUCCESSFULLY = 0; // 0x0
+    field public static final int RESULT_WILL_NOT_PROVIDE_SURFACE = 4; // 0x4
   }
 
   public class TorchState {
diff --git a/camera/camera-core/api/public_plus_experimental_current.txt b/camera/camera-core/api/public_plus_experimental_current.txt
index 738ab65..a18430b 100644
--- a/camera/camera-core/api/public_plus_experimental_current.txt
+++ b/camera/camera-core/api/public_plus_experimental_current.txt
@@ -256,11 +256,18 @@
   public final class SurfaceRequest {
     method public void addRequestCancellationListener(java.util.concurrent.Executor, Runnable);
     method public android.util.Size getResolution();
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> provideSurface(android.view.Surface);
+    method public void provideSurface(android.view.Surface, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceRequest.Result!>);
     method public boolean willNotProvideSurface();
   }
 
-  public static final class SurfaceRequest.RequestCancelledException extends java.lang.RuntimeException {
+  public abstract static class SurfaceRequest.Result {
+    method public abstract int getResultCode();
+    method public abstract android.view.Surface getSurface();
+    field public static final int RESULT_INVALID_SURFACE = 2; // 0x2
+    field public static final int RESULT_REQUEST_CANCELLED = 1; // 0x1
+    field public static final int RESULT_SURFACE_ALREADY_PROVIDED = 3; // 0x3
+    field public static final int RESULT_SURFACE_USED_SUCCESSFULLY = 0; // 0x0
+    field public static final int RESULT_WILL_NOT_PROVIDE_SURFACE = 4; // 0x4
   }
 
   public class TorchState {
diff --git a/camera/camera-core/api/restricted_1.0.0-beta01.txt b/camera/camera-core/api/restricted_1.0.0-beta01.txt
index 3352365..4e20a67 100644
--- a/camera/camera-core/api/restricted_1.0.0-beta01.txt
+++ b/camera/camera-core/api/restricted_1.0.0-beta01.txt
@@ -252,11 +252,18 @@
   public final class SurfaceRequest {
     method public void addRequestCancellationListener(java.util.concurrent.Executor, Runnable);
     method public android.util.Size getResolution();
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> provideSurface(android.view.Surface);
+    method public void provideSurface(android.view.Surface, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceRequest.Result!>);
     method public boolean willNotProvideSurface();
   }
 
-  public static final class SurfaceRequest.RequestCancelledException extends java.lang.RuntimeException {
+  public abstract static class SurfaceRequest.Result {
+    method public abstract int getResultCode();
+    method public abstract android.view.Surface getSurface();
+    field public static final int RESULT_INVALID_SURFACE = 2; // 0x2
+    field public static final int RESULT_REQUEST_CANCELLED = 1; // 0x1
+    field public static final int RESULT_SURFACE_ALREADY_PROVIDED = 3; // 0x3
+    field public static final int RESULT_SURFACE_USED_SUCCESSFULLY = 0; // 0x0
+    field public static final int RESULT_WILL_NOT_PROVIDE_SURFACE = 4; // 0x4
   }
 
   public class TorchState {
diff --git a/camera/camera-core/api/restricted_current.txt b/camera/camera-core/api/restricted_current.txt
index 3352365..4e20a67 100644
--- a/camera/camera-core/api/restricted_current.txt
+++ b/camera/camera-core/api/restricted_current.txt
@@ -252,11 +252,18 @@
   public final class SurfaceRequest {
     method public void addRequestCancellationListener(java.util.concurrent.Executor, Runnable);
     method public android.util.Size getResolution();
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> provideSurface(android.view.Surface);
+    method public void provideSurface(android.view.Surface, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceRequest.Result!>);
     method public boolean willNotProvideSurface();
   }
 
-  public static final class SurfaceRequest.RequestCancelledException extends java.lang.RuntimeException {
+  public abstract static class SurfaceRequest.Result {
+    method public abstract int getResultCode();
+    method public abstract android.view.Surface getSurface();
+    field public static final int RESULT_INVALID_SURFACE = 2; // 0x2
+    field public static final int RESULT_REQUEST_CANCELLED = 1; // 0x1
+    field public static final int RESULT_SURFACE_ALREADY_PROVIDED = 3; // 0x3
+    field public static final int RESULT_SURFACE_USED_SUCCESSFULLY = 0; // 0x0
+    field public static final int RESULT_WILL_NOT_PROVIDE_SURFACE = 4; // 0x4
   }
 
   public class TorchState {
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/SurfaceRequestTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/SurfaceRequestTest.java
index 7a49285..a42fc3a 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/SurfaceRequestTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/SurfaceRequestTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
@@ -26,26 +27,27 @@
 import android.view.Surface;
 
 import androidx.annotation.NonNull;
+import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.core.content.ContextCompat;
+import androidx.core.util.Consumer;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import com.google.common.util.concurrent.ListenableFuture;
-
 import org.junit.After;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public final class SurfaceRequestTest {
 
     private static final Size FAKE_SIZE = new Size(0, 0);
+    private static final Consumer<SurfaceRequest.Result> NO_OP_RESULT_LISTENER = ignored -> {
+    };
     private static final Surface MOCK_SURFACE = mock(Surface.class);
     private List<SurfaceRequest> mSurfaceRequests = new ArrayList<>();
 
@@ -67,19 +69,17 @@
         assertThat(request.getResolution()).isEqualTo(resolution);
     }
 
+    @SuppressWarnings("unchecked")
     @Test
-    public void willNotProvideSurface_setsIllegalStateException_onReturnedFuture()
-            throws InterruptedException {
+    public void setWillNotProvideSurface_resultsInWILL_NOT_PROVIDE_SURFACE() {
         SurfaceRequest request = createNewRequest(FAKE_SIZE);
 
+        Consumer<SurfaceRequest.Result> listener = mock(Consumer.class);
         request.willNotProvideSurface();
-        ListenableFuture<Void> completion = request.provideSurface(MOCK_SURFACE);
+        request.provideSurface(MOCK_SURFACE, CameraXExecutors.directExecutor(), listener);
 
-        try {
-            completion.get();
-        } catch (ExecutionException e) {
-            assertThat(e.getCause()).isInstanceOf(IllegalStateException.class);
-        }
+        verify(listener).accept(eq(SurfaceRequest.Result.of(
+                SurfaceRequest.Result.RESULT_WILL_NOT_PROVIDE_SURFACE, MOCK_SURFACE)));
     }
 
     @Test
@@ -87,7 +87,8 @@
         SurfaceRequest request = createNewRequest(FAKE_SIZE);
 
         // Complete the request
-        request.provideSurface(MOCK_SURFACE);
+        request.provideSurface(MOCK_SURFACE, CameraXExecutors.directExecutor(),
+                NO_OP_RESULT_LISTENER);
 
         assertThat(request.willNotProvideSurface()).isFalse();
     }
@@ -109,56 +110,50 @@
         assertThat(request.willNotProvideSurface()).isTrue();
     }
 
+    @SuppressWarnings("unchecked")
     @Test
-    public void returnedFuture_completesSuccessfully_afterProducerIsDone()
-            throws InterruptedException, ExecutionException {
+    public void surfaceRequestResult_completesSuccessfully_afterProducerIsDone() {
         SurfaceRequest request = createNewRequest(FAKE_SIZE);
 
-        ListenableFuture<Void> completion = request.provideSurface(MOCK_SURFACE);
-
-        Runnable listener = mock(Runnable.class);
-        completion.addListener(listener,
-                ContextCompat.getMainExecutor(ApplicationProvider.getApplicationContext()));
+        Consumer<SurfaceRequest.Result> listener = mock(Consumer.class);
+        request.provideSurface(MOCK_SURFACE,
+                ContextCompat.getMainExecutor(ApplicationProvider.getApplicationContext()),
+                listener);
 
         // Cause request to be completed from producer side
         request.getDeferrableSurface().close();
 
-        verify(listener, timeout(500)).run();
-        // Should not throw
-        completion.get();
+        verify(listener, timeout(500)).accept(eq(SurfaceRequest.Result.of(
+                SurfaceRequest.Result.RESULT_SURFACE_USED_SUCCESSFULLY, MOCK_SURFACE)));
     }
 
+    @SuppressWarnings("unchecked")
     @Test
-    public void provideSurface_setsIllegalStateException_onSecondInvocation()
-            throws InterruptedException {
+    public void provideSurface_resultsInSURFACE_ALREADY_PROVIDED_onSecondInvocation() {
         SurfaceRequest request = createNewRequest(FAKE_SIZE);
 
-        ListenableFuture<Void> completion1 = request.provideSurface(MOCK_SURFACE);
-        ListenableFuture<Void> completion2 = request.provideSurface(MOCK_SURFACE);
+        Consumer<SurfaceRequest.Result> listener = mock(Consumer.class);
+        request.provideSurface(MOCK_SURFACE, CameraXExecutors.directExecutor(),
+                NO_OP_RESULT_LISTENER);
+        request.provideSurface(MOCK_SURFACE, CameraXExecutors.directExecutor(), listener);
 
-        try {
-            completion2.get();
-        } catch (ExecutionException e) {
-            assertThat(e.getCause()).isInstanceOf(IllegalStateException.class);
-        }
-        assertThat(completion1).isNotSameInstanceAs(completion2);
+        verify(listener).accept(eq(SurfaceRequest.Result.of(
+                SurfaceRequest.Result.RESULT_SURFACE_ALREADY_PROVIDED, MOCK_SURFACE)));
     }
 
+    @SuppressWarnings("unchecked")
     @Test
-    public void cancelledRequest_setsRequestCancelledException_onReturnedFuture()
-            throws InterruptedException {
+    public void cancelledRequest_resultsInREQUEST_CANCELLED() {
         SurfaceRequest request = createNewRequest(FAKE_SIZE);
 
         // Cause request to be cancelled from producer side
         request.getDeferrableSurface().close();
 
-        ListenableFuture<Void> completion = request.provideSurface(MOCK_SURFACE);
+        Consumer<SurfaceRequest.Result> listener = mock(Consumer.class);
+        request.provideSurface(MOCK_SURFACE, CameraXExecutors.directExecutor(), listener);
 
-        try {
-            completion.get();
-        } catch (ExecutionException e) {
-            assertThat(e.getCause()).isInstanceOf(SurfaceRequest.RequestCancelledException.class);
-        }
+        verify(listener).accept(eq(SurfaceRequest.Result.of(
+                SurfaceRequest.Result.RESULT_REQUEST_CANCELLED, MOCK_SURFACE)));
     }
 
     @Test
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
index b69ead1..ea9c3ad 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
@@ -74,10 +74,15 @@
 import androidx.camera.core.impl.UseCaseConfig;
 import androidx.camera.core.impl.utils.Threads;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+import androidx.camera.core.impl.utils.futures.FutureCallback;
+import androidx.camera.core.impl.utils.futures.Futures;
 import androidx.camera.core.internal.CameraCaptureResultImageInfo;
 import androidx.camera.core.internal.TargetConfig;
 import androidx.camera.core.internal.ThreadConfig;
-import androidx.core.util.Preconditions;
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.core.util.Consumer;
+
+import com.google.common.util.concurrent.ListenableFuture;
 
 import java.util.List;
 import java.util.Map;
@@ -136,6 +141,9 @@
     @RestrictTo(Scope.LIBRARY_GROUP)
     public static final Defaults DEFAULT_CONFIG = new Defaults();
     private static final String TAG = "Preview";
+    private static final Executor DEFAULT_SURFACE_PROVIDER_EXECUTOR =
+            CameraXExecutors.mainThreadExecutor();
+
     @Nullable
     private HandlerThread mProcessingPreviewThread;
     @Nullable
@@ -145,9 +153,11 @@
     @Nullable
     SurfaceProvider mSurfaceProvider;
     @SuppressWarnings("WeakerAccess") /* Synthetic Accessor */
+    @NonNull
+    Executor mSurfaceProviderExecutor = DEFAULT_SURFACE_PROVIDER_EXECUTOR;
     @Nullable
-    Executor mPreviewSurfaceProviderExecutor;
-    // Cached latest resolution for creating the pipeline as soon as it's ready.
+    private CallbackToFutureAdapter.Completer<Pair<SurfaceProvider, Executor>>
+            mSurfaceProviderCompleter;
     @Nullable
     private Size mLatestResolution;
 
@@ -168,13 +178,12 @@
     SessionConfig.Builder createPipeline(@NonNull String cameraId, @NonNull PreviewConfig config,
             @NonNull Size resolution) {
         Threads.checkMainThread();
-        Preconditions.checkState(isPreviewSurfaceProviderSet());
         SessionConfig.Builder sessionConfigBuilder = SessionConfig.Builder.createFrom(config);
-
         final CaptureProcessor captureProcessor = config.getCaptureProcessor(null);
-        SurfaceRequest surfaceRequest = new SurfaceRequest(resolution);
-        mPreviewSurfaceProviderExecutor.execute(
-                () -> mSurfaceProvider.onSurfaceRequested(surfaceRequest));
+
+        final SurfaceRequest surfaceRequest = new SurfaceRequest(resolution);
+        setUpSurfaceProviderWrap(surfaceRequest);
+
         if (captureProcessor != null) {
             CaptureStage captureStage = new CaptureStage.DefaultCaptureStage();
             // TODO: To allow user to use an Executor for the processing.
@@ -185,15 +194,14 @@
                 mProcessingPreviewHandler = new Handler(mProcessingPreviewThread.getLooper());
             }
 
-            ProcessingSurface processingSurface =
-                    new ProcessingSurface(
-                            resolution.getWidth(),
-                            resolution.getHeight(),
-                            ImageFormat.YUV_420_888,
-                            mProcessingPreviewHandler,
-                            captureStage,
-                            captureProcessor,
-                            surfaceRequest.getDeferrableSurface());
+            ProcessingSurface processingSurface = new ProcessingSurface(
+                    resolution.getWidth(),
+                    resolution.getHeight(),
+                    ImageFormat.YUV_420_888,
+                    mProcessingPreviewHandler,
+                    captureStage,
+                    captureProcessor,
+                    surfaceRequest.getDeferrableSurface());
 
             sessionConfigBuilder.addCameraCaptureCallback(
                     processingSurface.getCameraCaptureCallback());
@@ -241,6 +249,43 @@
         return sessionConfigBuilder;
     }
 
+    private void setUpSurfaceProviderWrap(
+            @NonNull final SurfaceRequest surfaceRequest) {
+        final ListenableFuture<Pair<SurfaceProvider, Executor>> future =
+                CallbackToFutureAdapter.getFuture(completer -> {
+                    if (mSurfaceProviderCompleter != null) {
+                        mSurfaceProviderCompleter.setCancelled();
+                    }
+
+                    mSurfaceProviderCompleter = completer;
+                    if (mSurfaceProvider != null) {
+                        mSurfaceProviderCompleter.set(
+                                new Pair<>(mSurfaceProvider, mSurfaceProviderExecutor));
+                        mSurfaceProviderCompleter = null;
+                    }
+                    return "surface provider and executor future";
+                });
+        Futures.addCallback(future, new FutureCallback<Pair<SurfaceProvider, Executor>>() {
+            @Override
+            public void onSuccess(@Nullable final Pair<SurfaceProvider, Executor> result) {
+                if (result == null) {
+                    return;
+                }
+
+                final SurfaceProvider surfaceProvider = result.first;
+                final Executor executor = result.second;
+                if (surfaceProvider != null && executor != null) {
+                    executor.execute(() -> surfaceProvider.onSurfaceRequested(surfaceRequest));
+                }
+            }
+
+            @Override
+            public void onFailure(Throwable t) {
+                surfaceRequest.getDeferrableSurface().close();
+            }
+        }, CameraXExecutors.directExecutor());
+    }
+
     /**
      * Sets a {@link SurfaceProvider} to provide a {@link Surface} for Preview.
      *
@@ -259,12 +304,19 @@
             notifyInactive();
         } else {
             mSurfaceProvider = surfaceProvider;
-            mPreviewSurfaceProviderExecutor = executor;
+            mSurfaceProviderExecutor = executor;
             notifyActive();
-            if (mLatestResolution != null) {
-                updateConfigAndOutput(getBoundCameraId(), (PreviewConfig) getUseCaseConfig(),
-                        mLatestResolution);
-            }
+            onSurfaceProviderAvailable();
+        }
+    }
+
+    private void onSurfaceProviderAvailable() {
+        if (mSurfaceProviderCompleter != null) {
+            mSurfaceProviderCompleter.set(new Pair<>(mSurfaceProvider, mSurfaceProviderExecutor));
+            mSurfaceProviderCompleter = null;
+        } else if (mLatestResolution != null) {
+            updateConfigAndOutput(getBoundCameraId(), (PreviewConfig) getUseCaseConfig(),
+                    mLatestResolution);
         }
     }
 
@@ -278,21 +330,11 @@
      */
     @UiThread
     public void setSurfaceProvider(@Nullable SurfaceProvider surfaceProvider) {
-        setSurfaceProvider(CameraXExecutors.mainThreadExecutor(), surfaceProvider);
+        setSurfaceProvider(DEFAULT_SURFACE_PROVIDER_EXECUTOR, surfaceProvider);
     }
 
-    /**
-     * Checks if {@link SurfaceProvider} is set by the user.
-     */
-    @SuppressWarnings("WeakerAccess")
-    boolean isPreviewSurfaceProviderSet() {
-        return mSurfaceProvider != null && mPreviewSurfaceProviderExecutor != null;
-    }
-
-
     private void updateConfigAndOutput(@NonNull String cameraId, @NonNull PreviewConfig config,
             @NonNull Size resolution) {
-        Preconditions.checkState(isPreviewSurfaceProviderSet());
         attachToCamera(cameraId, createPipeline(cameraId, config, resolution).build());
     }
 
@@ -381,6 +423,10 @@
         if (mSessionDeferrableSurface != null) {
             mSessionDeferrableSurface.close();
         }
+        if (mSurfaceProviderCompleter != null) {
+            mSurfaceProviderCompleter.setCancelled();
+            mSurfaceProviderCompleter = null;
+        }
         super.clear();
     }
 
@@ -401,14 +447,10 @@
                     "Suggested resolution map missing resolution for camera " + cameraId);
         }
         mLatestResolution = resolution;
-
-        if (isPreviewSurfaceProviderSet()) {
-            updateConfigAndOutput(cameraId, (PreviewConfig) getUseCaseConfig(), resolution);
-        }
+        updateConfigAndOutput(cameraId, (PreviewConfig) getUseCaseConfig(), resolution);
         return suggestedResolutionMap;
     }
 
-
     /**
      * A interface implemented by the application to provide a {@link Surface} for {@link Preview}.
      *
@@ -427,15 +469,18 @@
          * only a single request will be active at a time.
          *
          * <p>A request is considered active until it is
-         * {@linkplain SurfaceRequest#provideSurface(Surface) set},
-         * {@linkplain SurfaceRequest#willNotProvideSurface()} marked as 'will not complete'}, or
-         * {@linkplain SurfaceRequest#addRequestCancellationListener(Executor, Runnable) cancelled
-         * by the camera}. After one of these conditions occurs, a request is considered
-         * completed.
          *
-         * <p>Once a request is successfully completed, if a new request is made it will only
-         * occur after the listenable future returned by
-         * {@link SurfaceRequest#provideSurface(Surface)} has completed.
+         * {@linkplain SurfaceRequest#provideSurface(Surface, Executor, androidx.core.util.Consumer)
+         * fulfilled}, {@linkplain SurfaceRequest#willNotProvideSurface()} marked as 'will not
+         * complete'}, or
+         * {@linkplain SurfaceRequest#addRequestCancellationListener(Executor, Runnable) cancelled
+         * by the camera}. After one of these conditions occurs, a request is considered completed.
+         *
+         * <p>Once a request is successfully completed, it is guaranteed that if a new request is
+         * made, the {@link Surface} used to fulfill the previous request will be detached from the
+         * camera and {@link SurfaceRequest#provideSurface(Surface, Executor, Consumer)} will be
+         * invoked with a {@link androidx.camera.core.SurfaceRequest.Result} containing
+         * {@link androidx.camera.core.SurfaceRequest.Result#RESULT_SURFACE_USED_SUCCESSFULLY}.
          *
          * Example:
          *
@@ -457,22 +502,19 @@
          *         // Create the surface and attempt to provide it to the camera.
          *         Surface surface = resetGlInputSurface(request.getResolution());
          *
-         *         // Provide the surface.
-         *         ListenableFuture<Void> sessionFuture = request.provideSurface(surface);
-         *         // Attach a listener that will be called to clean up the surface.
-         *         sessionFuture.addListener(() -> {
+         *         // Provide the surface and wait for the result to clean up the surface.
+         *         request.provideSurface(surface, mGlExecutor, (result) -> {
          *             // In all cases (even errors), we can clean up the state. As an
-         *             // optimization, we could also optionally check for errors on the
-         *             // ListenableFuture since we may be able to reuse the surface on
-         *             // subsequent surface requests.
+         *             // optimization, we could also optionally check for REQUEST_CANCELLED
+         *             // since we may be able to reuse the surface on subsequent surface requests.
          *             closeGlInputSurface(surface);
-         *         }, mGlExecutor);
+         *         });
          *     }
          * }
          * </pre>
          *
-         * @param request  the request for a surface which contains the requirements of the
-         *                 surface and methods for completing the request.
+         * @param request the request for a surface which contains the requirements of the
+         *                surface and methods for completing the request.
          */
         void onSurfaceRequested(@NonNull SurfaceRequest request);
     }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceRequest.java b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceRequest.java
index d16094e..827199f 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceRequest.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceRequest.java
@@ -21,6 +21,7 @@
 import android.util.Size;
 import android.view.Surface;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
@@ -29,10 +30,14 @@
 import androidx.camera.core.impl.utils.futures.FutureCallback;
 import androidx.camera.core.impl.utils.futures.Futures;
 import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.core.util.Consumer;
 import androidx.core.util.Preconditions;
 
+import com.google.auto.value.AutoValue;
 import com.google.common.util.concurrent.ListenableFuture;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
@@ -201,8 +206,9 @@
      *
      * The surface which fulfills this request must have the resolution specified here in
      * order to fulfill the resource requirements of the camera. Fulfillment of the request
-     * with a surface of a different resolution may cause the future returned by
-     * {@link #provideSurface(Surface)} to fail with an {@link IllegalArgumentException}.
+     * with a surface of a different resolution may cause the {@code resultListener} passed to
+     * {@link #provideSurface(Surface, Executor, Consumer)} to be invoked with a {@link Result}
+     * containing {@link Result#RESULT_INVALID_SURFACE}.
      *
      * @return The guaranteed supported resolution.
      * @see SurfaceTexture#setDefaultBufferSize(int, int)
@@ -216,50 +222,66 @@
      * Completes the request for a {@link Surface} if it has not already been
      * completed or cancelled.
      *
-     * <p>Upon returning from this method, the surface request is guaranteed to be complete.
-     * However, only the {@link ListenableFuture} returned by the first invocation of this method
-     * should be used to track when the provided {@link Surface} is no longer in use by the
-     * camera, as subsequent invocations will always return a {@link ListenableFuture} that
-     * immediately contains an {@link IllegalStateException}.
+     * <p>Once the camera no longer needs the provided surface, the {@code resultListener} will be
+     * invoked with a {@link Result} containing {@link Result#RESULT_SURFACE_USED_SUCCESSFULLY}.
+     * At this point it is safe to release the surface and any underlying resources. Releasing
+     * the surface before receiving this signal may cause undesired behavior on lower API levels.
      *
+     * <p>If the request is cancelled by the camera before successfully attaching the
+     * provided surface to the camera, then the {@code resultListener} will be invoked with a
+     * {@link Result} containing {@link Result#RESULT_REQUEST_CANCELLED}. In addition, any
+     * cancellation listeners provided to
+     * {@link #addRequestCancellationListener(Executor, Runnable)} will be invoked.
+     *
+     * <p>If the request was previously completed via {@link #willNotProvideSurface()}, then
+     * {@code resultListener} will be invoked with a {@link Result} containing
+     * {@link Result#RESULT_WILL_NOT_PROVIDE_SURFACE}.
+     *
+     * <p>Upon returning from this method, the surface request is guaranteed to be complete.
+     * However, only the {@code resultListener} provided to the first invocation of this method
+     * should be used to track when the provided {@link Surface} is no longer in use by the
+     * camera, as subsequent invocations will always invoke the {@code resultListener} with a
+     * {@link Result} containing {@link Result#RESULT_SURFACE_ALREADY_PROVIDED}.
      * @param surface The surface which will complete the request.
-     * @return A ListenableFuture which can be used to track when the provided surface is no
-     * longer being used. Successful completion of this future means the surface is
-     * no longer in use by the camera, and all resources associated with the surface
-     * can be safely cleaned up. If the SurfaceRequest was successfully completed by
-     * {@code provideSurface(Surface)} or {@link #willNotProvideSurface()} prior to providing
-     * the surface, then this future will fail with a {@link IllegalStateException}.
-     * If the SurfaceRequest was cancelled by the camera prior to calling
-     * {@code provideSurface(Surface)}, then this future will fail with an
-     * {@link RequestCancelledException}. If the surface does not meet the
-     * requirements, such has not having a resolution that matches
-     * {@link #getResolution()}, this future may fail with an
-     * {@link IllegalArgumentException}. Cancelling the returned future via
-     * {@link ListenableFuture#cancel(boolean)} is a no-op, and should be avoided in
-     * most cases as this bypasses the signal that the Surface is no longer in use by
-     * the camera. Releasing the Surface before the camera is done using it may lead
-     * to undesired behavior.
+     * @param executor Executor used to execute the {@code resultListener}.
+     * @param resultListener Listener used to track how the surface is used by the camera in
+     *                       response to being provided by this method.
      */
-    @NonNull
-    public ListenableFuture<Void> provideSurface(@NonNull Surface surface) {
+    public void provideSurface(@NonNull Surface surface, @NonNull Executor executor,
+            @NonNull Consumer<Result> resultListener) {
         if (mSurfaceCompleter.set(surface) || mSurfaceFuture.isCancelled()) {
             // Session will be pending completion (or surface request was cancelled). Return the
             // session future.
-            return Futures.nonCancellationPropagating(mSessionStatusFuture);
+            Futures.addCallback(mSessionStatusFuture, new FutureCallback<Void>() {
+                @Override
+                public void onSuccess(@Nullable Void result) {
+                    resultListener.accept(Result.of(Result.RESULT_SURFACE_USED_SUCCESSFULLY,
+                            surface));
+                }
+
+                @Override
+                public void onFailure(Throwable t) {
+                    Preconditions.checkState(t instanceof RequestCancelledException, "Camera "
+                            + "surface session should only fail with request "
+                            + "cancellation. Instead failed due to:\n" + t);
+                    resultListener.accept(Result.of(Result.RESULT_REQUEST_CANCELLED, surface));
+                }
+            }, executor);
         } else {
+            // Surface request is already complete
+            Preconditions.checkState(mSurfaceFuture.isDone());
             try {
-                Surface alreadySetSurface = mSurfaceFuture.get();
-                return Futures.immediateFailedFuture(new IllegalStateException("Surface "
-                        + "request already complete with surface " + alreadySetSurface));
-            } catch (InterruptedException e) {
-                // Should never happen?
-                return Futures.immediateFailedFuture(new IllegalStateException("Surface "
-                        + "request already complete."));
-            } catch (ExecutionException e) {
-                // Likely failure due to #willNotProvideSurface()
-                return Futures.immediateFailedFuture(new IllegalStateException("Surface "
-                        + "request already complete.", e.getCause()));
+                mSurfaceFuture.get();
+                // Getting this far means the surface was already provided.
+                executor.execute(
+                        () -> resultListener.accept(
+                                Result.of(Result.RESULT_SURFACE_ALREADY_PROVIDED, surface)));
+            } catch (InterruptedException | ExecutionException e) {
+                executor.execute(
+                        () -> resultListener.accept(
+                                Result.of(Result.RESULT_WILL_NOT_PROVIDE_SURFACE, surface)));
             }
+
         }
     }
 
@@ -271,16 +293,17 @@
      *
      * <p>This should always be called as soon as it is known that the request will not
      * be fulfilled. Failure to complete the SurfaceRequest via {@code willNotProvideSurface()}
-     * or {@link #provideSurface(Surface)} may cause long delays in shutting down the camera.
+     * or {@link #provideSurface(Surface, Executor, Consumer)} may cause long delays in shutting
+     * down the camera.
      *
      * <p>Upon returning from this method, the request is guaranteed to be complete, regardless
      * of the return value. If the request was previously successfully completed by
-     * {@link #provideSurface(Surface)}, invoking this method will not affection completion of the
-     * returned {@link ListenableFuture}.
+     * {@link #provideSurface(Surface, Executor, Consumer)}, invoking this method will return
+     * {@code false}, and will have no effect on how the surface is used by the camera.
      *
      * @return {@code true} if this call to {@code willNotProvideSurface()} successfully
      * completes the request, i.e., the request has not already been completed via
-     * {@link #provideSurface(Surface)} or by a previous call to
+     * {@link #provideSurface(Surface, Executor, Consumer)} or by a previous call to
      * {@code willNotProvideSurface()} and has not already been cancelled by the camera.
      */
     public boolean willNotProvideSurface() {
@@ -304,15 +327,16 @@
      *
      * <p>Once a surface request has been cancelled by the camera,
      * {@link #willNotProvideSurface()} will have no effect and will return {@code false}.
-     * Attempting to complete the request via {@link #provideSurface(Surface)} will also have no
-     * effect, and the returned {@link ListenableFuture} will contain a
-     * {@link RequestCancelledException}.
+     * Attempting to complete the request via {@link #provideSurface(Surface, Executor, Consumer)}
+     * will also have no effect, and any {@code resultListener} passed to
+     * {@link #provideSurface(Surface, Executor, Consumer)} will be invoked with a {@link Result}
+     * containing {@link Result#RESULT_REQUEST_CANCELLED}.
      *
      * <p>Note that due to the asynchronous nature of this listener, it is not guaranteed
      * that the listener will be called before an attempt to complete the request with
-     * {@link #provideSurface(Surface)} or {@link #willNotProvideSurface()}, so the return values of
-     * these methods can always be checked if your workflow for producing a surface expects them
-     * to complete successfully.
+     * {@link #provideSurface(Surface, Executor, Consumer)} or {@link #willNotProvideSurface()}, so
+     * the return values of these methods can always be checked if your workflow for producing a
+     * surface expects them to complete successfully.
      *
      * @param executor The executor used to notify the listener.
      * @param listener The listener which will be run upon request cancellation.
@@ -331,15 +355,132 @@
      * An exception used to signal that the camera has cancelled a request for a {@link Surface}.
      *
      * <p>This may be set on the {@link ListenableFuture} returned by
-     * {@link SurfaceRequest#provideSurface(Surface) when the camera is shutting down or when the
-     * surface resolution requirements have changed in order to accommodate newly bound use
-     * cases, in which case the {@link Runnable Runnables} provided to {
-     * @link #addRequestCancellationListener(Executor, Runnable)} will also be invoked on their
-     * respective {@link Executor Executors}.
+     * {@link SurfaceRequest#provideSurface(Surface, Executor, Consumer) when the camera is
+     * shutting down or when the surface resolution requirements have changed in order to
+     * accommodate newly bound use cases, in which case the {@linkplain Runnable runnables}
+     * provided to {@link #addRequestCancellationListener(Executor, Runnable)} will also be
+     * invoked on their respective {@link Executor Executors}.
      */
-    public static final class RequestCancelledException extends RuntimeException {
+    private static final class RequestCancelledException extends RuntimeException {
         RequestCancelledException(@NonNull String message, @NonNull Throwable cause) {
             super(message, cause);
         }
     }
+
+    /**
+     * Result of providing a surface to a {@link SurfaceRequest} via
+     * {@link #provideSurface(Surface, Executor, Consumer)}.
+     */
+    @AutoValue
+    public abstract static class Result {
+
+        /**
+         * Possible result codes.
+         * @hide
+         */
+        @IntDef({RESULT_SURFACE_USED_SUCCESSFULLY, RESULT_REQUEST_CANCELLED, RESULT_INVALID_SURFACE,
+                RESULT_SURFACE_ALREADY_PROVIDED, RESULT_WILL_NOT_PROVIDE_SURFACE})
+        @Retention(RetentionPolicy.SOURCE)
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+        public @interface ResultCode {
+        }
+
+        /**
+         * Provided surface was successfully used by the camera and eventually detached once no
+         * longer needed by the camera.
+         *
+         * <p>This result denotes that it is safe to release the {@link Surface} and any underlying
+         * resources.
+         *
+         * <p>For compatibility reasons, the {@link Surface} object should not be reused by
+         * future {@link SurfaceRequest SurfaceRequests}, and a new surface should be
+         * created instead.
+         */
+        public static final int RESULT_SURFACE_USED_SUCCESSFULLY = 0;
+
+        /**
+         * Provided surface was never attached to the camera due to the {@link SurfaceRequest} being
+         * cancelled by the camera.
+         *
+         * <p>It is safe to release or reuse {@link Surface}, assuming it was not previously
+         * attached to a camera via {@link #provideSurface(Surface, Executor, Consumer)}. If
+         * reusing the surface for a future surface request, it should be verified that the
+         * surface still matches the resolution specified by {@link SurfaceRequest#getResolution()}.
+         */
+        public static final int RESULT_REQUEST_CANCELLED = 1;
+
+        /**
+         * Provided surface could not be used by the camera.
+         *
+         * <p>This is likely due to the {@link Surface} being closed prematurely or the resolution
+         * of the surface not matching the resolution specified by
+         * {@link SurfaceRequest#getResolution()}.
+         */
+        public static final int RESULT_INVALID_SURFACE = 2;
+
+        /**
+         * Surface was not attached to the camera through this invocation of
+         * {@link #provideSurface(Surface, Executor, Consumer)} due to the {@link SurfaceRequest}
+         * already being complete with a surface.
+         *
+         * <p>The {@link SurfaceRequest} has already been completed by a previous invocation
+         * of {@link #provideSurface(Surface, Executor, Consumer)}.
+         *
+         * <p>It is safe to release or reuse the {@link Surface}, assuming it was not previously
+         * attached to a camera via {@link #provideSurface(Surface, Executor, Consumer)}.
+         */
+        public static final int RESULT_SURFACE_ALREADY_PROVIDED = 3;
+
+        /**
+         * Surface was not attached to the camera through this invocation of
+         * {@link #provideSurface(Surface, Executor, Consumer)} due to the {@link SurfaceRequest}
+         * already being marked as "will not provide surface".
+         *
+         * <p>The {@link SurfaceRequest} has already been marked as 'will not provide surface' by a
+         * previous invocation of {@link #willNotProvideSurface()}.
+         *
+         * <p>It is safe to release or reuse the {@link Surface}, assuming it was not previously
+         * attached to a camera via {@link #provideSurface(Surface, Executor, Consumer)}.
+         */
+        public static final int RESULT_WILL_NOT_PROVIDE_SURFACE = 4;
+
+        /**
+         * Creates a result from the given result code and surface.
+         *
+         * <p>Can be used to compare to results returned to {@code resultListener} in
+         * {@link #provideSurface(Surface, Executor, Consumer)}.
+         *
+         * @param code    One of {@link #RESULT_SURFACE_USED_SUCCESSFULLY},
+         *                {@link #RESULT_REQUEST_CANCELLED}, {@link #RESULT_INVALID_SURFACE},
+         *                {@link #RESULT_SURFACE_ALREADY_PROVIDED}, or
+         *                {@link #RESULT_WILL_NOT_PROVIDE_SURFACE}.
+         * @param surface The {@link Surface} used to complete the {@link SurfaceRequest}.
+         */
+        @NonNull
+        static Result of(@ResultCode int code, @NonNull Surface surface) {
+            return new AutoValue_SurfaceRequest_Result(code, surface);
+        }
+
+        /**
+         * Returns the result of invoking {@link #provideSurface(Surface, Executor, Consumer)}
+         * with the surface from {@link #getSurface()}.
+         * @return One of {@link #RESULT_SURFACE_USED_SUCCESSFULLY},
+         * {@link #RESULT_REQUEST_CANCELLED}, {@link #RESULT_INVALID_SURFACE}, or
+         * {@link #RESULT_SURFACE_ALREADY_PROVIDED}, {@link #RESULT_WILL_NOT_PROVIDE_SURFACE}.
+         */
+        @ResultCode
+        public abstract int getResultCode();
+
+        /**
+         * The surface used to complete a {@link SurfaceRequest} with
+         * {@link #provideSurface(Surface, Executor, Consumer)}.
+         * @return the surface.
+         */
+        @NonNull
+        public abstract Surface getSurface();
+
+        // Ensure Result can't be subclassed outside the package
+        Result() {
+        }
+    }
 }
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/SurfaceTextureProvider.java b/camera/camera-testing/src/main/java/androidx/camera/testing/SurfaceTextureProvider.java
index 19708c6..ecb5d25 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/SurfaceTextureProvider.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/SurfaceTextureProvider.java
@@ -44,7 +44,7 @@
      * Example:
      *
      * <pre><code>
-     * preview.setPreviewSurfaceProvider(createSurfaceTextureProvider(
+     * preview.setSurfaceProvider(createSurfaceTextureProvider(
      *         new SurfaceTextureProvider.SurfaceTextureCallback() {
      *             &#64;Override
      *             public void onSurfaceTextureReady(@NonNull SurfaceTexture surfaceTexture) {
@@ -74,10 +74,12 @@
             surfaceTextureCallback.onSurfaceTextureReady(surfaceTexture,
                     surfaceRequest.getResolution());
             Surface surface = new Surface(surfaceTexture);
-            surfaceRequest.provideSurface(surface).addListener(() -> {
-                surface.release();
-                surfaceTextureCallback.onSafeToRelease(surfaceTexture);
-            }, CameraXExecutors.directExecutor());
+            surfaceRequest.provideSurface(surface,
+                    CameraXExecutors.directExecutor(),
+                    (surfaceResponse) -> {
+                        surface.release();
+                        surfaceTextureCallback.onSafeToRelease(surfaceTexture);
+                    });
         };
     }
 
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/preview/transform/transformation/TransformationDeviceTest.java b/camera/camera-view/src/androidTest/java/androidx/camera/view/preview/transform/transformation/TransformationDeviceTest.java
new file mode 100644
index 0000000..df86666
--- /dev/null
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/preview/transform/transformation/TransformationDeviceTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.view.preview.transform.transformation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+import android.view.View;
+
+import androidx.camera.testing.fakes.FakeActivity;
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+public class TransformationDeviceTest {
+
+    @Rule
+    public final ActivityTestRule<FakeActivity> mRule = new ActivityTestRule<>(FakeActivity.class);
+
+    @Test
+    @UiThreadTest
+    public void getTransformationFromView() {
+        final Activity activity = mRule.getActivity();
+        final View view = new View(activity);
+        view.setScaleX(2F);
+        view.setScaleY(0.5F);
+        view.setTranslationX(100);
+        view.setTranslationY(35.5F);
+        view.setRotation(3.14F);
+        activity.setContentView(view);
+
+        final Transformation transformation = Transformation.getTransformation(view);
+
+        assertThat(transformation.getScaleX()).isEqualTo(2F);
+        assertThat(transformation.getScaleY()).isEqualTo(0.5F);
+        assertThat(transformation.getTransX()).isEqualTo(100);
+        assertThat(transformation.getTransY()).isEqualTo(35.5F);
+        assertThat(transformation.getRotation()).isEqualTo(3.14F);
+    }
+}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/SurfaceViewImplementation.java b/camera/camera-view/src/main/java/androidx/camera/view/SurfaceViewImplementation.java
index 339b3d2..b8c6316 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/SurfaceViewImplementation.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/SurfaceViewImplementation.java
@@ -135,9 +135,9 @@
             if (mSurfaceRequest != null && mTargetSize != null && mTargetSize.equals(
                     mCurrentSurfaceSize)) {
                 Log.d(TAG, "Surface set on Preview.");
-                mSurfaceRequest.provideSurface(surface).addListener(() -> {
-                    Log.d(TAG, "Safe to release surface.");
-                }, ContextCompat.getMainExecutor(mSurfaceView.getContext()));
+                mSurfaceRequest.provideSurface(surface,
+                        ContextCompat.getMainExecutor(mSurfaceView.getContext()),
+                        (result) -> Log.d(TAG, "Safe to release surface."));
                 mSurfaceRequest = null;
                 mTargetSize = null;
                 return true;
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/TextureViewImplementation.java b/camera/camera-view/src/main/java/androidx/camera/view/TextureViewImplementation.java
index f20a11b..430b5a3 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/TextureViewImplementation.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/TextureViewImplementation.java
@@ -16,6 +16,7 @@
 
 package androidx.camera.view;
 
+import static androidx.camera.core.SurfaceRequest.Result;
 import static androidx.camera.view.ScaleTypeTransform.getFillScaleWithBufferAspectRatio;
 import static androidx.camera.view.ScaleTypeTransform.getOriginOfCenteredView;
 import static androidx.camera.view.ScaleTypeTransform.getRotationDegrees;
@@ -31,12 +32,14 @@
 import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.camera.core.Preview;
 import androidx.camera.core.SurfaceRequest;
+import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.camera.core.impl.utils.futures.FutureCallback;
 import androidx.camera.core.impl.utils.futures.Futures;
+import androidx.concurrent.futures.CallbackToFutureAdapter;
 import androidx.core.content.ContextCompat;
+import androidx.core.util.Preconditions;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -51,7 +54,7 @@
     TextureView mTextureView;
     SurfaceTexture mSurfaceTexture;
     private Size mResolution;
-    ListenableFuture<Void> mSurfaceReleaseFuture;
+    ListenableFuture<Result> mSurfaceReleaseFuture;
     SurfaceRequest mSurfaceRequest;
 
     @Override
@@ -125,22 +128,23 @@
             public boolean onSurfaceTextureDestroyed(final SurfaceTexture surfaceTexture) {
                 mSurfaceTexture = null;
                 if (mSurfaceRequest == null && mSurfaceReleaseFuture != null) {
-                    Futures.addCallback(mSurfaceReleaseFuture, new FutureCallback<Void>() {
-                        @Override
-                        public void onSuccess(@Nullable Void result) {
-                            surfaceTexture.release();
-                        }
+                    Futures.addCallback(mSurfaceReleaseFuture,
+                            new FutureCallback<Result>() {
+                                @Override
+                                public void onSuccess(Result result) {
+                                    Preconditions.checkState(result.getResultCode()
+                                                    != Result.RESULT_SURFACE_ALREADY_PROVIDED,
+                                            "Unexpected result from SurfaceRequest. Surface was "
+                                                    + "provided twice.");
+                                    surfaceTexture.release();
+                                }
 
-                        @Override
-                        public void onFailure(Throwable t) {
-                            if (t instanceof SurfaceRequest.RequestCancelledException) {
-                                surfaceTexture.release();
-                            } else {
-                                throw new IllegalStateException("SurfaceReleaseFuture did not "
-                                        + "complete nicely.", t);
-                            }
-                        }
-                    }, ContextCompat.getMainExecutor(mTextureView.getContext()));
+                                @Override
+                                public void onFailure(Throwable t) {
+                                    throw new IllegalStateException("SurfaceReleaseFuture did not "
+                                            + "complete nicely.", t);
+                                }
+                            }, ContextCompat.getMainExecutor(mTextureView.getContext()));
                     return false;
                 } else {
                     return true;
@@ -176,7 +180,13 @@
         mSurfaceTexture.setDefaultBufferSize(mResolution.getWidth(), mResolution.getHeight());
 
         final Surface surface = new Surface(mSurfaceTexture);
-        final ListenableFuture<Void> surfaceReleaseFuture = mSurfaceRequest.provideSurface(surface);
+        final ListenableFuture<Result> surfaceReleaseFuture =
+                CallbackToFutureAdapter.getFuture(completer -> {
+                    mSurfaceRequest.provideSurface(surface,
+                            CameraXExecutors.directExecutor(), completer::set);
+                    return "provideSurface[request=" + mSurfaceRequest + " surface=" + surface
+                            + "]";
+                });
         mSurfaceReleaseFuture = surfaceReleaseFuture;
         mSurfaceReleaseFuture.addListener(() -> {
             surface.release();
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/CustomTransform.java b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/CustomTransform.java
index 69af67a..c90f52b 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/CustomTransform.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/CustomTransform.java
@@ -20,6 +20,7 @@
 import android.view.View;
 
 import androidx.annotation.NonNull;
+import androidx.camera.view.preview.transform.transformation.Transformation;
 
 final class CustomTransform {
 
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/PreviewTransform.java b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/PreviewTransform.java
index 95176d8..ba04666 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/PreviewTransform.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/PreviewTransform.java
@@ -23,6 +23,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
 import androidx.camera.view.PreviewView;
+import androidx.camera.view.preview.transform.transformation.Transformation;
 
 /**
  * Transforms the camera preview using a supported {@link PreviewView.ScaleType} or a custom
@@ -73,8 +74,8 @@
             @NonNull final View view) {
         view.setScaleX(transformation.getScaleX());
         view.setScaleY(transformation.getScaleY());
-        view.setX(transformation.getOriginX());
-        view.setY(transformation.getOriginY());
+        view.setTranslationX(transformation.getTransX());
+        view.setTranslationY(transformation.getTransY());
         view.setRotation(transformation.getRotation());
     }
 }
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/ScaleTypeTransform.java b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/ScaleTypeTransform.java
index 966b114..840dfcc 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/ScaleTypeTransform.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/ScaleTypeTransform.java
@@ -23,6 +23,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.camera.view.PreviewView;
+import androidx.camera.view.preview.transform.transformation.Transformation;
 
 final class ScaleTypeTransform {
 
@@ -30,8 +31,7 @@
     }
 
     /**
-     * Converts a {@link PreviewView.ScaleType} to a
-     * {@link androidx.camera.view.preview.transform.Transformation}.
+     * Converts a {@link PreviewView.ScaleType} to a {@link Transformation}.
      */
     static Transformation getTransformation(@NonNull final View container, @NonNull final View view,
             @NonNull final Size bufferSize, @NonNull final PreviewView.ScaleType scaleType) {
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/Transformation.java b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/Transformation.java
deleted file mode 100644
index 2e1b9c0..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/Transformation.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.view.preview.transform;
-
-/** Contains the required information to transform a camera preview. */
-final class Transformation {
-
-    private final float mScaleX;
-    private final float mScaleY;
-    private final float mOriginX;
-    private final float mOriginY;
-    private final float mRotation;
-
-    Transformation(final float scaleX, final float scaleY, final float originX,
-            final float originY, final float rotation) {
-        this.mScaleX = scaleX;
-        this.mScaleY = scaleY;
-        this.mOriginX = originX;
-        this.mOriginY = originY;
-        this.mRotation = rotation;
-    }
-
-    float getScaleX() {
-        return mScaleX;
-    }
-
-    float getScaleY() {
-        return mScaleY;
-    }
-
-    float getOriginX() {
-        return mOriginX;
-    }
-
-    float getOriginY() {
-        return mOriginY;
-    }
-
-    float getRotation() {
-        return mRotation;
-    }
-}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/transformation/Transformation.java b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/transformation/Transformation.java
new file mode 100644
index 0000000..d3bf25c
--- /dev/null
+++ b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/transformation/Transformation.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.view.preview.transform.transformation;
+
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+/**
+ * Contains the required information to transform a camera preview.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class Transformation {
+
+    private final float mScaleX;
+    private final float mScaleY;
+    private final float mTransX;
+    private final float mTransY;
+    private final float mRotation;
+
+    public Transformation() {
+        this(1, 1, 0, 0, 0);
+    }
+
+    public Transformation(final float scaleX, final float scaleY, final float transX,
+            final float transY, final float rotation) {
+        this.mScaleX = scaleX;
+        this.mScaleY = scaleY;
+        this.mTransX = transX;
+        this.mTransY = transY;
+        this.mRotation = rotation;
+    }
+
+    public float getScaleX() {
+        return mScaleX;
+    }
+
+    public float getScaleY() {
+        return mScaleY;
+    }
+
+    public float getTransX() {
+        return mTransX;
+    }
+
+    public float getTransY() {
+        return mTransY;
+    }
+
+    public float getRotation() {
+        return mRotation;
+    }
+
+    /** Performs the `sum` of two {@link Transformation} instances. */
+    @NonNull
+    public Transformation add(@NonNull final Transformation other) {
+        return new Transformation(mScaleX * other.mScaleX,
+                mScaleY * other.mScaleY,
+                mTransX + other.mTransX,
+                mTransY + other.mTransY,
+                mRotation + other.mRotation);
+    }
+
+    /** Performs the `subtraction` of two {@link Transformation} instances. */
+    @NonNull
+    public Transformation subtract(@NonNull final Transformation other) {
+        return new Transformation(mScaleX / other.mScaleX,
+                mScaleY / other.mScaleY,
+                mTransX - other.mTransX,
+                mTransY - other.mTransY,
+                mRotation - other.mRotation);
+    }
+
+    /** Returns a {@link Transformation} that represents the current state of the {@link View}. */
+    @NonNull
+    public static Transformation getTransformation(@NonNull final View view) {
+        return new Transformation(view.getScaleX(), view.getScaleY(), view.getTranslationX(),
+                view.getTranslationY(), view.getRotation());
+    }
+}
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/CustomTransformTest.java b/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/CustomTransformTest.java
index 9d1c8c94..c4d9bd5 100644
--- a/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/CustomTransformTest.java
+++ b/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/CustomTransformTest.java
@@ -24,6 +24,7 @@
 import android.graphics.Matrix;
 import android.view.View;
 
+import androidx.camera.view.preview.transform.transformation.Transformation;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
@@ -60,19 +61,6 @@
     }
 
     @Test
-    public void getTranslation() {
-        final float translationX = -200F;
-        final float translationY = 300F;
-        final Matrix matrix = new Matrix();
-        matrix.setTranslate(translationX, translationY);
-
-        final Transformation transformation = CustomTransform.getTransformation(mView, matrix);
-
-        assertThat(transformation.getOriginX()).isEqualTo(mView.getX() + translationX);
-        assertThat(transformation.getOriginY()).isEqualTo(mView.getY() + translationY);
-    }
-
-    @Test
     public void getRotation_lessThan360() {
         final float rotationDegrees = 47.5F;
         final Matrix matrix = new Matrix();
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/transformation/TransformationTest.java b/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/transformation/TransformationTest.java
new file mode 100644
index 0000000..fa4d437
--- /dev/null
+++ b/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/transformation/TransformationTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.view.preview.transform.transformation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class TransformationTest {
+
+    @Test
+    public void addTransformations() {
+        final Transformation transformation = new Transformation(2.3F, 7.5F, 100F, 23.4F, 3.14F);
+        final Transformation other = new Transformation(12.3F, 0.5F, 0F, 0.24F, 90F);
+
+        final Transformation sum = transformation.add(other);
+
+        assertThat(sum.getScaleX()).isEqualTo(transformation.getScaleX() * other.getScaleX());
+        assertThat(sum.getScaleY()).isEqualTo(transformation.getScaleY() * other.getScaleY());
+        assertThat(sum.getTransX()).isEqualTo(transformation.getTransX() + other.getTransX());
+        assertThat(sum.getTransY()).isEqualTo(transformation.getTransY() + other.getTransY());
+        assertThat(sum.getRotation()).isEqualTo(transformation.getRotation() + other.getRotation());
+    }
+
+    @Test
+    public void subtractTransformations() {
+        final Transformation transformation = new Transformation(2.3F, 7.5F, 100F, 23.4F, 3.14F);
+        final Transformation other = new Transformation(12.3F, 0.5F, 0F, 0.24F, 90F);
+
+        final Transformation sum = transformation.subtract(other);
+
+        assertThat(sum.getScaleX()).isEqualTo(transformation.getScaleX() / other.getScaleX());
+        assertThat(sum.getScaleY()).isEqualTo(transformation.getScaleY() / other.getScaleY());
+        assertThat(sum.getTransX()).isEqualTo(transformation.getTransX() - other.getTransX());
+        assertThat(sum.getTransY()).isEqualTo(transformation.getTransY() - other.getTransY());
+        assertThat(sum.getRotation()).isEqualTo(transformation.getRotation() - other.getRotation());
+    }
+}
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
index 3184546..2e7d072 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
@@ -131,7 +131,7 @@
     @SuppressWarnings("WeakerAccess")
     private Size mResolution;
     @SuppressWarnings("WeakerAccess")
-    ListenableFuture<Void> mSurfaceReleaseFuture;
+    ListenableFuture<SurfaceRequest.Result> mSurfaceReleaseFuture;
     @SuppressWarnings("WeakerAccess")
     SurfaceRequest mSurfaceRequest;
 
@@ -856,7 +856,13 @@
         mSurfaceTexture.setDefaultBufferSize(mResolution.getWidth(), mResolution.getHeight());
 
         final Surface surface = new Surface(mSurfaceTexture);
-        final ListenableFuture<Void> surfaceReleaseFuture = mSurfaceRequest.provideSurface(surface);
+        final ListenableFuture<SurfaceRequest.Result> surfaceReleaseFuture =
+                CallbackToFutureAdapter.getFuture(completer -> {
+                    mSurfaceRequest.provideSurface(surface,
+                            CameraXExecutors.directExecutor(), completer::set);
+                    return "provideSurface[request=" + mSurfaceRequest + " surface=" + surface
+                            + "]";
+                });
         mSurfaceReleaseFuture = surfaceReleaseFuture;
         mSurfaceReleaseFuture.addListener(() -> {
             surface.release();
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/CameraXController.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/CameraXController.kt
index 381362f..3c67dee 100644
--- a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/CameraXController.kt
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/CameraXController.kt
@@ -40,6 +40,7 @@
 import androidx.camera.integration.antelope.TestType
 import androidx.camera.lifecycle.ProcessCameraProvider
 import androidx.concurrent.futures.await
+import androidx.core.util.Consumer
 import androidx.lifecycle.LifecycleOwner
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.GlobalScope
@@ -118,13 +119,10 @@
 
                 // Surface provided to camera for producing buffers into and
                 // Release the SurfaceTexture and Surface once camera is done with it
-                surfaceRequest.provideSurface(surface).addListener(
-                    Runnable {
+                surfaceRequest.provideSurface(surface, CameraXExecutors.directExecutor(), Consumer {
                         surface.release()
                         surfaceTexture.release()
-                    },
-                    CameraXExecutors.directExecutor()
-                )
+                })
             }
         }
 
diff --git a/development/importMaven/build.gradle.kts b/development/importMaven/build.gradle.kts
index ca40116..164eade 100644
--- a/development/importMaven/build.gradle.kts
+++ b/development/importMaven/build.gradle.kts
@@ -18,18 +18,8 @@
 import okhttp3.OkHttpClient
 import okhttp3.Request
 import okhttp3.RequestBody
-import org.apache.maven.model.Dependency
-import org.apache.maven.model.Parent
-import org.apache.maven.model.Repository
-import org.apache.maven.model.building.DefaultModelBuilderFactory
-import org.apache.maven.model.building.DefaultModelBuildingRequest
-import org.apache.maven.model.building.ModelBuildingException
-import org.apache.maven.model.building.ModelBuildingRequest
-import org.apache.maven.model.building.ModelSource
-import org.apache.maven.model.resolution.ModelResolver
 import org.w3c.dom.Element
 import org.w3c.dom.Node
-import java.io.InputStream
 import java.security.MessageDigest
 import javax.xml.parsers.DocumentBuilderFactory
 import javax.xml.transform.OutputKeys
@@ -45,72 +35,10 @@
     }
 
     dependencies {
-        classpath(gradleApi())
         classpath("org.apache.maven:maven-model:3.5.4")
         classpath("org.apache.maven:maven-model-builder:3.5.4")
         classpath("com.squareup.okhttp3:okhttp:3.11.0")
-    }
-}
-
-typealias MavenListener = (String, String, String, File) -> Unit
-
-/**
- * A Maven module resolver which uses Gradle.
- */
-class MavenModuleResolver(val project: Project, val listener: MavenListener) : ModelResolver {
-
-    override fun resolveModel(dependency: Dependency): ModelSource? {
-        return resolveModel(dependency.groupId, dependency.artifactId, dependency.version)
-    }
-
-    override fun resolveModel(parent: Parent): ModelSource? {
-        return resolveModel(parent.groupId, parent.artifactId, parent.version)
-    }
-
-    override fun resolveModel(
-        groupId: String,
-        artifactId: String,
-        version: String
-    ): ModelSource? {
-        val pomQuery = project.dependencies.createArtifactResolutionQuery()
-        val pomQueryResult = pomQuery.forModule(groupId, artifactId, version)
-            .withArtifacts(
-                MavenModule::class.java,
-                MavenPomArtifact::class.java
-            )
-            .execute()
-        var result: File? = null
-        for (component in pomQueryResult.resolvedComponents) {
-            val pomArtifacts = component.getArtifacts(MavenPomArtifact::class.java)
-            for (pomArtifact in pomArtifacts) {
-                val pomFile = pomArtifact as? ResolvedArtifactResult
-                if (pomFile != null) {
-                    result = pomFile.file
-                    listener.invoke(groupId, artifactId, version, result)
-                }
-            }
-        }
-        return object : ModelSource {
-            override fun getInputStream(): InputStream {
-                return result!!.inputStream()
-            }
-
-            override fun getLocation(): String {
-                return result!!.absolutePath
-            }
-        }
-    }
-
-    override fun addRepository(repository: Repository?) {
-        // We don't need to support this
-    }
-
-    override fun addRepository(repository: Repository?, replace: Boolean) {
-        // We don't need to support this
-    }
-
-    override fun newCopy(): ModelResolver {
-        return this
+        classpath("javax.inject:javax.inject:1")
     }
 }
 
@@ -118,9 +46,6 @@
 val prebuiltsLocation = file("../../../../prebuilts/androidx")
 val internalFolder = "internal"
 val externalFolder = "external"
-val configurationName = "fetchArtifacts"
-val fetchArtifacts = configurations.create(configurationName)
-val fetchArtifactsContainer = configurations.getByName(configurationName)
 // Passed in as a project property
 val artifactName = project.findProperty("artifactName")
 val mediaType = MediaType.get("application/json; charset=utf-8")
@@ -176,26 +101,38 @@
     }
 }
 
+val allFilesWithDependencies: Configuration by configurations.creating {
+    attributes {
+        // We define this attribute in DirectMetadataAccessVariantRule
+        attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named("all-files-with-dependencies"))
+    }
+    extendsFrom(configurations.runtimeClasspath.get())
+}
+
 if (artifactName != null) {
     dependencies {
         // This is the configuration container that we use to lookup the
         // transitive closure of all dependencies.
-        fetchArtifacts(artifactName)
+        implementation(artifactName)
+
+        // For metadata access
+        components {
+            all<DirectMetadataAccessVariantRule>()
+        }
     }
 }
 
 /**
- * Returns the list of libraries that are *internal*.
+ * Checks if an artifact is *internal*.
  */
-fun filterInternalLibraries(artifacts: Set<ResolvedArtifact>): Set<ResolvedArtifact> {
-    return artifacts.filter {
-        val moduleVersionId = it.moduleVersion.id
-        val group = moduleVersionId.group
-
+fun isInternalArtifact(artifact: ResolvedArtifactResult): Boolean {
+    val component = artifact.id.componentIdentifier as? ModuleComponentIdentifier
+    if (component != null) {
+        val group = component.group
         for (regex in internalArtifacts) {
             val match = regex.matches(group)
             if (match) {
-                return@filter regex.matches(group)
+                return true
             }
         }
 
@@ -206,78 +143,11 @@
                         !forceExternal.contains(sub)
                     } ?: true
             if (match) {
-                return@filter true
-            }
-        }
-        false
-    }.toSet()
-}
-
-/**
- * Returns the supporting files (POM, Source files) for a given artifact.
- */
-fun supportingArtifacts(
-    artifact: ResolvedArtifact,
-    internal: Boolean = false
-): List<ResolvedArtifactResult> {
-    val supportingArtifacts = mutableListOf<ResolvedArtifactResult>()
-    val pomQuery = project.dependencies.createArtifactResolutionQuery()
-    val modelBuilderFactory = DefaultModelBuilderFactory()
-    val builder = modelBuilderFactory.newInstance()
-    val resolver = MavenModuleResolver(project) { groupId, artifactId, version, pomFile ->
-        copyPomFile(groupId, artifactId, version, pomFile, internal)
-    }
-    val pomQueryResult = pomQuery.forComponents(artifact.id.componentIdentifier)
-        .withArtifacts(
-            MavenModule::class.java,
-            MavenPomArtifact::class.java
-        )
-        .execute()
-
-    for (component in pomQueryResult.resolvedComponents) {
-        val pomArtifacts = component.getArtifacts(MavenPomArtifact::class.java)
-        for (pomArtifact in pomArtifacts) {
-            val pomFile = pomArtifact as? ResolvedArtifactResult
-            if (pomFile != null) {
-                try {
-                    val request: ModelBuildingRequest = DefaultModelBuildingRequest()
-                    request.modelResolver = resolver
-                    request.pomFile = pomFile.file
-                    // Turn off validations becuase there are lots of bad POM files out there.
-                    request.validationLevel = ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL
-                    builder.build(request).effectiveModel
-                } catch (exception: ModelBuildingException) {
-                    println("Error building model request for $pomArtifact")
-                }
-                supportingArtifacts.add(pomFile)
+                return true
             }
         }
     }
-
-    // Create a separate query for a sources. This is because, withArtifacts seems to be an AND.
-    // So if artifacts only have a distributable without a source, we still want to copy the POM file.
-    val sourcesQuery = project.dependencies.createArtifactResolutionQuery()
-    val sourcesQueryResult = sourcesQuery.forComponents(artifact.id.componentIdentifier)
-        .withArtifacts(
-            JvmLibrary::class.java,
-            SourcesArtifact::class.java
-        )
-        .execute()
-
-    if (sourcesQueryResult.resolvedComponents.size > 0) {
-        for (component in sourcesQueryResult.resolvedComponents) {
-            val sourcesArtifacts = component.getArtifacts(SourcesArtifact::class.java)
-            for (sourcesArtifact in sourcesArtifacts) {
-                val sourcesFile = sourcesArtifact as? ResolvedArtifactResult
-                if (sourcesFile != null) {
-                    supportingArtifacts.add(sourcesFile)
-                }
-            }
-        }
-    } else {
-        project.logger.warn("No sources found for $artifact")
-    }
-    return supportingArtifacts
+    return false
 }
 
 /**
@@ -407,42 +277,32 @@
 /**
  * Copies artifacts to the right locations.
  */
-fun copyArtifact(artifact: ResolvedArtifact, internal: Boolean = false) {
+fun copyArtifact(artifact: ResolvedArtifactResult, internal: Boolean = false) {
     val folder = if (internal) internalFolder else externalFolder
-    val moduleVersionId = artifact.moduleVersion.id
-    val group = moduleVersionId.group
-    val groupPath = groupToPath(group)
-    val pathComponents = listOf(
-        prebuiltsLocation,
-        folder,
-        groupPath,
-        moduleVersionId.name,
-        moduleVersionId.version
-    )
-    val location = pathComponents.joinToString("/")
-    val supportingArtifacts = supportingArtifacts(artifact, internal = internal)
-    // Copy main artifact
-    println("Copying $artifact to $location")
-    copy {
-        from(
-            artifact.file,
-            digest(artifact.file, "MD5"),
-            digest(artifact.file, "SHA1")
+    val file = artifact.file
+    val component = artifact.id.componentIdentifier as? ModuleComponentIdentifier
+    if (component != null) {
+        val group = component.group
+        val moduleName = component.module
+        val moduleVersion = component.version
+        val groupPath = groupToPath(group)
+        val pathComponents = listOf(
+            prebuiltsLocation,
+            folder,
+            groupPath,
+            moduleName,
+            moduleVersion
         )
-        into(location)
-    }
-    // Copy supporting artifacts
-    for (supportingArtifact in supportingArtifacts) {
-        val file = supportingArtifact.file
+        val location = pathComponents.joinToString("/")
+        println("Copying $name to $location")
         if (file.name.endsWith(".pom")) {
-            copyPomFile(group, moduleVersionId.name, moduleVersionId.version, file, internal)
+            copyPomFile(group, moduleName, moduleVersion, file, internal)
         } else {
-            println("Copying $supportingArtifact to $location")
             copy {
                 from(
-                    supportingArtifact.file,
-                    digest(supportingArtifact.file, "MD5"),
-                    digest(supportingArtifact.file, "SHA1")
+                    file,
+                    digest(file, "MD5"),
+                    digest(file, "SHA1")
                 )
                 into(location)
             }
@@ -512,35 +372,64 @@
     }
 }
 
+/**
+ * This rule runs in a sandbox, and does not have access ot things in scope which it should usually
+ * have access to. This is why the constant `all-files-with-dependencies` is being duplicated.
+ */
+@CacheableRule
+open class DirectMetadataAccessVariantRule : ComponentMetadataRule {
+
+    @javax.inject.Inject
+    open fun getObjects(): ObjectFactory = throw UnsupportedOperationException()
+
+    override fun execute(ctx: ComponentMetadataContext) {
+        val id = ctx.details.id
+        ctx.details.maybeAddVariant("allFilesWithDependenciesElements", "runtimeElements") {
+            attributes {
+                attribute(Usage.USAGE_ATTRIBUTE, getObjects().named(Usage.JAVA_RUNTIME))
+                attribute(Category.CATEGORY_ATTRIBUTE, getObjects().named(Category.DOCUMENTATION))
+                attribute(
+                    DocsType.DOCS_TYPE_ATTRIBUTE,
+                    getObjects().named("all-files-with-dependencies")
+                )
+            }
+            withFiles {
+                addFile("${id.name}-${id.version}.pom")
+                addFile("${id.name}-${id.version}.module")
+                addFile("${id.name}-${id.version}.jar")
+                addFile("${id.name}-${id.version}-sources.jar")
+            }
+        }
+        ctx.details.maybeAddVariant("allFilesWithDependencies", "runtime") {
+            attributes {
+                attribute(Usage.USAGE_ATTRIBUTE, getObjects().named(Usage.JAVA_RUNTIME))
+                attribute(Category.CATEGORY_ATTRIBUTE, getObjects().named(Category.DOCUMENTATION))
+                attribute(
+                    DocsType.DOCS_TYPE_ATTRIBUTE,
+                    getObjects().named("all-files-with-dependencies")
+                )
+            }
+            withFiles {
+                addFile("${id.name}-${id.version}.pom")
+                // No harm in leaving it in here.
+                addFile("${id.name}-${id.version}.module")
+                addFile("${id.name}-${id.version}.jar")
+                addFile("${id.name}-${id.version}-sources.jar")
+            }
+        }
+    }
+}
+
 tasks {
     val fetchArtifacts by creating {
         doLast {
-            // Collect all the internal and external dependencies.
-            // Copy the jar/aar's and their respective POM files.
-            val internalLibraries =
-                filterInternalLibraries(
-                    fetchArtifactsContainer
-                        .resolvedConfiguration
-                        .resolvedArtifacts
-                )
-
-            val externalLibraries =
-                fetchArtifactsContainer
-                    .resolvedConfiguration
-                    .resolvedArtifacts.filter {
-                    val isInternal = internalLibraries.contains(it)
-                    !isInternal
-                }
-
-            println("\r\nInternal Libraries")
-            internalLibraries.forEach { library ->
-                copyArtifact(library, internal = true)
+            println("\r\nAll Files with Dependencies")
+            allFilesWithDependencies.incoming.artifactView {
+                lenient(true)
+            }.artifacts.forEach {
+                copyArtifact(it, internal = isInternalArtifact(it))
             }
 
-            println("\r\nExternal Libraries")
-            externalLibraries.forEach { library ->
-                copyArtifact(library, internal = false)
-            }
             println("\r\nResolved artifacts for $artifactName.")
         }
     }
diff --git a/development/importMaven/import_maven_artifacts.py b/development/importMaven/import_maven_artifacts.py
index 319a600..5fbf9fa 100755
--- a/development/importMaven/import_maven_artifacts.py
+++ b/development/importMaven/import_maven_artifacts.py
@@ -38,6 +38,7 @@
     artifact_name = parse_result.name
     if ("kotlin-native-linux" in artifact_name): artifact_name = fix_kotlin_native(artifact_name)
 
+    # Add -Dorg.gradle.debug=true to debug or --stacktrace to see the stack trace
     command = './gradlew --build-file build.gradle.kts -PartifactName=%s' % (
         artifact_name)
     process = subprocess.Popen(command,
diff --git a/fragment/fragment-lint/src/main/java/androidx/fragment/lint/UseRequireInsteadOfGet.kt b/fragment/fragment-lint/src/main/java/androidx/fragment/lint/UseRequireInsteadOfGet.kt
index 773e9da..9650efa 100644
--- a/fragment/fragment-lint/src/main/java/androidx/fragment/lint/UseRequireInsteadOfGet.kt
+++ b/fragment/fragment-lint/src/main/java/androidx/fragment/lint/UseRequireInsteadOfGet.kt
@@ -32,11 +32,14 @@
 import org.jetbrains.uast.UCallExpression
 import org.jetbrains.uast.UElement
 import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.ULocalVariable
 import org.jetbrains.uast.UPostfixExpression
 import org.jetbrains.uast.UQualifiedReferenceExpression
 import org.jetbrains.uast.USimpleNameReferenceExpression
 import org.jetbrains.uast.getContainingUClass
 import org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression
+import org.jetbrains.uast.toUElement
+import org.jetbrains.uast.tryResolve
 import java.util.Locale
 
 /**
@@ -120,6 +123,14 @@
                 resolveEnclosingClass: () -> PsiClass?
             ) {
                 if (identifier in REQUIRABLE_REFERENCES) {
+                    // If this is a local variable do nothing
+                    // We are doing this to avoid false positives on local variables that shadow
+                    // Kotlin property accessors.  There is probably a better way to organize
+                    // this Lint rule.
+                    val element = node.tryResolve()
+                    if (element != null && element.toUElement() is ULocalVariable) {
+                        return
+                    }
                     val enclosingClass = resolveEnclosingClass() ?: return
                     if (context.evaluator.extendsClass(enclosingClass, FRAGMENT_FQCN, false)) {
                         checkForIssue(node, identifier)
diff --git a/fragment/fragment-lint/src/test/java/androidx/fragment/lint/UseRequireInsteadOfGetTest.kt b/fragment/fragment-lint/src/test/java/androidx/fragment/lint/UseRequireInsteadOfGetTest.kt
index 4ac1e5f..7752398 100644
--- a/fragment/fragment-lint/src/test/java/androidx/fragment/lint/UseRequireInsteadOfGetTest.kt
+++ b/fragment/fragment-lint/src/test/java/androidx/fragment/lint/UseRequireInsteadOfGetTest.kt
@@ -952,5 +952,59 @@
         """.trimIndent()
             )
     }
+
+    @Test
+    fun `view local variables should be ignored`() {
+        useRequireLint()
+            .files(
+                fragmentStub,
+                preconditionsStub,
+                java(
+                    """
+                  package foo;
+
+                  import androidx.fragment.app.Fragment;
+                  import util.Preconditions;
+
+                  class TestFragment extends Fragment {
+                    void test() {
+                      View view = null;
+                      Preconditions.checkNotNull(view);
+                    }
+                  }
+                """
+                ).indented()
+            )
+            .allowCompilationErrors(false)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun `activity local variables should be ignored`() {
+        useRequireLint()
+            .files(
+                fragmentStub,
+                preconditionsStub,
+                java(
+                    """
+                  package foo;
+
+                  import androidx.fragment.app.Fragment;
+                  import util.Preconditions;
+
+                  class TestFragment extends Fragment {
+                    void test() {
+                      Activity activity = null;
+                      Preconditions.checkNotNull(activity);
+                    }
+                  }
+                """
+                ).indented()
+            )
+            .allowCompilationErrors(false)
+            .run()
+            .expectClean()
+    }
 }
 /* ktlint-enable max-line-length */
diff --git a/fragment/fragment/proguard-rules.pro b/fragment/fragment/proguard-rules.pro
index e5da11b..b650671 100644
--- a/fragment/fragment/proguard-rules.pro
+++ b/fragment/fragment/proguard-rules.pro
@@ -14,6 +14,6 @@
 
 # The default FragmentFactory creates Fragment instances using reflection
 -if public class ** extends androidx.fragment.app.Fragment
--keep public class ** extends androidx.fragment.app.Fragment {
+-keep public class <1> {
     public <init>();
 }
diff --git a/media2/session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java b/media2/session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java
index fbd623e..b9bef0a 100644
--- a/media2/session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java
+++ b/media2/session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java
@@ -66,6 +66,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -1107,6 +1108,7 @@
     }
 
     @Test
+    @Ignore("This tests hidden API, which isn't public at this moment.")
     public void testPlayFromSearch() throws Exception {
         final String request = "random query";
         final Bundle bundle = new Bundle();
@@ -1136,6 +1138,7 @@
     }
 
     @Test
+    @Ignore("This tests hidden API, which isn't public at this moment.")
     public void testPlayFromUri() throws Exception {
         final Uri request = Uri.parse("foo://boo");
         final Bundle bundle = new Bundle();
@@ -1164,6 +1167,7 @@
     }
 
     @Test
+    @Ignore("This tests hidden API, which isn't public at this moment.")
     public void testPlayFromMediaId() throws Exception {
         final String request = "media_id";
         final Bundle bundle = new Bundle();
@@ -1192,6 +1196,7 @@
     }
 
     @Test
+    @Ignore("This tests hidden API, which isn't public at this moment.")
     public void testPrepareFromSearch() throws Exception {
         final String request = "random query";
         final Bundle bundle = new Bundle();
@@ -1220,6 +1225,7 @@
     }
 
     @Test
+    @Ignore("This tests hidden API, which isn't public at this moment.")
     public void testPrepareFromUri() throws Exception {
         final Uri request = Uri.parse("foo://boo");
         final Bundle bundle = new Bundle();
@@ -1248,6 +1254,7 @@
     }
 
     @Test
+    @Ignore("This tests hidden API, which isn't public at this moment.")
     public void testPrepareFromMediaId() throws Exception {
         final String request = "media_id";
         final Bundle bundle = new Bundle();
diff --git a/media2/session/src/main/java/androidx/media2/session/SessionCommand.java b/media2/session/src/main/java/androidx/media2/session/SessionCommand.java
index 685a23a..30c0074 100644
--- a/media2/session/src/main/java/androidx/media2/session/SessionCommand.java
+++ b/media2/session/src/main/java/androidx/media2/session/SessionCommand.java
@@ -568,12 +568,6 @@
                         COMMAND_CODE_SESSION_REWIND,
                         COMMAND_CODE_SESSION_SKIP_FORWARD,
                         COMMAND_CODE_SESSION_SKIP_BACKWARD,
-                        COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID,
-                        COMMAND_CODE_SESSION_PLAY_FROM_SEARCH,
-                        COMMAND_CODE_SESSION_PLAY_FROM_URI,
-                        COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID,
-                        COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH,
-                        COMMAND_CODE_SESSION_PREPARE_FROM_URI,
                         COMMAND_CODE_SESSION_SET_RATING));
     }
 
diff --git a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTest.java b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTest.java
index a9ecb03..ec476a0 100644
--- a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTest.java
+++ b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTest.java
@@ -49,6 +49,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -320,6 +321,7 @@
     }
 
     @Test
+    @Ignore("This tests hidden API, which isn't public at this moment.")
     public void testOnPlayFromSearch() throws InterruptedException {
         final String testQuery = "random query";
         final Bundle testExtras = TestUtils.createTestBundle();
@@ -346,6 +348,7 @@
     }
 
     @Test
+    @Ignore("This tests hidden API, which isn't public at this moment.")
     public void testOnPlayFromUri() throws InterruptedException {
         final Uri testUri = Uri.parse("foo://boo");
         final Bundle testExtras = TestUtils.createTestBundle();
@@ -373,6 +376,7 @@
     }
 
     @Test
+    @Ignore("This tests hidden API, which isn't public at this moment.")
     public void testOnPlayFromMediaId() throws InterruptedException {
         final String testMediaId = "media_id";
         final Bundle testExtras = TestUtils.createTestBundle();
@@ -399,6 +403,7 @@
     }
 
     @Test
+    @Ignore("This tests hidden API, which isn't public at this moment.")
     public void testOnPrepareFromSearch() throws InterruptedException {
         final String testQuery = "random query";
         final Bundle testExtras = TestUtils.createTestBundle();
@@ -425,6 +430,7 @@
     }
 
     @Test
+    @Ignore("This tests hidden API, which isn't public at this moment.")
     public void testOnPrepareFromUri() throws InterruptedException {
         final Uri testUri = Uri.parse("foo://boo");
         final Bundle testExtras = TestUtils.createTestBundle();
@@ -451,6 +457,7 @@
     }
 
     @Test
+    @Ignore("This tests hidden API, which isn't public at this moment.")
     public void testOnPrepareFromMediaId() throws InterruptedException {
         final String testMediaId = "media_id";
         final Bundle testExtras = TestUtils.createTestBundle();
diff --git a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTestWithMediaControllerCompat.java b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTestWithMediaControllerCompat.java
index df1a484..a671060 100644
--- a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTestWithMediaControllerCompat.java
+++ b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTestWithMediaControllerCompat.java
@@ -67,6 +67,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -572,6 +573,7 @@
     }
 
     @Test
+    @Ignore("This tests hidden API, which isn't public at this moment.")
     public void testPlayFromSearch() throws InterruptedException {
         final String request = "random query";
         final Bundle bundle = new Bundle();
@@ -600,6 +602,7 @@
     }
 
     @Test
+    @Ignore("This tests hidden API, which isn't public at this moment.")
     public void testPlayFromUri() throws InterruptedException {
         final Uri request = Uri.parse("foo://boo");
         final Bundle bundle = new Bundle();
@@ -627,6 +630,7 @@
     }
 
     @Test
+    @Ignore("This tests hidden API, which isn't public at this moment.")
     public void testPlayFromMediaId() throws InterruptedException {
         final String request = "media_id";
         final Bundle bundle = new Bundle();
@@ -654,6 +658,7 @@
     }
 
     @Test
+    @Ignore("This tests hidden API, which isn't public at this moment.")
     public void testPrepareFromSearch() throws InterruptedException {
         final String request = "random query";
         final Bundle bundle = new Bundle();
@@ -681,6 +686,7 @@
     }
 
     @Test
+    @Ignore("This tests hidden API, which isn't public at this moment.")
     public void testPrepareFromUri() throws InterruptedException {
         final Uri request = Uri.parse("foo://boo");
         final Bundle bundle = new Bundle();
@@ -708,6 +714,7 @@
     }
 
     @Test
+    @Ignore("This tests hidden API, which isn't public at this moment.")
     public void testPrepareFromMediaId() throws InterruptedException {
         final String request = "media_id";
         final Bundle bundle = new Bundle();
diff --git a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/SessionCommandTest.java b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/SessionCommandTest.java
index 872121a..02fb989 100644
--- a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/SessionCommandTest.java
+++ b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/SessionCommandTest.java
@@ -18,6 +18,7 @@
 
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.os.Build;
@@ -118,6 +119,19 @@
         }
     }
 
+    @Test
+    public void testAddAllPredefinedCommands_withVersion1_notHaveHiddenCodes() {
+        SessionCommandGroup.Builder builder = new SessionCommandGroup.Builder();
+        builder.addAllPredefinedCommands(SessionCommand.COMMAND_VERSION_1);
+        SessionCommandGroup commands = builder.build();
+        assertFalse(commands.hasCommand(SessionCommand.COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID));
+        assertFalse(commands.hasCommand(SessionCommand.COMMAND_CODE_SESSION_PLAY_FROM_URI));
+        assertFalse(commands.hasCommand(SessionCommand.COMMAND_CODE_SESSION_PLAY_FROM_SEARCH));
+        assertFalse(commands.hasCommand(SessionCommand.COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID));
+        assertFalse(commands.hasCommand(SessionCommand.COMMAND_CODE_SESSION_PREPARE_FROM_URI));
+        assertFalse(commands.hasCommand(SessionCommand.COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH));
+    }
+
     private static List<Field> getSessionCommandsFields(String prefix) {
         final List<Field> list = new ArrayList<>();
         final Field[] fields = SessionCommand.class.getFields();
diff --git a/navigation/navigation-common-ktx/proguard-rules.pro b/navigation/navigation-common-ktx/proguard-rules.pro
index efe165c..d633c02 100644
--- a/navigation/navigation-common-ktx/proguard-rules.pro
+++ b/navigation/navigation-common-ktx/proguard-rules.pro
@@ -14,6 +14,6 @@
 
 # NavArgsLazy creates NavArgs instances using reflection
 -if public class ** implements androidx.navigation.NavArgs
--keepclassmembers public class ** implements androidx.navigation.NavArgs {
+-keepclassmembers public class <1> {
     ** fromBundle(android.os.Bundle);
-}
\ No newline at end of file
+}
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
index a41637e..fcf324d 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
@@ -40,6 +40,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
 import androidx.testutils.TestNavigator
 import androidx.testutils.test
 import com.google.common.truth.Truth.assertThat
@@ -366,6 +367,7 @@
 
     @LargeTest
     @Test
+    @SdkSuppress(minSdkVersion = 17)
     fun testNavigateViaImplicitDeepLink() {
         val intent = Intent(
             Intent.ACTION_VIEW,
@@ -401,6 +403,7 @@
             assertThat(this.state).isEqualTo(Lifecycle.State.DESTROYED)
         }
 
+        // this relies on MonitoringInstrumentation.execStartActivity() which was added in API 17
         intended(
             allOf(
                 toPackage((ApplicationProvider.getApplicationContext() as Context).packageName),
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt b/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
index 00fdd76..615658a 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
@@ -22,6 +22,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.withContext
+import kotlinx.coroutines.yield
 import java.util.concurrent.atomic.AtomicBoolean
 
 /** @suppress */
@@ -48,6 +49,8 @@
         lastAccessedIndex: Int
     ): Int?
 
+    open fun postEvents(): Boolean = false
+
     @UseExperimental(ExperimentalCoroutinesApi::class)
     suspend fun collectFrom(pagingData: PagingData<T>, callback: PresenterCallback) {
         check(collecting.compareAndSet(false, true)) {
@@ -81,6 +84,9 @@
                                 receiver?.addHint(presenter.loadAround(newIndex))
                             }
                         } else {
+                            if (postEvents()) {
+                                yield()
+                            }
                             presenter.processEvent(event, callback)
                         }
                     }
diff --git a/paging/runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt b/paging/runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
index b957b53..c30733b 100644
--- a/paging/runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
+++ b/paging/runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
@@ -73,6 +73,9 @@
         }
     }
 
+    /** True if we're currently executing [getItem] */
+    internal var inGetItem: Boolean = false
+
     private val differBase = object : PagingDataDiffer<T>(mainDispatcher) {
         override suspend fun performDiff(
             previousList: NullPaddedList<T>,
@@ -109,6 +112,16 @@
                 }
             }
         }
+
+        /**
+         * Return if [getItem] is running to post any data modifications.
+         *
+         * This must be done because RecyclerView can't be modified during an onBind, when
+         * [getItem] is generally called.
+         */
+        override fun postEvents(): Boolean {
+            return inGetItem
+        }
     }
 
     suspend fun collectFrom(pagingData: PagingData<T>) {
@@ -131,7 +144,14 @@
      * @param index Index of item to get, must be >= 0, and < [itemCount]
      * @return The item, or null, if a null placeholder is at the specified position.
      */
-    open fun getItem(index: Int): T? = differBase[index]
+    open fun getItem(index: Int): T? {
+        try {
+            inGetItem = true
+            return differBase[index]
+        } finally {
+            inGetItem = false
+        }
+    }
 
     /**
      * Get the number of items currently presented by this Differ. This value can be directly
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/MergeAdapterTest.kt b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/MergeAdapterTest.kt
index f75050f..94fc581 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/MergeAdapterTest.kt
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/MergeAdapterTest.kt
@@ -66,6 +66,18 @@
         }
     }
 
+    @Test(expected = UnsupportedOperationException::class)
+    fun cannotCallSetStableIds_true() {
+        val merge = MergeAdapter()
+        merge.setHasStableIds(true)
+    }
+
+    @Test(expected = UnsupportedOperationException::class)
+    fun cannotCallSetStableIds_false() {
+        val merge = MergeAdapter()
+        merge.setHasStableIds(false)
+    }
+
     @UiThreadTest
     @Test
     fun attachAndDetachAll() {
@@ -793,19 +805,10 @@
         }
     }
 
-    @Test
-    fun stateRestrorationTest_callingPublicMerthodIsIgnored() {
-        val adapter = NestedTestAdapter(3).also {
-            it.stateRestorationStrategy = PREVENT
-        }
-        val merge = MergeAdapter(adapter)
-        assertThat(merge).hasStateRestorationStrategy(PREVENT)
-        merge.stateRestorationStrategy = ALLOW
-        assertThat(merge).hasStateRestorationStrategy(PREVENT)
-        merge.stateRestorationStrategy = PREVENT_WHEN_EMPTY
-        assertThat(merge).hasStateRestorationStrategy(PREVENT)
-        adapter.stateRestorationStrategy = ALLOW
-        assertThat(merge).hasStateRestorationStrategy(ALLOW)
+    @Test(expected = java.lang.UnsupportedOperationException::class)
+    fun stateRestorationTest_callingOnTheMergeAdapterIsNotAllowed() {
+        val merge = MergeAdapter()
+        merge.stateRestorationStrategy = PREVENT
     }
 
     @Test
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/MergeAdapter.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/MergeAdapter.java
index 784d989..8d39b3b 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/MergeAdapter.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/MergeAdapter.java
@@ -18,7 +18,6 @@
 
 import static androidx.recyclerview.widget.MergeAdapter.Config.StableIdMode.NO_STABLE_IDS;
 
-import android.util.Log;
 import android.view.ViewGroup;
 
 import androidx.annotation.NonNull;
@@ -186,33 +185,35 @@
     }
 
     /**
-     * MergeAdapter does not support stable ids yet.
-     * Calling this method with {@code true} will result in an {@link IllegalArgumentException}.
+     * Calling this method is an error and will result in an {@link UnsupportedOperationException}.
+     * You should use the {@link Config} object passed into the MergeAdapter to configure this
+     * behavior.
      *
      * @param hasStableIds Whether items in data set have unique identifiers or not.
      */
     @Override
     public void setHasStableIds(boolean hasStableIds) {
-        Log.w(TAG, "Calling setHasStableIds has no impact as MergeAdapter's stable id setting "
-                + "is preset by the given configuration");
+        throw new UnsupportedOperationException(
+                "Calling setHasStableIds is not allowed on the MergeAdapter. "
+                        + "Use the Config object passed in the constructor to control this "
+                        + "behavior");
     }
 
     /**
-     * Calling this method on the MergeAdapter has no impact as the MergeAdapter infers this
-     * value from added sub adapters.
+     * Calling this method is an error and will result in an {@link UnsupportedOperationException}.
      *
-     * {@link MergeAdapter} picks the least allowing strategy from given adapters. So if a nested
-     * adapter sets this to
-     * {@link androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationStrategy#PREVENT},
-     * merge adapter will report that.
+     * MergeAdapter infers this value from added {@link Adapter}s.
      *
-     * @param strategy The saved state restoration strategy for this Adapter.
+     * @param strategy The saved state restoration strategy for this Adapter such that
+     * {@link MergeAdapter} will allow state restoration only if all added adapters allow it or
+     *                 there are no adapters.
      */
     @Override
     public void setStateRestorationStrategy(@NonNull StateRestorationStrategy strategy) {
         // do nothing
-        Log.w(TAG, "Calling setStateRestorationStrategy has no impact on the MergeAdapter as"
-                + " it derives its state restoration strategy from nested adapters");
+        throw new UnsupportedOperationException(
+                "Calling setStateRestorationStrategy is not allowed on the MergeAdapter."
+                + " This value is inferred from added adapters");
     }
 
     @Override
@@ -221,7 +222,7 @@
     }
 
     /**
-     * Internal method to be called from the wrappers.
+     * Internal method called by the MergeAdapterController.
      */
     void internalSetStateRestorationStrategy(@NonNull StateRestorationStrategy strategy) {
         super.setStateRestorationStrategy(strategy);
diff --git a/room/compiler/src/main/kotlin/androidx/room/verifier/DatabaseVerifier.kt b/room/compiler/src/main/kotlin/androidx/room/verifier/DatabaseVerifier.kt
index 0a38780..acf01e1c 100644
--- a/room/compiler/src/main/kotlin/androidx/room/verifier/DatabaseVerifier.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/verifier/DatabaseVerifier.kt
@@ -44,6 +44,8 @@
 ) {
     companion object {
         private const val CONNECTION_URL = "jdbc:sqlite::memory:"
+        private const val SQLITE_INITIALIZED_FLAG = "room.sqlite.initialized"
+        private const val SQLITE_TEMPDIR_FLAG = "org.sqlite.tmpdir"
         private val NATIVE_LIB_RELOAD_RETRY_CNT = 5
         /**
          * Taken from:
@@ -69,20 +71,55 @@
         }
 
         /**
+         * Tells whether sqlite was previously initialized successfully
+         */
+        private fun reusePreviousSqliteTempdir(): Boolean {
+            val previouslyInitialized = System.getProperty(SQLITE_INITIALIZED_FLAG) != null
+            if (!previouslyInitialized) {
+                return false
+            }
+            val previousTempDirString = System.getProperty(SQLITE_TEMPDIR_FLAG)
+            if (previousTempDirString == null) {
+                return false
+            }
+            val previousTempDir = File(previousTempDirString)
+            if (!previousTempDir.isDirectory) {
+                return false
+            }
+            // reuse existing temp dir
+            sqliteNativeLibDir = previousTempDir
+            return true
+        }
+
+        /**
          * Copies native libraries into a tmp folder to be loaded.
          */
         private fun copyNativeLibs() {
-            // see: https://github.com/xerial/sqlite-jdbc/issues/97
-            val tmpDir = System.getProperty("java.io.tmpdir")
-            checkNotNull(tmpDir) {
-                "Room needs java.io.tmpdir system property to be set to setup sqlite"
+            // check whether a previous initialization succeeded
+            if (reusePreviousSqliteTempdir()) {
+                return
             }
-            sqliteNativeLibDir = File(tmpDir, "room-${UUID.randomUUID()}")
-            sqliteNativeLibDir.mkdirs()
-            sqliteNativeLibDir.deleteOnExit()
-            System.setProperty("org.sqlite.tmpdir", sqliteNativeLibDir.absolutePath)
-            // dummy call to trigger JDBC initialization so that we can unregister it
-            JDBC.isValidURL(CONNECTION_URL)
+            synchronized(this) {
+                // check again (inside synchronization) whether a previous initialization succeeded
+                if (reusePreviousSqliteTempdir()) {
+                    return
+                }
+
+                // set up sqlite
+                // see: https://github.com/xerial/sqlite-jdbc/issues/97
+                val baseTempDir = System.getProperty("java.io.tmpdir")
+                checkNotNull(baseTempDir) {
+                    "Room needs java.io.tmpdir system property to be set to setup sqlite"
+                }
+                sqliteNativeLibDir = File(baseTempDir, "room-${UUID.randomUUID()}")
+                sqliteNativeLibDir.mkdirs()
+                sqliteNativeLibDir.deleteOnExit()
+                System.setProperty(SQLITE_TEMPDIR_FLAG, sqliteNativeLibDir.absolutePath)
+                // dummy call to trigger JDBC initialization so that we can unregister it
+                JDBC.isValidURL(CONNECTION_URL)
+                // record successful initialization
+                System.setProperty(SQLITE_INITIALIZED_FLAG, "true")
+            }
         }
 
         /**
@@ -132,6 +169,13 @@
                     context.logger.w(Warning.CANNOT_CREATE_VERIFICATION_DATABASE, element,
                         DatabaseVerificationErrors.cannotCreateConnection(ex))
                     return null
+                } finally {
+                    val systemPropertyTempDir = System.getProperty(SQLITE_TEMPDIR_FLAG)
+                    if (systemPropertyTempDir != sqliteNativeLibDir.toString()) {
+                        throw ConcurrentModificationException("System property " +
+                            "org.sqlite.tmpdir changed from $sqliteNativeLibDir to " +
+                            "$systemPropertyTempDir inside DatabaseVerifier.create")
+                    }
                 }
             }
             return null
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java
index 8cb26c4..0549d299 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java
@@ -26,6 +26,7 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.os.Build;
 import android.os.SystemClock;
 
 import androidx.annotation.NonNull;
@@ -46,6 +47,7 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
+import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ServiceTestRule;
 
@@ -66,6 +68,7 @@
 
 @LargeTest
 @RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN)
 public class MultiInstanceInvalidationTest {
 
     private static final Customer CUSTOMER_1 = new Customer();
diff --git a/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/Product.java b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/Product.java
index 44741fd..86ae44a 100644
--- a/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/Product.java
+++ b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/Product.java
@@ -16,7 +16,6 @@
 
 package androidx.room.integration.testapp.database;
 
-import androidx.core.util.ObjectsCompat;
 import androidx.room.Entity;
 import androidx.room.PrimaryKey;
 
@@ -65,11 +64,15 @@
         if (this == o) return true;
         if (!(o instanceof Product)) return false;
         Product product = (Product) o;
-        return mId == product.mId && ObjectsCompat.equals(mName, product.mName);
+
+        if (mId != product.mId) return false;
+        return mName != null ? mName.equals(product.mName) : product.mName == null;
     }
 
     @Override
     public int hashCode() {
-        return ObjectsCompat.hash(mId, mName);
+        int result = mId;
+        result = 31 * result + (mName != null ? mName.hashCode() : 0);
+        return result;
     }
 }
diff --git a/samples/SupportVectorDrawableDemos/src/main/java/com/example/android/support/vectordrawable/app/SeekableDemo.java b/samples/SupportVectorDrawableDemos/src/main/java/com/example/android/support/vectordrawable/app/SeekableDemo.java
index 5acb970..4661ea8 100644
--- a/samples/SupportVectorDrawableDemos/src/main/java/com/example/android/support/vectordrawable/app/SeekableDemo.java
+++ b/samples/SupportVectorDrawableDemos/src/main/java/com/example/android/support/vectordrawable/app/SeekableDemo.java
@@ -20,6 +20,7 @@
 import android.os.Bundle;
 import android.widget.Button;
 import android.widget.ImageView;
+import android.widget.SeekBar;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -41,6 +42,7 @@
 
         final ImageView image = findViewById(R.id.image);
         final Button start = findViewById(R.id.start);
+        final SeekBar seekBar = findViewById(R.id.seek);
 
         final SeekableAnimatedVectorDrawable avd =
                 SeekableAnimatedVectorDrawable.create(this, R.drawable.ic_hourglass_animation);
@@ -68,5 +70,22 @@
             }
             avd.start();
         });
+
+        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                if (fromUser) {
+                    avd.setCurrentPlayTime(progress);
+                }
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+            }
+        });
     }
 }
diff --git a/samples/SupportVectorDrawableDemos/src/main/res/layout/seekable_demo.xml b/samples/SupportVectorDrawableDemos/src/main/res/layout/seekable_demo.xml
index 592a7c3..54834e4 100644
--- a/samples/SupportVectorDrawableDemos/src/main/res/layout/seekable_demo.xml
+++ b/samples/SupportVectorDrawableDemos/src/main/res/layout/seekable_demo.xml
@@ -33,4 +33,11 @@
         android:layout_margin="16dp"
         android:text="Start" />
 
+    <SeekBar
+        android:id="@+id/seek"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="16dp"
+        android:max="1333" />
+
 </LinearLayout>
diff --git a/settings.gradle b/settings.gradle
index a690902..9bc283e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -372,6 +372,7 @@
 File externalRoot = new File(rootDir, "../../external")
 
 includeProject(":noto-emoji-compat", new File(externalRoot, "noto-fonts/emoji-compat"))
+includeProject(":icing", new File(externalRoot, "icing"))
 
 // fake project which is used for docs generation from prebuilts
 // we need real android project to generate R.java, aidl etc files that mentioned in sources
diff --git a/ui/ui-core/src/test/java/androidx/ui/core/AlignmentTest.kt b/ui/ui-core/src/test/java/androidx/ui/core/AlignmentTest.kt
new file mode 100644
index 0000000..c772c95
--- /dev/null
+++ b/ui/ui-core/src/test/java/androidx/ui/core/AlignmentTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.core
+
+import androidx.ui.unit.IntPxPosition
+import androidx.ui.unit.IntPxSize
+import androidx.ui.unit.ipx
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class AlignmentTest {
+    private val space = IntPxSize(100.ipx, 100.ipx)
+
+    @Test
+    fun testAlign_topStart() {
+        assertEquals(
+            IntPxPosition(0.ipx, 0.ipx),
+            Alignment.TopStart.align(space, LayoutDirection.Ltr)
+        )
+        assertEquals(
+            IntPxPosition(100.ipx, 0.ipx),
+            Alignment.TopStart.align(space, LayoutDirection.Rtl)
+        )
+    }
+
+    @Test
+    fun testAlign_topCenter() {
+        assertEquals(
+            IntPxPosition(50.ipx, 0.ipx),
+            Alignment.TopCenter.align(space, LayoutDirection.Ltr)
+        )
+        assertEquals(
+            IntPxPosition(50.ipx, 0.ipx),
+            Alignment.TopCenter.align(space, LayoutDirection.Rtl)
+        )
+    }
+    @Test
+    fun testAlign_topEnd() {
+        assertEquals(
+            IntPxPosition(100.ipx, 0.ipx),
+            Alignment.TopEnd.align(space, LayoutDirection.Ltr)
+        )
+        assertEquals(
+            IntPxPosition(0.ipx, 0.ipx),
+            Alignment.TopEnd.align(space, LayoutDirection.Rtl)
+        )
+    }
+
+    @Test
+    fun testAlign_centerStart() {
+        assertEquals(
+            IntPxPosition(0.ipx, 50.ipx),
+            Alignment.CenterStart.align(space, LayoutDirection.Ltr)
+        )
+        assertEquals(
+            IntPxPosition(100.ipx, 50.ipx),
+            Alignment.CenterStart.align(space, LayoutDirection.Rtl)
+        )
+    }
+
+    @Test
+    fun testAlign_center() {
+        assertEquals(
+            IntPxPosition(50.ipx, 50.ipx),
+            Alignment.Center.align(space, LayoutDirection.Ltr)
+        )
+        assertEquals(
+            IntPxPosition(50.ipx, 50.ipx),
+            Alignment.Center.align(space, LayoutDirection.Rtl)
+        )
+    }
+    @Test
+    fun testAlign_centerEnd() {
+        assertEquals(
+            IntPxPosition(100.ipx, 50.ipx),
+            Alignment.CenterEnd.align(space, LayoutDirection.Ltr)
+        )
+        assertEquals(
+            IntPxPosition(0.ipx, 50.ipx),
+            Alignment.CenterEnd.align(space, LayoutDirection.Rtl)
+        )
+    }
+
+    @Test
+    fun testAlign_bottomStart() {
+        assertEquals(
+            IntPxPosition(0.ipx, 100.ipx),
+            Alignment.BottomStart.align(space, LayoutDirection.Ltr)
+        )
+        assertEquals(
+            IntPxPosition(100.ipx, 100.ipx),
+            Alignment.BottomStart.align(space, LayoutDirection.Rtl)
+        )
+    }
+
+    @Test
+    fun testAlign_bottomCenter() {
+        assertEquals(
+            IntPxPosition(50.ipx, 100.ipx),
+            Alignment.BottomCenter.align(space, LayoutDirection.Ltr)
+        )
+        assertEquals(
+            IntPxPosition(50.ipx, 100.ipx),
+            Alignment.BottomCenter.align(space, LayoutDirection.Rtl)
+        )
+    }
+    @Test
+    fun testAlign_bottomEnd() {
+        assertEquals(
+            IntPxPosition(100.ipx, 100.ipx),
+            Alignment.BottomEnd.align(space, LayoutDirection.Ltr)
+        )
+        assertEquals(
+            IntPxPosition(0.ipx, 100.ipx),
+            Alignment.BottomEnd.align(space, LayoutDirection.Rtl)
+        )
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ScrollerTest.kt b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ScrollerTest.kt
index 20a7710..00647b9 100644
--- a/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ScrollerTest.kt
+++ b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ScrollerTest.kt
@@ -53,6 +53,7 @@
 import androidx.ui.unit.dp
 import androidx.ui.unit.ipx
 import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
 import org.junit.Rule
@@ -60,6 +61,7 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
 
 @SmallTest
 @RunWith(JUnit4::class)
@@ -449,7 +451,8 @@
                 }
             }
         })
-        latch.await()
+        assertWithMessage("Scroll didn't finish after 20 seconds")
+            .that(latch.await(20, TimeUnit.SECONDS)).isTrue()
         return this
     }
 }
diff --git a/ui/ui-foundation/src/androidTest/res/values/styles.xml b/ui/ui-foundation/src/androidTest/res/values/styles.xml
index e4bcb85..4310d17 100644
--- a/ui/ui-foundation/src/androidTest/res/values/styles.xml
+++ b/ui/ui-foundation/src/androidTest/res/values/styles.xml
@@ -16,7 +16,6 @@
 
 <resources>
     <style name="TestTheme" parent="@android:style/Theme.Material.Light.NoActionBar">
-        <item name="android:windowBackground">#ffffff</item>
         <item name="android:windowActionBar">false</item>
         <item name="android:windowAnimationStyle">@null</item>
         <item name="android:windowContentOverlay">@null</item>
diff --git a/ui/ui-framework/api/0.1.0-dev06.txt b/ui/ui-framework/api/0.1.0-dev06.txt
index 2b01c26..7a6bacaf 100644
--- a/ui/ui-framework/api/0.1.0-dev06.txt
+++ b/ui/ui-framework/api/0.1.0-dev06.txt
@@ -69,7 +69,7 @@
   }
 
   public final class PainterModifierKt {
-    method public static androidx.ui.core.DrawModifier toModifier(androidx.ui.graphics.painter.Painter, androidx.ui.core.Alignment alignment = Alignment.Center, androidx.ui.graphics.ScaleFit scaleFit = ScaleFit.Fit, float alpha = 1.0f, androidx.ui.graphics.ColorFilter? colorFilter = null, boolean rtl = false);
+    method public static androidx.ui.core.DrawModifier toModifier(androidx.ui.graphics.painter.Painter, boolean sizeToIntrinsics = true, androidx.ui.core.Alignment alignment = Alignment.Center, androidx.ui.graphics.ScaleFit scaleFit = ScaleFit.Fit, float alpha = 1.0f, androidx.ui.graphics.ColorFilter? colorFilter = null, boolean rtl = false);
   }
 
   public final class ParentDataKt {
diff --git a/ui/ui-framework/api/current.txt b/ui/ui-framework/api/current.txt
index 2b01c26..7a6bacaf 100644
--- a/ui/ui-framework/api/current.txt
+++ b/ui/ui-framework/api/current.txt
@@ -69,7 +69,7 @@
   }
 
   public final class PainterModifierKt {
-    method public static androidx.ui.core.DrawModifier toModifier(androidx.ui.graphics.painter.Painter, androidx.ui.core.Alignment alignment = Alignment.Center, androidx.ui.graphics.ScaleFit scaleFit = ScaleFit.Fit, float alpha = 1.0f, androidx.ui.graphics.ColorFilter? colorFilter = null, boolean rtl = false);
+    method public static androidx.ui.core.DrawModifier toModifier(androidx.ui.graphics.painter.Painter, boolean sizeToIntrinsics = true, androidx.ui.core.Alignment alignment = Alignment.Center, androidx.ui.graphics.ScaleFit scaleFit = ScaleFit.Fit, float alpha = 1.0f, androidx.ui.graphics.ColorFilter? colorFilter = null, boolean rtl = false);
   }
 
   public final class ParentDataKt {
diff --git a/ui/ui-framework/api/public_plus_experimental_0.1.0-dev06.txt b/ui/ui-framework/api/public_plus_experimental_0.1.0-dev06.txt
index 2b01c26..7a6bacaf 100644
--- a/ui/ui-framework/api/public_plus_experimental_0.1.0-dev06.txt
+++ b/ui/ui-framework/api/public_plus_experimental_0.1.0-dev06.txt
@@ -69,7 +69,7 @@
   }
 
   public final class PainterModifierKt {
-    method public static androidx.ui.core.DrawModifier toModifier(androidx.ui.graphics.painter.Painter, androidx.ui.core.Alignment alignment = Alignment.Center, androidx.ui.graphics.ScaleFit scaleFit = ScaleFit.Fit, float alpha = 1.0f, androidx.ui.graphics.ColorFilter? colorFilter = null, boolean rtl = false);
+    method public static androidx.ui.core.DrawModifier toModifier(androidx.ui.graphics.painter.Painter, boolean sizeToIntrinsics = true, androidx.ui.core.Alignment alignment = Alignment.Center, androidx.ui.graphics.ScaleFit scaleFit = ScaleFit.Fit, float alpha = 1.0f, androidx.ui.graphics.ColorFilter? colorFilter = null, boolean rtl = false);
   }
 
   public final class ParentDataKt {
diff --git a/ui/ui-framework/api/public_plus_experimental_current.txt b/ui/ui-framework/api/public_plus_experimental_current.txt
index 2b01c26..7a6bacaf 100644
--- a/ui/ui-framework/api/public_plus_experimental_current.txt
+++ b/ui/ui-framework/api/public_plus_experimental_current.txt
@@ -69,7 +69,7 @@
   }
 
   public final class PainterModifierKt {
-    method public static androidx.ui.core.DrawModifier toModifier(androidx.ui.graphics.painter.Painter, androidx.ui.core.Alignment alignment = Alignment.Center, androidx.ui.graphics.ScaleFit scaleFit = ScaleFit.Fit, float alpha = 1.0f, androidx.ui.graphics.ColorFilter? colorFilter = null, boolean rtl = false);
+    method public static androidx.ui.core.DrawModifier toModifier(androidx.ui.graphics.painter.Painter, boolean sizeToIntrinsics = true, androidx.ui.core.Alignment alignment = Alignment.Center, androidx.ui.graphics.ScaleFit scaleFit = ScaleFit.Fit, float alpha = 1.0f, androidx.ui.graphics.ColorFilter? colorFilter = null, boolean rtl = false);
   }
 
   public final class ParentDataKt {
diff --git a/ui/ui-framework/api/restricted_0.1.0-dev06.txt b/ui/ui-framework/api/restricted_0.1.0-dev06.txt
index 2b01c26..7a6bacaf 100644
--- a/ui/ui-framework/api/restricted_0.1.0-dev06.txt
+++ b/ui/ui-framework/api/restricted_0.1.0-dev06.txt
@@ -69,7 +69,7 @@
   }
 
   public final class PainterModifierKt {
-    method public static androidx.ui.core.DrawModifier toModifier(androidx.ui.graphics.painter.Painter, androidx.ui.core.Alignment alignment = Alignment.Center, androidx.ui.graphics.ScaleFit scaleFit = ScaleFit.Fit, float alpha = 1.0f, androidx.ui.graphics.ColorFilter? colorFilter = null, boolean rtl = false);
+    method public static androidx.ui.core.DrawModifier toModifier(androidx.ui.graphics.painter.Painter, boolean sizeToIntrinsics = true, androidx.ui.core.Alignment alignment = Alignment.Center, androidx.ui.graphics.ScaleFit scaleFit = ScaleFit.Fit, float alpha = 1.0f, androidx.ui.graphics.ColorFilter? colorFilter = null, boolean rtl = false);
   }
 
   public final class ParentDataKt {
diff --git a/ui/ui-framework/api/restricted_current.txt b/ui/ui-framework/api/restricted_current.txt
index 2b01c26..7a6bacaf 100644
--- a/ui/ui-framework/api/restricted_current.txt
+++ b/ui/ui-framework/api/restricted_current.txt
@@ -69,7 +69,7 @@
   }
 
   public final class PainterModifierKt {
-    method public static androidx.ui.core.DrawModifier toModifier(androidx.ui.graphics.painter.Painter, androidx.ui.core.Alignment alignment = Alignment.Center, androidx.ui.graphics.ScaleFit scaleFit = ScaleFit.Fit, float alpha = 1.0f, androidx.ui.graphics.ColorFilter? colorFilter = null, boolean rtl = false);
+    method public static androidx.ui.core.DrawModifier toModifier(androidx.ui.graphics.painter.Painter, boolean sizeToIntrinsics = true, androidx.ui.core.Alignment alignment = Alignment.Center, androidx.ui.graphics.ScaleFit scaleFit = ScaleFit.Fit, float alpha = 1.0f, androidx.ui.graphics.ColorFilter? colorFilter = null, boolean rtl = false);
   }
 
   public final class ParentDataKt {
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/PainterModifierTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/PainterModifierTest.kt
index b9284cb..36321d1 100644
--- a/ui/ui-framework/src/androidTest/java/androidx/ui/core/PainterModifierTest.kt
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/PainterModifierTest.kt
@@ -40,9 +40,12 @@
 import androidx.ui.graphics.compositeOver
 import androidx.ui.graphics.painter.Painter
 import androidx.ui.graphics.toArgb
+import androidx.ui.unit.IntPx
+import androidx.ui.unit.IntPxSize
 import androidx.ui.unit.Px
 import androidx.ui.unit.ipx
 import androidx.ui.unit.PxSize
+import androidx.ui.unit.max
 import org.junit.Assert
 import org.junit.Assert.assertEquals
 import org.junit.Before
@@ -236,6 +239,128 @@
         }
     }
 
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testPainterModifierIntrinsicSize() {
+        val paintLatch = CountDownLatch(1)
+        rule.runOnUiThreadIR {
+            activity.setContent {
+                NoMinSizeContainer {
+                    NoIntrinsicSizeContainer(
+                        LatchPainter(containerWidth, containerHeight, paintLatch).toModifier()
+                    ) {
+                        // Intentionally empty
+                    }
+                }
+            }
+        }
+
+        paintLatch.await()
+        obtainScreenshotBitmap(
+            containerWidth.roundToInt(),
+            containerHeight.roundToInt()
+        ).apply {
+            assertEquals(Color.Red.toArgb(), getPixel(0, 0))
+            assertEquals(Color.Red.toArgb(), getPixel(containerWidth.roundToInt() - 1, 0))
+            assertEquals(
+                Color.Red.toArgb(), getPixel(
+                    containerWidth.roundToInt() - 1,
+                    containerHeight.roundToInt() - 1
+                )
+            )
+            assertEquals(Color.Red.toArgb(), getPixel(0, containerHeight.roundToInt() - 1))
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testPainterIntrinsicSizeDoesNotExceedMax() {
+        val paintLatch = CountDownLatch(1)
+        val containerSize = containerWidth.roundToInt() / 2
+        rule.runOnUiThreadIR {
+            activity.setContent {
+                NoIntrinsicSizeContainer(
+                    background(Color.White) +
+                            FixedSizeModifier(containerWidth.roundToInt().ipx)
+                ) {
+                    NoIntrinsicSizeContainer(
+                    FixedSizeModifier(containerSize.ipx) +
+                            LatchPainter(
+                                containerWidth,
+                                containerHeight,
+                                paintLatch
+                            ).toModifier(alignment = Alignment.TopStart)
+                    ) {
+                        // Intentionally empty
+                    }
+                }
+            }
+        }
+
+        paintLatch.await()
+        obtainScreenshotBitmap(
+            containerWidth.roundToInt(),
+            containerHeight.roundToInt()
+        ).apply {
+            assertEquals(Color.Red.toArgb(), getPixel(0, 0))
+            assertEquals(Color.Red.toArgb(), getPixel(containerWidth.roundToInt() / 2 - 1, 0))
+            assertEquals(
+                Color.White.toArgb(), getPixel(
+                    containerWidth.roundToInt() - 1,
+                    containerHeight.roundToInt() - 1
+                )
+            )
+            assertEquals(Color.Red.toArgb(), getPixel(0, containerHeight.roundToInt() / 2 - 1))
+
+            assertEquals(Color.White.toArgb(), getPixel(containerWidth.roundToInt() / 2 + 1, 0))
+            assertEquals(Color.White.toArgb(), getPixel(containerWidth.roundToInt() / 2 + 1,
+                containerHeight.roundToInt() / 2 + 1))
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testPainterNotSizedToIntrinsics() {
+        val paintLatch = CountDownLatch(1)
+        val containerSize = containerWidth.roundToInt() / 2
+        rule.runOnUiThreadIR {
+            activity.setContent {
+                NoIntrinsicSizeContainer(
+                    background(Color.White) +
+                            FixedSizeModifier(containerSize.ipx)
+                ) {
+                    NoIntrinsicSizeContainer(
+                        FixedSizeModifier(containerSize.ipx) +
+                        LatchPainter
+                            (
+                            containerWidth,
+                            containerHeight,
+                            paintLatch
+                        ).toModifier(sizeToIntrinsics = false, alignment = Alignment.TopStart)
+                    ) {
+                        // Intentionally empty
+                    }
+                }
+            }
+        }
+
+        paintLatch.await()
+        obtainScreenshotBitmap(
+            containerSize,
+            containerSize
+        ).apply {
+            assertEquals(Color.Red.toArgb(), getPixel(0, 0))
+            assertEquals(Color.Red.toArgb(), getPixel(containerSize - 1, 0))
+            assertEquals(
+                Color.Red.toArgb(), getPixel(
+                    containerSize - 1,
+                    containerSize - 1
+                )
+            )
+            assertEquals(Color.Red.toArgb(), getPixel(0, containerSize - 1))
+        }
+    }
+
     @Composable
     private fun testPainter(
         alpha: Float = DefaultAlpha,
@@ -286,4 +411,62 @@
             latch.countDown()
         }
     }
+
+    /**
+     * Container composable that relaxes the minimum width and height constraints
+     * before giving them to their child
+     */
+    @Composable
+    fun NoMinSizeContainer(children: @Composable() () -> Unit) {
+        Layout(children) { measurables, constraints ->
+            val loosenedConstraints = constraints.copy(minWidth = 0.ipx, minHeight = 0.ipx)
+            val placeables = measurables.map { it.measure(loosenedConstraints) }
+            val maxPlaceableWidth = placeables.maxBy { it.width.value }?.width ?: 0.ipx
+            val maxPlaceableHeight = placeables.maxBy { it.height.value }?.width ?: 0.ipx
+            val width = max(maxPlaceableWidth, loosenedConstraints.minWidth)
+            val height = max(maxPlaceableHeight, loosenedConstraints.minHeight)
+            layout(width, height) {
+                placeables.forEach { it.place(0.ipx, 0.ipx) }
+            }
+        }
+    }
+
+    /**
+     * Composable that is sized purely by the constraints given by its modifiers
+     */
+    @Composable
+    fun NoIntrinsicSizeContainer(
+        modifier: Modifier = Modifier.None,
+        children: @Composable() () -> Unit
+    ) {
+        Layout(children, modifier) { measurables, constraints ->
+            val placeables = measurables.map { it.measure(constraints) }
+            val width = max(
+                placeables.maxBy { it.width.value }?.width ?: 0.ipx, constraints
+                    .minWidth
+            )
+            val height = max(
+                placeables.maxBy { it.height.value }?.height ?: 0.ipx, constraints
+                    .minHeight
+            )
+            layout(width, height) {
+                placeables.forEach { it.place(0.ipx, 0.ipx) }
+            }
+        }
+    }
+
+    class FixedSizeModifier(val width: IntPx, val height: IntPx = width) : LayoutModifier {
+        override fun ModifierScope.modifySize(
+            constraints: Constraints,
+            childSize: IntPxSize
+        ): IntPxSize = IntPxSize(width, height)
+
+        override fun ModifierScope.modifyConstraints(constraints: Constraints): Constraints =
+            Constraints(
+                minWidth = width,
+                minHeight = height,
+                maxWidth = width,
+                maxHeight = height
+            )
+    }
 }
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/PainterModifier.kt b/ui/ui-framework/src/main/java/androidx/ui/core/PainterModifier.kt
index cddd569..bea71fa 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/PainterModifier.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/PainterModifier.kt
@@ -26,17 +26,42 @@
 import androidx.ui.unit.Density
 import androidx.ui.unit.IntPx
 import androidx.ui.unit.IntPxSize
+import androidx.ui.unit.Px
 import androidx.ui.unit.PxSize
+import androidx.ui.unit.ceil
+import androidx.ui.unit.max
 import kotlin.math.ceil
 
 /**
  * Create a [DrawModifier] from this [Painter]. This modifier is memoized and re-used across
  * subsequent compositions
  *
+ * @param sizeToIntrinsics: Flag to indicate whether this PainterModifier should be involved with
+ * appropriately sizing the component it is associated with. True if the intrinsic size should
+ * influence the size of the component, false otherwise. A value of false here is equivalent to
+ * the underlying Painter having no intrinsic size, that is [Painter.intrinsicSize] returns
+ * [PxSize.UnspecifiedSize]
+ *
+ * @param alignment: Specifies the rule used to place the contents of the Painter within the
+ * specified bounds, the default of [Alignment.Center] centers the content within the specified
+ * rendering bounds
+ *
+ * @param scaleFit: Specifies the rule used to scale the content of the Painter within the
+ * specified bounds, the default of [ScaleFit.Fit] scales the content to be as large as possible
+ * within the specified bounds while still maintaining the aspect ratio of its intrinsic size
+ *
+ * @param alpha: Specifies the opacity to render the contents of the underlying [Painter]
+ *
+ * @param colorFilter: Specifies an optional tint to apply to the contents of the [Painter] when
+ * drawn in the specified area
+ *
+ * @param rtl: Flag to indicate contents of the [Painter] should render for right to left languages
+ *
  * @sample androidx.ui.framework.samples.PainterModifierSample
  */
 @Composable
 fun Painter.toModifier(
+    sizeToIntrinsics: Boolean = true,
     alignment: Alignment = Alignment.Center,
     scaleFit: ScaleFit = ScaleFit.Fit,
     alpha: Float = DefaultAlpha,
@@ -45,8 +70,8 @@
 ): DrawModifier {
     // TODO potentially create thread-safe PainterModifier pool to allow for re-use
     //  of PainterModifier instances and avoid gc overhead
-    return remember(this, alignment, scaleFit, alpha, colorFilter, rtl) {
-        PainterModifier(this, alignment, scaleFit, alpha, colorFilter, rtl)
+    return remember(this, sizeToIntrinsics, alignment, scaleFit, alpha, colorFilter, rtl) {
+        PainterModifier(this, sizeToIntrinsics, alignment, scaleFit, alpha, colorFilter, rtl)
     }
 }
 
@@ -56,12 +81,74 @@
  */
 private data class PainterModifier(
     val painter: Painter,
+    var sizeToIntrinsics: Boolean,
     var alignment: Alignment = Alignment.Center,
     var scaleFit: ScaleFit = ScaleFit.Fit,
     var alpha: Float = DefaultAlpha,
     var colorFilter: ColorFilter? = null,
     var rtl: Boolean = false
-) : DrawModifier {
+) : LayoutModifier, DrawModifier {
+
+    override fun ModifierScope.modifyConstraints(constraints: Constraints): Constraints {
+        val intrinsicSize = painter.intrinsicSize
+        val intrinsicWidth =
+            intrinsicSize.width.takeUnless {
+                !sizeToIntrinsics || it == Px.Infinity
+            }?.ceil() ?: constraints.minWidth
+        val intrinsicHeight =
+            intrinsicSize.height.takeUnless {
+                !sizeToIntrinsics || it == Px.Infinity
+            }?.ceil() ?: constraints.minHeight
+
+        val minWidth = intrinsicWidth.coerceIn(constraints.minWidth, constraints.maxWidth)
+        val minHeight = intrinsicHeight.coerceIn(constraints.minHeight, constraints.maxHeight)
+
+        return if (minWidth == constraints.minWidth && minHeight == constraints.minHeight) {
+            constraints
+        } else {
+            constraints.copy(minWidth = minWidth, minHeight = minHeight)
+        }
+    }
+
+    override fun ModifierScope.minIntrinsicWidthOf(measurable: Measurable, height: IntPx): IntPx {
+        val constraints = Constraints(maxHeight = height)
+        val layoutWidth = measurable.minIntrinsicWidth(modifyConstraints(constraints).maxHeight)
+        val painterIntrinsicWidth =
+            painter.intrinsicSize.width.takeUnless {
+                !sizeToIntrinsics || it == Px.Infinity
+            }?.ceil() ?: layoutWidth
+        return max(painterIntrinsicWidth, layoutWidth)
+    }
+
+    override fun ModifierScope.maxIntrinsicWidthOf(measurable: Measurable, height: IntPx): IntPx {
+        val constraints = Constraints(maxHeight = height)
+        val layoutWidth = measurable.maxIntrinsicWidth(modifyConstraints(constraints).maxHeight)
+        val painterIntrinsicWidth =
+            painter.intrinsicSize.width.takeUnless {
+                !sizeToIntrinsics || it == Px.Infinity
+            }?.ceil() ?: layoutWidth
+        return max(painterIntrinsicWidth, layoutWidth)
+    }
+
+    override fun ModifierScope.minIntrinsicHeightOf(measurable: Measurable, width: IntPx): IntPx {
+        val constraints = Constraints(maxWidth = width)
+        val layoutHeight = measurable.minIntrinsicHeight(modifyConstraints(constraints).maxWidth)
+        val painterIntrinsicHeight =
+            painter.intrinsicSize.height.takeUnless {
+                !sizeToIntrinsics || it == Px.Infinity
+            }?.ceil() ?: layoutHeight
+        return max(painterIntrinsicHeight, layoutHeight)
+    }
+
+    override fun ModifierScope.maxIntrinsicHeightOf(measurable: Measurable, width: IntPx): IntPx {
+        val constraints = Constraints(maxWidth = width)
+        val layoutHeight = measurable.maxIntrinsicHeight(modifyConstraints(constraints).maxWidth)
+        val painterIntrinsicHeight =
+            painter.intrinsicSize.height.takeUnless {
+                !sizeToIntrinsics || it == Px.Infinity
+            }?.ceil() ?: layoutHeight
+        return max(painterIntrinsicHeight, layoutHeight)
+    }
 
     override fun draw(
         density: Density,
diff --git a/ui/ui-material/icons/README.md b/ui/ui-material/icons/README.md
new file mode 100644
index 0000000..69b347a
--- /dev/null
+++ b/ui/ui-material/icons/README.md
@@ -0,0 +1,30 @@
+# Material Iconography
+
+## Modules / components
+Material iconography is split across three modules:
+
+ 1. The `generator` module, in `generator/` - this module processes and generates Kotlin source files as part of the build step of the other modules. This module is not shipped as an artifact, and caches its outputs based on the input icons (found in `generator/raw-icons`).
+ 2. `ui-material-icons-core` , in `core/` - this module contains _core_ icons, the set of most-commonly-used icons used by applications, including the icons that are required by Material components themselves, such as the menu icon. This module is fairly small and is depended on by `ui-material`.
+ 3. `ui-material-icons-extended`, in `extended/` - this module contains every icon that is not in `ui-material-icons-core`, and has a transitive `api` dependency on `ui-material-icons-core`, so depending on this module will provide every single Material icon (over 5000 at the time of writing). Due to the excessive size of this module, this module should ***NOT*** be included as a direct dependency of any other library, and should only be used if Proguard / R8 is enabled.
+
+## Icon Generation
+
+Generation is split into a few distinct steps:
+
+1. Icons are downloaded (manually) using the Google Fonts API, using the script in the `generator` module. This downloads vector drawables for every single Material icon to the `raw-icons` folder.
+2. During compilation of the core and extended modules, these icons are processed to remove theme attributes that we cannot generate code for, checked to ensure that all icons exist in all themes, and then an API tracking file similar to API files in other modules is generated. This API file tracks what icons we have processed / will generate code, and the build will fail at this point if there are differences between the checked in API file and the generated API file.
+3. Once these icons are processed, we then parse each file, create a Vector-like representation, and convert this to `VectorAssetBuilder` commands that during run time will create a matching source code representation of this XML file. We then write this generated Kotlin file to the output directory, where it will be compiled as part of the `core` / `extended` module's source code, as if it was manually written and checked in. Each XML file creates a corresponding Kotlin file, containing a `by lazy` property representing that icon. For more information on using the generated icons, see `androidx.ui.material.icons.Icons`.
+
+## Adding new icons
+To add new icons, simply use the icon downloading script at `generator/download_material_icons.py`, run any Gradle command that will trigger compilation of the icon modules (such as `./gradlew buildOnServer`), and follow the message in the build failure asking to confirm API changes by updating the API tracking file.
+
+## Icon Testing
+Similar to how we generate Kotlin source for each icon, we also generate a 'testing manifest' that contains a list of all the source drawables, matched to their generated code representations. This allows us to run screenshot comparison tests (`IconComparisonTest`) that compare each pixel of the generated and source drawables, to ensure we generated the correct code, and that any changes in parsing logic that causes inconsistencies with our generation logic is caught in CI.
+
+## Useful files
+
+ - `generator/download_material_icons.py` - script to download icons from Google Fonts API
+ - `IconGenerationTask` - base Gradle task for generating icons / associated testing resources as part of the build. See subclasses for specific task logic.
+ - `IconProcessor` - processes raw XML files in `generator/raw-icons`, creates a list of all icons that we will generate source for and ensures the API surface of these icons has not changed. (We do not run Metalava (or lint) on the extended module due to the extreme size of the module (5000+ source files) - running Metalava here would take hours.)
+ - `IconParser` - simple XML parser that parses the processed XML files into Vector representations.
+ - `VectorAssetGenerator` - converts icons parsed by `IconParser` into Kotlin source files that represent the icon.
diff --git a/ui/ui-material/icons/extended/build.gradle b/ui/ui-material/icons/extended/build.gradle
index 1b8729f..efde2e4 100644
--- a/ui/ui-material/icons/extended/build.gradle
+++ b/ui/ui-material/icons/extended/build.gradle
@@ -18,6 +18,7 @@
 import androidx.build.LibraryVersions
 import androidx.build.Publish
 import androidx.ui.material.icons.generator.tasks.IconGenerationTask
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 import static androidx.build.dependencies.DependenciesKt.*
 
@@ -29,10 +30,12 @@
 }
 
 dependencies {
+    kotlinPlugin project(path: ":compose:compose-compiler")
     implementation(KOTLIN_STDLIB)
 
     api project(":ui:ui-material-icons-core")
 
+    androidTestImplementation project(":ui:ui-foundation")
     androidTestImplementation project(":ui:ui-layout")
     androidTestImplementation project(":ui:ui-platform")
     androidTestImplementation project(":ui:ui-test")
@@ -62,3 +65,9 @@
     inceptionYear = "2020"
     description = "AndroidX Extended Material Icons"
 }
+
+tasks.withType(KotlinCompile).configureEach {
+    kotlinOptions {
+        useIR = true
+    }
+}
diff --git a/ui/ui-material/icons/extended/src/androidTest/java/androidx/ui/material/icons/test/IconComparisonTest.kt b/ui/ui-material/icons/extended/src/androidTest/java/androidx/ui/material/icons/test/IconComparisonTest.kt
index 04a7c53..dfaf609 100644
--- a/ui/ui-material/icons/extended/src/androidTest/java/androidx/ui/material/icons/test/IconComparisonTest.kt
+++ b/ui/ui-material/icons/extended/src/androidTest/java/androidx/ui/material/icons/test/IconComparisonTest.kt
@@ -18,21 +18,24 @@
 
 import android.app.Activity
 import android.graphics.Bitmap
+import android.os.Build
 import android.view.ViewGroup
 import androidx.compose.Composable
 import androidx.compose.Compose
 import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
 import androidx.test.rule.ActivityTestRule
 import androidx.ui.core.AndroidComposeView
 import androidx.ui.core.ContextAmbient
 import androidx.ui.core.TestTag
 import androidx.ui.core.setContent
+import androidx.ui.foundation.Box
 import androidx.ui.graphics.Color
 import androidx.ui.graphics.vector.DrawVector
 import androidx.ui.graphics.vector.VectorAsset
 import androidx.ui.layout.Center
 import androidx.ui.layout.Column
-import androidx.ui.layout.Container
+import androidx.ui.layout.LayoutSize
 import androidx.ui.res.vectorResource
 import androidx.ui.semantics.Semantics
 import androidx.ui.test.captureToBitmap
@@ -53,6 +56,7 @@
  * Material [androidx.ui.material.icons.Icons] and their XML source.
  */
 @LargeTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
 @RunWith(JUnit4::class)
 class IconComparisonTest {
 
@@ -163,14 +167,14 @@
         Column {
             TestTag(ProgrammaticTestTag) {
                 Semantics(container = true) {
-                    Container(width = 24.dp, height = 24.dp) {
+                    Box(LayoutSize(24.dp)) {
                         DrawVector(vectorImage = programmaticVector, tintColor = Color.Red)
                     }
                 }
             }
             TestTag(XmlTestTag) {
                 Semantics(container = true) {
-                    Container(width = 24.dp, height = 24.dp) {
+                    Box(LayoutSize(24.dp)) {
                         DrawVector(vectorImage = xmlVector, tintColor = Color.Red)
                     }
                 }
diff --git a/ui/ui-material/icons/extended/src/androidTest/res/values/styles.xml b/ui/ui-material/icons/extended/src/androidTest/res/values/styles.xml
index 637be11..43e2d55 100644
--- a/ui/ui-material/icons/extended/src/androidTest/res/values/styles.xml
+++ b/ui/ui-material/icons/extended/src/androidTest/res/values/styles.xml
@@ -16,7 +16,6 @@
 
 <resources>
     <style name="TestTheme" parent="@android:style/Theme.Material.Light.NoActionBar">
-        <item name="android:windowBackground">#ffffff</item>
         <item name="android:windowActionBar">false</item>
         <item name="android:windowAnimationStyle">@null</item>
         <item name="android:windowContentOverlay">@null</item>
diff --git a/ui/ui-material/src/androidTest/res/values/styles.xml b/ui/ui-material/src/androidTest/res/values/styles.xml
index e4bcb85..4310d17 100644
--- a/ui/ui-material/src/androidTest/res/values/styles.xml
+++ b/ui/ui-material/src/androidTest/res/values/styles.xml
@@ -16,7 +16,6 @@
 
 <resources>
     <style name="TestTheme" parent="@android:style/Theme.Material.Light.NoActionBar">
-        <item name="android:windowBackground">#ffffff</item>
         <item name="android:windowActionBar">false</item>
         <item name="android:windowAnimationStyle">@null</item>
         <item name="android:windowContentOverlay">@null</item>
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt b/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
index 8776e70..a3e35ae 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
@@ -191,16 +191,21 @@
 
             // TODO: add proper landscape support
             val isLandscape = constraints.maxWidth > constraints.maxHeight
-            val openedValue = if (isLandscape) maxValue else lerp(
+            val openedValue = if (isLandscape) minValue else lerp(
                 minValue,
                 maxValue,
                 BottomDrawerOpenFraction
             )
-            val anchors = listOf(
-                maxValue to DrawerState.Closed,
-                openedValue to DrawerState.Opened,
-                minValue to DrawerState.Opened
-            )
+            val anchors =
+                if (isLandscape) {
+                    listOf(maxValue to DrawerState.Closed, minValue to DrawerState.Opened)
+                } else {
+                    listOf(
+                        maxValue to DrawerState.Closed,
+                        openedValue to DrawerState.Opened,
+                        minValue to DrawerState.Opened
+                    )
+                }
             StateDraggable(
                 state = drawerState,
                 onStateChange = onStateChange,
diff --git a/ui/ui-tooling/OWNERS b/ui/ui-tooling/OWNERS
index abfbec5..2d44de5 100644
--- a/ui/ui-tooling/OWNERS
+++ b/ui/ui-tooling/OWNERS
@@ -1 +1,2 @@
 chuckj@google.com
+diegoperez@google.com
\ No newline at end of file
diff --git a/ui/ui-tooling/src/androidTest/res/values/style.xml b/ui/ui-tooling/src/androidTest/res/values/style.xml
index e4bcb85..4310d17 100644
--- a/ui/ui-tooling/src/androidTest/res/values/style.xml
+++ b/ui/ui-tooling/src/androidTest/res/values/style.xml
@@ -16,7 +16,6 @@
 
 <resources>
     <style name="TestTheme" parent="@android:style/Theme.Material.Light.NoActionBar">
-        <item name="android:windowBackground">#ffffff</item>
         <item name="android:windowActionBar">false</item>
         <item name="android:windowAnimationStyle">@null</item>
         <item name="android:windowContentOverlay">@null</item>
diff --git a/vectordrawable/vectordrawable-seekable/src/androidTest/java/androidx/vectordrawable/graphics/drawable/tests/SeekableAnimatedVectorDrawableTest.java b/vectordrawable/vectordrawable-seekable/src/androidTest/java/androidx/vectordrawable/graphics/drawable/tests/SeekableAnimatedVectorDrawableTest.java
index b1304c4..9327ffe4 100644
--- a/vectordrawable/vectordrawable-seekable/src/androidTest/java/androidx/vectordrawable/graphics/drawable/tests/SeekableAnimatedVectorDrawableTest.java
+++ b/vectordrawable/vectordrawable-seekable/src/androidTest/java/androidx/vectordrawable/graphics/drawable/tests/SeekableAnimatedVectorDrawableTest.java
@@ -264,6 +264,48 @@
         );
     }
 
+    @Test
+    @UiThreadTest
+    public void setCurrentPlayTime() {
+        final Bitmap bitmap = Bitmap.createBitmap(
+                IMAGE_WIDTH, IMAGE_HEIGHT, Bitmap.Config.ARGB_8888
+        );
+        final Canvas canvas = new Canvas(bitmap);
+        final SeekableAnimatedVectorDrawable avd = SeekableAnimatedVectorDrawable.create(
+                ApplicationProvider.getApplicationContext(),
+                R.drawable.animated_color_fill
+        );
+
+        assertThat(avd).isNotNull();
+        avd.setBounds(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
+        avd.draw(canvas);
+        assertThat(bitmap.getPixel(0, 0)).isEqualTo(Color.RED);
+
+        int previousRed = Integer.MAX_VALUE;
+        for (int i = 0; i < 10; i++) {
+            avd.setCurrentPlayTime((i + 1) * 100L);
+            avd.draw(canvas);
+            final int fillColor = bitmap.getPixel(IMAGE_WIDTH / 2, IMAGE_HEIGHT / 2);
+            assertThat(Color.blue(fillColor)).isEqualTo(0);
+            assertThat(Color.green(fillColor)).isEqualTo(0);
+            int red = Color.red(fillColor);
+            assertThat(red).isLessThan(previousRed);
+            previousRed = red;
+        }
+    }
+
+    @Test
+    @UiThreadTest
+    public void getCurrentPlayTime() {
+        final SeekableAnimatedVectorDrawable avd = SeekableAnimatedVectorDrawable.create(
+                ApplicationProvider.getApplicationContext(),
+                R.drawable.animated_color_fill
+        );
+        assertThat(avd).isNotNull();
+        avd.setCurrentPlayTime(100L);
+        assertThat(avd.getCurrentPlayTime()).isEqualTo(100L);
+    }
+
     private SeekableAnimatedVectorDrawable createAvd() {
         final SeekableAnimatedVectorDrawable avd = SeekableAnimatedVectorDrawable.create(
                 ApplicationProvider.getApplicationContext(),
diff --git a/vectordrawable/vectordrawable-seekable/src/main/java/androidx/vectordrawable/graphics/drawable/SeekableAnimatedVectorDrawable.java b/vectordrawable/vectordrawable-seekable/src/main/java/androidx/vectordrawable/graphics/drawable/SeekableAnimatedVectorDrawable.java
index ad61cbd..8485055 100644
--- a/vectordrawable/vectordrawable-seekable/src/main/java/androidx/vectordrawable/graphics/drawable/SeekableAnimatedVectorDrawable.java
+++ b/vectordrawable/vectordrawable-seekable/src/main/java/androidx/vectordrawable/graphics/drawable/SeekableAnimatedVectorDrawable.java
@@ -554,6 +554,38 @@
         mAnimatedVectorState.mAnimatorSet.end();
     }
 
+    /**
+     * Sets the position of the animation to the specified point in time. This time should be
+     * between 0 and the total duration of the animation, including any repetition. If the
+     * animation has not yet been started, then it will not advance forward after it is set to this
+     * time; it will simply set the time to this value and perform any appropriate actions based on
+     * that time. If the animation is already running, then setCurrentPlayTime() will set the
+     * current playing time to this value and continue playing from that point.
+     *
+     * @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
+     *                 Unless the animation is reversing, the playtime is considered the time since
+     *                 the end of the start delay of the AnimatorSet in a forward playing direction.
+     */
+    public void setCurrentPlayTime(@IntRange(from = 0) long playTime) {
+        mAnimatedVectorState.mAnimatorSet.setCurrentPlayTime(playTime);
+        invalidateSelf();
+    }
+
+    /**
+     * Returns the milliseconds elapsed since the start of the animation.
+     *
+     * <p>For ongoing animations, this method returns the current progress of the animation in
+     * terms of play time. For an animation that has not yet been started: if the animation has been
+     * seeked to a certain time via {@link #setCurrentPlayTime(long)}, the seeked play time will
+     * be returned; otherwise, this method will return 0.
+     *
+     * @return the current position in time of the animation in milliseconds
+     */
+    @IntRange(from = 0)
+    public long getCurrentPlayTime() {
+        return mAnimatedVectorState.mAnimatorSet.getCurrentPlayTime();
+    }
+
     @Override
     public void registerAnimationCallback(@NonNull Animatable2.AnimationCallback callback) {
         // Add listener accordingly.
diff --git a/work/workmanager-lint/src/main/java/androidx/work/lint/RxWorkerSetProgressDetector.kt b/work/workmanager-lint/src/main/java/androidx/work/lint/RxWorkerSetProgressDetector.kt
new file mode 100644
index 0000000..41a6ac4
--- /dev/null
+++ b/work/workmanager-lint/src/main/java/androidx/work/lint/RxWorkerSetProgressDetector.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.work.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.LintFix
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+import java.util.EnumSet
+
+class RxWorkerSetProgressDetector : Detector(), SourceCodeScanner {
+    companion object {
+        private const val SET_COMPLETABLE_PROGRESS = "setCompletableProgress"
+
+        private const val DESCRIPTION =
+            "`setProgress` is deprecated. Use `$SET_COMPLETABLE_PROGRESS` instead."
+
+        val ISSUE = Issue.create(
+            id = "UseRxSetProgress2",
+            briefDescription = DESCRIPTION,
+            explanation = """
+                Use `$SET_COMPLETABLE_PROGRESS(...)` instead of `setProgress(...) in `RxWorker`.
+            """,
+            androidSpecific = true,
+            category = Category.CORRECTNESS,
+            severity = Severity.FATAL,
+            implementation = Implementation(
+                RxWorkerSetProgressDetector::class.java,
+                EnumSet.of(Scope.JAVA_FILE)
+            )
+        )
+    }
+
+    override fun getApplicableMethodNames(): List<String> = listOf("setProgress")
+
+    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+        if (context.evaluator.isMemberInClass(method, "androidx.work.RxWorker")) {
+            val lintFix = LintFix.create()
+                .name("Use $SET_COMPLETABLE_PROGRESS instead")
+                .replace()
+                .text(method.name)
+                .with(SET_COMPLETABLE_PROGRESS)
+                .independent(true)
+                .build()
+
+            context.report(
+                issue = ISSUE,
+                location = context.getLocation(node),
+                message = DESCRIPTION,
+                quickfixData = lintFix
+            )
+        }
+    }
+}
diff --git a/work/workmanager-lint/src/main/java/androidx/work/lint/WorkManagerIssueRegistry.kt b/work/workmanager-lint/src/main/java/androidx/work/lint/WorkManagerIssueRegistry.kt
index 983d900..be42017 100644
--- a/work/workmanager-lint/src/main/java/androidx/work/lint/WorkManagerIssueRegistry.kt
+++ b/work/workmanager-lint/src/main/java/androidx/work/lint/WorkManagerIssueRegistry.kt
@@ -30,6 +30,7 @@
         InvalidPeriodicWorkRequestIntervalDetector.ISSUE,
         PeriodicEnqueueIssueDetector.ISSUE,
         RemoveWorkManagerInitializerDetector.ISSUE,
+        RxWorkerSetProgressDetector.ISSUE,
         SpecifyForegroundServiceTypeIssueDetector.ISSUE,
         SpecifyJobSchedulerIdRangeIssueDetector.ISSUE
     )
diff --git a/work/workmanager-lint/src/test/java/androidx/work/lint/RxWorkerSetProgressDetectorTest.kt b/work/workmanager-lint/src/test/java/androidx/work/lint/RxWorkerSetProgressDetectorTest.kt
new file mode 100644
index 0000000..e2a5fc9
--- /dev/null
+++ b/work/workmanager-lint/src/test/java/androidx/work/lint/RxWorkerSetProgressDetectorTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.work.lint
+
+import androidx.work.lint.Stubs.RX_WORKER
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest.kotlin
+import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
+import org.junit.Test
+
+class RxWorkerSetProgressDetectorTest {
+    @Test
+    fun setProgressDetectorTest() {
+        val application = kotlin(
+            "com/example/App.kt",
+            """
+            package com.example
+
+            import androidx.work.RxWorker
+
+            class App {
+                fun onCreate() {
+                    val worker = RxWorker()
+                    worker.setProgress()
+                }
+            }
+            """
+        ).indented().within("src")
+
+        lint().files(
+            // Source files
+            RX_WORKER,
+            application
+        ).issues(RxWorkerSetProgressDetector.ISSUE)
+            .run()
+            /* ktlint-disable max-line-length */
+            .expect(
+                """
+                src/com/example/App.kt:8: Error: setProgress is deprecated. Use setCompletableProgress instead. [UseRxSetProgress2]
+                        worker.setProgress()
+                        ~~~~~~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+            """.trimIndent()
+            )
+            .expectFixDiffs("""
+                Fix for src/com/example/App.kt line 8: Use setCompletableProgress instead:
+                @@ -8 +8
+                -         worker.setProgress()
+                +         worker.setCompletableProgress()
+            """.trimIndent())
+        /* ktlint-enable max-line-length */
+    }
+}
diff --git a/work/workmanager-lint/src/test/java/androidx/work/lint/Stubs.kt b/work/workmanager-lint/src/test/java/androidx/work/lint/Stubs.kt
index 1ba8dff..8c70516 100644
--- a/work/workmanager-lint/src/test/java/androidx/work/lint/Stubs.kt
+++ b/work/workmanager-lint/src/test/java/androidx/work/lint/Stubs.kt
@@ -62,6 +62,23 @@
         """
     ).indented().within("src")
 
+    val RX_WORKER: TestFile = kotlin(
+        "androidx/work/RxWorker.kt",
+        """
+            package androidx.work
+
+            open class RxWorker: ListenableWorker() {
+                fun setProgress() {
+
+                }
+
+                fun setCompletableProgress() {
+
+                }
+            }
+        """
+    ).indented().within("src")
+
     val WORK_REQUEST: TestFile = kotlin(
         "androidx/work/WorkRequest.kt",
         """
diff --git a/work/workmanager-rxjava2/api/2.4.0-alpha01.txt b/work/workmanager-rxjava2/api/2.4.0-alpha01.txt
index 757ca4c..8764610 100644
--- a/work/workmanager-rxjava2/api/2.4.0-alpha01.txt
+++ b/work/workmanager-rxjava2/api/2.4.0-alpha01.txt
@@ -5,7 +5,8 @@
     ctor public RxWorker(android.content.Context, androidx.work.WorkerParameters);
     method @MainThread public abstract io.reactivex.Single<androidx.work.ListenableWorker.Result!> createWork();
     method protected io.reactivex.Scheduler getBackgroundScheduler();
-    method public final io.reactivex.Single<java.lang.Void!> setProgress(androidx.work.Data);
+    method public final io.reactivex.Completable setCompletableProgress(androidx.work.Data);
+    method @Deprecated public final io.reactivex.Single<java.lang.Void!> setProgress(androidx.work.Data);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result!> startWork();
   }
 
diff --git a/work/workmanager-rxjava2/api/current.txt b/work/workmanager-rxjava2/api/current.txt
index 757ca4c..8764610 100644
--- a/work/workmanager-rxjava2/api/current.txt
+++ b/work/workmanager-rxjava2/api/current.txt
@@ -5,7 +5,8 @@
     ctor public RxWorker(android.content.Context, androidx.work.WorkerParameters);
     method @MainThread public abstract io.reactivex.Single<androidx.work.ListenableWorker.Result!> createWork();
     method protected io.reactivex.Scheduler getBackgroundScheduler();
-    method public final io.reactivex.Single<java.lang.Void!> setProgress(androidx.work.Data);
+    method public final io.reactivex.Completable setCompletableProgress(androidx.work.Data);
+    method @Deprecated public final io.reactivex.Single<java.lang.Void!> setProgress(androidx.work.Data);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result!> startWork();
   }
 
diff --git a/work/workmanager-rxjava2/api/public_plus_experimental_2.4.0-alpha01.txt b/work/workmanager-rxjava2/api/public_plus_experimental_2.4.0-alpha01.txt
index 757ca4c..8764610 100644
--- a/work/workmanager-rxjava2/api/public_plus_experimental_2.4.0-alpha01.txt
+++ b/work/workmanager-rxjava2/api/public_plus_experimental_2.4.0-alpha01.txt
@@ -5,7 +5,8 @@
     ctor public RxWorker(android.content.Context, androidx.work.WorkerParameters);
     method @MainThread public abstract io.reactivex.Single<androidx.work.ListenableWorker.Result!> createWork();
     method protected io.reactivex.Scheduler getBackgroundScheduler();
-    method public final io.reactivex.Single<java.lang.Void!> setProgress(androidx.work.Data);
+    method public final io.reactivex.Completable setCompletableProgress(androidx.work.Data);
+    method @Deprecated public final io.reactivex.Single<java.lang.Void!> setProgress(androidx.work.Data);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result!> startWork();
   }
 
diff --git a/work/workmanager-rxjava2/api/public_plus_experimental_current.txt b/work/workmanager-rxjava2/api/public_plus_experimental_current.txt
index 757ca4c..8764610 100644
--- a/work/workmanager-rxjava2/api/public_plus_experimental_current.txt
+++ b/work/workmanager-rxjava2/api/public_plus_experimental_current.txt
@@ -5,7 +5,8 @@
     ctor public RxWorker(android.content.Context, androidx.work.WorkerParameters);
     method @MainThread public abstract io.reactivex.Single<androidx.work.ListenableWorker.Result!> createWork();
     method protected io.reactivex.Scheduler getBackgroundScheduler();
-    method public final io.reactivex.Single<java.lang.Void!> setProgress(androidx.work.Data);
+    method public final io.reactivex.Completable setCompletableProgress(androidx.work.Data);
+    method @Deprecated public final io.reactivex.Single<java.lang.Void!> setProgress(androidx.work.Data);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result!> startWork();
   }
 
diff --git a/work/workmanager-rxjava2/api/restricted_2.4.0-alpha01.txt b/work/workmanager-rxjava2/api/restricted_2.4.0-alpha01.txt
index 757ca4c..8764610 100644
--- a/work/workmanager-rxjava2/api/restricted_2.4.0-alpha01.txt
+++ b/work/workmanager-rxjava2/api/restricted_2.4.0-alpha01.txt
@@ -5,7 +5,8 @@
     ctor public RxWorker(android.content.Context, androidx.work.WorkerParameters);
     method @MainThread public abstract io.reactivex.Single<androidx.work.ListenableWorker.Result!> createWork();
     method protected io.reactivex.Scheduler getBackgroundScheduler();
-    method public final io.reactivex.Single<java.lang.Void!> setProgress(androidx.work.Data);
+    method public final io.reactivex.Completable setCompletableProgress(androidx.work.Data);
+    method @Deprecated public final io.reactivex.Single<java.lang.Void!> setProgress(androidx.work.Data);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result!> startWork();
   }
 
diff --git a/work/workmanager-rxjava2/api/restricted_current.txt b/work/workmanager-rxjava2/api/restricted_current.txt
index 757ca4c..8764610 100644
--- a/work/workmanager-rxjava2/api/restricted_current.txt
+++ b/work/workmanager-rxjava2/api/restricted_current.txt
@@ -5,7 +5,8 @@
     ctor public RxWorker(android.content.Context, androidx.work.WorkerParameters);
     method @MainThread public abstract io.reactivex.Single<androidx.work.ListenableWorker.Result!> createWork();
     method protected io.reactivex.Scheduler getBackgroundScheduler();
-    method public final io.reactivex.Single<java.lang.Void!> setProgress(androidx.work.Data);
+    method public final io.reactivex.Completable setCompletableProgress(androidx.work.Data);
+    method @Deprecated public final io.reactivex.Single<java.lang.Void!> setProgress(androidx.work.Data);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result!> startWork();
   }
 
diff --git a/work/workmanager-rxjava2/src/main/java/androidx/work/RxWorker.java b/work/workmanager-rxjava2/src/main/java/androidx/work/RxWorker.java
index 2200863..bea67d8 100644
--- a/work/workmanager-rxjava2/src/main/java/androidx/work/RxWorker.java
+++ b/work/workmanager-rxjava2/src/main/java/androidx/work/RxWorker.java
@@ -28,6 +28,7 @@
 
 import java.util.concurrent.Executor;
 
+import io.reactivex.Completable;
 import io.reactivex.Scheduler;
 import io.reactivex.Single;
 import io.reactivex.SingleObserver;
@@ -120,15 +121,34 @@
     /**
      * Updates the progress for a {@link RxWorker}. This method returns a {@link Single} unlike the
      * {@link ListenableWorker#setProgressAsync(Data)} API.
+     * <p>
+     * This method is deprecated. Use {@link #setCompletableProgress(Data)} instead.
      *
      * @param data The progress {@link Data}
      * @return The {@link Single}
+     * @deprecated This method is being deprecated because it is impossible to signal success via
+     * a `Single&lt;Void&gt;` type. A {@link Completable} should have been used.
+     * <p>
+     * Use {@link #setCompletableProgress(Data)} instead.
      */
     @NonNull
+    @Deprecated
     public final Single<Void> setProgress(@NonNull Data data) {
         return Single.fromFuture(setProgressAsync(data));
     }
 
+    /**
+     * Updates the progress for a {@link RxWorker}. This method returns a {@link Completable}
+     * unlike the {@link ListenableWorker#setProgressAsync(Data)} API.
+     *
+     * @param data The progress {@link Data}
+     * @return The {@link Completable}
+     */
+    @NonNull
+    public final Completable setCompletableProgress(@NonNull Data data) {
+        return Completable.fromFuture(setProgressAsync(data));
+    }
+
     @Override
     public void onStopped() {
         super.onStopped();
diff --git a/work/workmanager-rxjava2/src/test/java/androidx/work/SetCompletableProgressTest.kt b/work/workmanager-rxjava2/src/test/java/androidx/work/SetCompletableProgressTest.kt
new file mode 100644
index 0000000..a1bd9fc
--- /dev/null
+++ b/work/workmanager-rxjava2/src/test/java/androidx/work/SetCompletableProgressTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.work
+
+import android.content.Context
+import androidx.work.ListenableWorker.Result
+import androidx.work.impl.utils.SynchronousExecutor
+import androidx.work.impl.utils.futures.SettableFuture
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.mock
+import java.util.UUID
+import java.util.concurrent.Executor
+
+@RunWith(JUnit4::class)
+class SetCompletableProgressTest {
+
+    private lateinit var context: Context
+
+    @Before
+    fun setUp() {
+        context = mock(Context::class.java)
+    }
+
+    @Test
+    fun testSetProgressCompletable() {
+        val progressUpdater = ProgressUpdater { _, _, _ ->
+            val future = SettableFuture.create<Void>()
+            future.set(null)
+            future
+        }
+        val worker =
+            TestRxWorker(context, createWorkerParams(progressUpdater = progressUpdater))
+        val result = worker.startWork().get()
+        assertEquals(result, Result.success())
+    }
+
+    private fun createWorkerParams(
+        executor: Executor = SynchronousExecutor(),
+        progressUpdater: ProgressUpdater = mock(ProgressUpdater::class.java),
+        foregroundUpdater: ForegroundUpdater = mock(ForegroundUpdater::class.java)
+    ) = WorkerParameters(
+        UUID.randomUUID(),
+        Data.EMPTY,
+        emptyList(),
+        WorkerParameters.RuntimeExtras(),
+        1,
+        executor,
+        RxWorkerTest.InstantWorkTaskExecutor(),
+        WorkerFactory.getDefaultWorkerFactory(),
+        progressUpdater,
+        foregroundUpdater
+    )
+}
diff --git a/work/workmanager-rxjava2/src/test/java/androidx/work/TestRxWorker.kt b/work/workmanager-rxjava2/src/test/java/androidx/work/TestRxWorker.kt
new file mode 100644
index 0000000..c187fe0
--- /dev/null
+++ b/work/workmanager-rxjava2/src/test/java/androidx/work/TestRxWorker.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.work
+
+import android.content.Context
+import io.reactivex.Single
+
+class TestRxWorker(context: Context, parameters: WorkerParameters) : RxWorker(context, parameters) {
+    override fun createWork(): Single<Result> {
+        setCompletableProgress(Data.EMPTY).blockingAwait()
+        return Single.just(Result.success())
+    }
+}