Merge "AndroidX Webkit: demo the per-webview-enable API" into androidx-master-dev
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerTest.java b/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerTest.java
index f9695e0..7c45fa9 100644
--- a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerTest.java
+++ b/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerTest.java
@@ -23,14 +23,15 @@
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
 import static androidx.test.espresso.matcher.RootMatchers.isPlatformPopup;
 import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
-import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
 import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 
+import android.app.Instrumentation;
 import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
 import android.os.SystemClock;
@@ -50,6 +51,8 @@
 import androidx.test.espresso.action.Swipe;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.testutils.PollingCheck;
 
 import org.hamcrest.Matcher;
 import org.junit.After;
@@ -63,6 +66,7 @@
 public class AppCompatSpinnerTest
         extends AppCompatBaseViewTest<AppCompatSpinnerActivity, AppCompatSpinner> {
     private static final String EARTH = "Earth";
+    private Instrumentation mInstrumentation;
 
     public AppCompatSpinnerTest() {
         super(AppCompatSpinnerActivity.class);
@@ -77,6 +81,8 @@
     @Override
     public void setUp() {
         super.setUp();
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+
         if (mActivity.getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
             mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
             SystemClock.sleep(250);
@@ -105,6 +111,9 @@
         // Click the spinner to show its popup content
         onView(withId(spinnerId)).perform(click());
 
+        // Wait until the popup is showing
+        waitUntilPopupIsShown(spinner);
+
         // The internal implementation details of the AppCompatSpinner's popup content depends
         // on the platform version itself (in android.widget.PopupWindow) as well as on when the
         // popup theme is being applied first (in XML or at runtime). Instead of trying to catch
@@ -124,6 +133,9 @@
 
         // Click an entry in the popup to dismiss it
         onView(withText(itemText)).perform(click());
+
+        // Wait until the popup is gone
+        waitUntilPopupIsHidden(spinner);
     }
 
     @LargeTest
@@ -172,6 +184,8 @@
         assertThat(popup, instanceOf(AppCompatSpinner.DialogPopup.class));
 
         onView(withId(R.id.spinner_dialog_popup)).perform(click());
+        // Wait until the popup is showing
+        waitUntilPopupIsShown(spinner);
 
         final AppCompatSpinner.DialogPopup dialogPopup = (AppCompatSpinner.DialogPopup) popup;
         assertThat(dialogPopup.mPopup, instanceOf(AlertDialog.class));
@@ -180,28 +194,47 @@
     @LargeTest
     @Test
     public void testChangeOrientationDialogPopupPersists() {
-        verifyChangeOrientationPopupPersists(R.id.spinner_dialog_popup);
+        verifyChangeOrientationPopupPersists(R.id.spinner_dialog_popup, true);
     }
 
     @LargeTest
     @Test
     public void testChangeOrientationDropdownPopupPersists() {
-        verifyChangeOrientationPopupPersists(R.id.spinner_dropdown_popup);
+        verifyChangeOrientationPopupPersists(R.id.spinner_dropdown_popup, false);
     }
 
-    private void verifyChangeOrientationPopupPersists(@IdRes int spinnerId) {
+    private void verifyChangeOrientationPopupPersists(@IdRes int spinnerId, boolean isDialog) {
         onView(withId(spinnerId)).perform(click());
+        // Wait until the popup is showing
+        waitUntilPopupIsShown((AppCompatSpinner) mActivity.findViewById(spinnerId));
+
+        // Use ActivityMonitor so that we can get the Activity instance after it has been
+        // recreated when the rotation request completes
+        Instrumentation.ActivityMonitor monitor =
+                new Instrumentation.ActivityMonitor(mActivity.getClass().getName(), null, false);
+        mInstrumentation.addMonitor(monitor);
         mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
-        onView(withText(EARTH)).check(matches(isDisplayed()));
+        SystemClock.sleep(250);
+        mInstrumentation.waitForIdleSync();
+
+        mActivity = (AppCompatSpinnerActivity) mInstrumentation.waitForMonitor(monitor);
+
+        // Now we can get the new (post-rotation) instance of our spinner
+        AppCompatSpinner newSpinner = mActivity.findViewById(spinnerId);
+        // And check that it's showing the popup
+        assertTrue(newSpinner.getInternalPopup().isShowing());
     }
 
     @LargeTest
     @Test
     public void testSlowScroll() {
-        onView(withId(R.id.spinner_dropdown_popup_with_scroll)).perform(click());
-
         final AppCompatSpinner spinner = mContainer
                 .findViewById(R.id.spinner_dropdown_popup_with_scroll);
+        onView(withId(R.id.spinner_dropdown_popup_with_scroll)).perform(click());
+
+        // Wait until the popup is showing
+        waitUntilPopupIsShown(spinner);
+
         String secondItem = (String) spinner.getAdapter().getItem(1);
 
         onView(isAssignableFrom(DropDownListView.class)).perform(slowScrollPopup());
@@ -211,8 +244,7 @@
 
         // because we scroll twice with one element height each,
         // the second item should not be visible
-        onView(withText(secondItem))
-                .check(doesNotExist());
+        onView(withText(secondItem)).check(doesNotExist());
     }
 
     private ViewAction slowScrollPopup() {
@@ -264,4 +296,22 @@
         // so we add a little bit more to be safe
         return child.getHeight() * 2;
     }
+
+    private void waitUntilPopupIsShown(final AppCompatSpinner spinner) {
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return spinner.getInternalPopup().isShowing();
+            }
+        });
+    }
+
+    private void waitUntilPopupIsHidden(final AppCompatSpinner spinner) {
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return !spinner.getInternalPopup().isShowing();
+            }
+        });
+    }
 }
diff --git a/appcompat/src/androidTest/res/layout/appcompat_spinner_activity.xml b/appcompat/src/androidTest/res/layout/appcompat_spinner_activity.xml
index 38228a9..534048e 100644
--- a/appcompat/src/androidTest/res/layout/appcompat_spinner_activity.xml
+++ b/appcompat/src/androidTest/res/layout/appcompat_spinner_activity.xml
@@ -25,13 +25,20 @@
         android:layout_height="wrap_content"
         android:orientation="vertical">
 
+        <View
+            android:id="@+id/for_focus"
+            android:layout_width="match_parent"
+            android:layout_height="4dp"
+            android:focusable="true"/>
+
         <androidx.appcompat.widget.AppCompatSpinner
             android:id="@+id/view_tinted_no_background"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:entries="@array/planets_array"
             app:backgroundTint="@color/color_state_list_lilac"
-            app:backgroundTintMode="src_in" />
+            app:backgroundTintMode="src_in"
+            android:focusable="false" />
 
         <androidx.appcompat.widget.AppCompatSpinner
             android:id="@+id/view_tinted_background"
@@ -40,20 +47,23 @@
             android:background="@drawable/test_drawable"
             android:entries="@array/planets_array"
             app:backgroundTint="@color/color_state_list_lilac"
-            app:backgroundTintMode="src_in" />
+            app:backgroundTintMode="src_in"
+            android:focusable="false" />
 
         <androidx.appcompat.widget.AppCompatSpinner
             android:id="@+id/view_untinted_no_background"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:entries="@array/planets_array" />
+            android:entries="@array/planets_array"
+            android:focusable="false" />
 
         <androidx.appcompat.widget.AppCompatSpinner
             android:id="@+id/view_untinted_background"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:entries="@array/planets_array"
-            android:background="@drawable/test_background_green" />
+            android:background="@drawable/test_background_green"
+            android:focusable="false" />
 
         <androidx.appcompat.widget.AppCompatSpinner
             android:id="@+id/view_magenta_themed_popup"
@@ -62,7 +72,8 @@
             android:entries="@array/planets_array"
             app:backgroundTint="@color/color_state_list_lilac"
             app:backgroundTintMode="src_in"
-            app:popupTheme="@style/MagentaSpinnerPopupTheme" />
+            app:popupTheme="@style/MagentaSpinnerPopupTheme"
+            android:focusable="false" />
 
         <androidx.appcompat.widget.AppCompatSpinner
             android:id="@+id/view_unthemed_popup"
@@ -70,7 +81,8 @@
             android:layout_height="wrap_content"
             android:entries="@array/planets_array"
             app:backgroundTint="@color/color_state_list_lilac"
-            app:backgroundTintMode="src_in" />
+            app:backgroundTintMode="src_in"
+            android:focusable="false" />
 
         <androidx.appcompat.widget.AppCompatSpinner
             android:id="@+id/view_ocean_themed_popup"
@@ -78,28 +90,32 @@
             android:layout_height="wrap_content"
             android:entries="@array/planets_array"
             android:spinnerMode="dropdown"
-            app:popupTheme="@style/OceanSpinnerPopupTheme" />
+            app:popupTheme="@style/OceanSpinnerPopupTheme"
+            android:focusable="false" />
 
         <androidx.appcompat.widget.AppCompatSpinner
             android:id="@+id/spinner_dialog_popup"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:entries="@array/planets_array"
-            android:spinnerMode="dialog" />
+            android:spinnerMode="dialog"
+            android:focusable="false" />
 
         <androidx.appcompat.widget.AppCompatSpinner
-                android:id="@+id/spinner_dropdown_popup"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:entries="@array/planets_array"
-                android:spinnerMode="dropdown" />
+            android:id="@+id/spinner_dropdown_popup"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:entries="@array/planets_array"
+            android:focusable="false"
+            android:spinnerMode="dropdown" />
 
         <androidx.appcompat.widget.AppCompatSpinner
-                android:id="@+id/spinner_dropdown_popup_with_scroll"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:entries="@array/numbers_array"
-                android:spinnerMode="dropdown" />
+            android:id="@+id/spinner_dropdown_popup_with_scroll"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:entries="@array/numbers_array"
+            android:focusable="false"
+            android:spinnerMode="dropdown" />
     </LinearLayout>
 
 </ScrollView>
diff --git a/benchmark/src/androidTest/AndroidManifest.xml b/benchmark/src/androidTest/AndroidManifest.xml
index 522a8f4..d3e8cf0 100644
--- a/benchmark/src/androidTest/AndroidManifest.xml
+++ b/benchmark/src/androidTest/AndroidManifest.xml
@@ -24,5 +24,7 @@
     <!-- Important: disable debuggable for accurate performance results -->
     <application
         android:debuggable="false"
-        tools:replace="android:debuggable" />
+        tools:replace="android:debuggable">
+        <activity android:name="android.app.Activity"/>
+    </application>
 </manifest>
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/ActivityScenarioBenchmark.kt b/benchmark/src/androidTest/java/androidx/benchmark/ActivityScenarioBenchmark.kt
new file mode 100644
index 0000000..5f21bd5
--- /dev/null
+++ b/benchmark/src/androidTest/java/androidx/benchmark/ActivityScenarioBenchmark.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 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.benchmark
+
+import android.app.Activity
+import androidx.test.core.app.ActivityScenario
+import androidx.test.filters.LargeTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@LargeTest
+@RunWith(JUnit4::class)
+class ActivityScenarioBenchmark {
+    @get:Rule
+    val benchmarkRule = BenchmarkRule()
+
+    private lateinit var activityRule: ActivityScenario<Activity>
+
+    @Before
+    fun setup() {
+        activityRule = ActivityScenario.launch(Activity::class.java)
+    }
+
+    @Test
+    fun activityScenario() {
+        activityRule.onActivity {
+            var i = 0
+            benchmarkRule.measureRepeated {
+                i++
+            }
+        }
+    }
+}
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/ActivityTestRuleBenchmark.kt b/benchmark/src/androidTest/java/androidx/benchmark/ActivityTestRuleBenchmark.kt
new file mode 100644
index 0000000..e1a3547
--- /dev/null
+++ b/benchmark/src/androidTest/java/androidx/benchmark/ActivityTestRuleBenchmark.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 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.benchmark
+
+import android.app.Activity
+import androidx.test.filters.LargeTest
+import androidx.test.rule.ActivityTestRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@LargeTest
+@RunWith(JUnit4::class)
+class ActivityTestRuleBenchmark {
+    @get:Rule
+    val benchmarkRule = BenchmarkRule()
+
+    @get:Rule
+    val activityRule = ActivityTestRule(Activity::class.java)
+
+    @Test
+    fun activityTestRule() {
+        activityRule.runOnUiThread {
+            var i = 0
+            benchmarkRule.measureRepeated {
+                i++
+            }
+        }
+    }
+}
diff --git a/benchmark/src/main/java/androidx/benchmark/Clocks.kt b/benchmark/src/main/java/androidx/benchmark/Clocks.kt
index 41ad0c4..fa41332 100644
--- a/benchmark/src/main/java/androidx/benchmark/Clocks.kt
+++ b/benchmark/src/main/java/androidx/benchmark/Clocks.kt
@@ -18,6 +18,7 @@
 
 import android.util.Log
 import java.io.File
+import java.io.IOException
 
 internal object Clocks {
     private const val TAG = "Benchmark"
@@ -98,11 +99,17 @@
     }
 
     /**
-     * Read the text of a file as a String, null if file doesn't exist.
+     * Read the text of a file as a String, null if file doesn't exist or can't be read.
      */
     private fun readFileTextOrNull(path: String): String? {
-        File(path).run {
-            return if (exists()) readText().trim() else null
+        try {
+            File(path).run {
+                return if (exists()) {
+                    readText().trim()
+                } else null
+            }
+        } catch (e: IOException) {
+            return null
         }
     }
 }
diff --git a/benchmark/src/main/java/androidx/benchmark/IsolationActivity.kt b/benchmark/src/main/java/androidx/benchmark/IsolationActivity.kt
index 7a96db6..eaf50e8 100644
--- a/benchmark/src/main/java/androidx/benchmark/IsolationActivity.kt
+++ b/benchmark/src/main/java/androidx/benchmark/IsolationActivity.kt
@@ -41,6 +41,7 @@
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 class IsolationActivity : android.app.Activity() {
     var resumed = false
+    private var destroyed = false
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -50,7 +51,7 @@
         overridePendingTransition(0, 0)
 
         val old = singleton.getAndSet(this)
-        if (old != null) {
+        if (old != null && !old.destroyed && !old.isFinishing) {
             throw IllegalStateException("Only one IsolationActivity should exist")
         }
 
@@ -71,6 +72,11 @@
         resumed = false
     }
 
+    override fun onDestroy() {
+        super.onDestroy()
+        destroyed = true
+    }
+
     /** finish is ignored! we defer until [actuallyFinish] is called. */
     override fun finish() {
     }
diff --git a/build.gradle b/build.gradle
index 6d5d017..a583e16 100644
--- a/build.gradle
+++ b/build.gradle
@@ -48,6 +48,3 @@
 
 apply plugin: AndroidXPlugin
 
-// AndroidX needed before jetify since it accesses the createArchive task name directly.
-apply from: 'buildSrc/jetify.gradle'
-
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
index e336047..67eb40e 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
@@ -61,6 +61,7 @@
 import org.gradle.kotlin.dsl.apply
 import org.gradle.kotlin.dsl.configure
 import org.gradle.kotlin.dsl.extra
+import org.gradle.kotlin.dsl.get
 import org.gradle.kotlin.dsl.getPlugin
 import org.gradle.kotlin.dsl.withType
 import org.gradle.testing.jacoco.plugins.JacocoPluginExtension
@@ -201,15 +202,22 @@
         }
         val createLibraryBuildInfoFilesTask =
             tasks.register(CREATE_LIBRARY_BUILD_INFO_FILES_TASK)
-        val buildOnServerTask = tasks.create(BUILD_ON_SERVER_TASK)
+
+        extra.set("versionChecker", GMavenVersionChecker(logger))
+        val createArchiveTask = Release.getGlobalFullZipTask(this)
+
+        val buildOnServerTask = tasks.create(BUILD_ON_SERVER_TASK, BuildOnServer::class.java)
         buildOnServerTask.dependsOn(createLibraryBuildInfoFilesTask)
 
+        val partiallyDejetifyArchiveTask = partiallyDejetifyArchiveTask(
+            createArchiveTask.get().archiveFile)
+        buildOnServerTask.dependsOn(partiallyDejetifyArchiveTask)
+
         val projectModules = ConcurrentHashMap<String, String>()
         extra.set("projects", projectModules)
         tasks.all { task ->
             if (task.name.startsWith(Release.DIFF_TASK_PREFIX) ||
                     "distDocs" == task.name ||
-                    "partiallyDejetifyArchive" == task.name ||
                     CheckExternalDependencyLicensesTask.TASK_NAME == task.name) {
                 buildOnServerTask.dependsOn(task)
             }
@@ -236,6 +244,15 @@
             }
         }
 
+        project(":jetifier-standalone").afterEvaluate { standAloneProject ->
+            partiallyDejetifyArchiveTask.configure {
+                it.dependsOn(standAloneProject.tasks.named("installDist"))
+            }
+            createArchiveTask.configure {
+                it.dependsOn(standAloneProject.tasks.named("dist"))
+            }
+        }
+
         val createCoverageJarTask = Jacoco.createCoverageJarTask(this)
         buildOnServerTask.dependsOn(createCoverageJarTask)
 
@@ -243,8 +260,6 @@
             it.dependsOn(createCoverageJarTask)
         }
 
-        extra.set("versionChecker", GMavenVersionChecker(logger))
-        Release.createGlobalArchiveTask(this)
         val allDocsTask = DiffAndDocs.configureDiffAndDocs(this, projectDir,
                 DacOptions("androidx", "ANDROIDX_DATA"),
                 listOf(RELEASE_RULE))
diff --git a/buildSrc/src/main/kotlin/androidx/build/BuildOnServer.kt b/buildSrc/src/main/kotlin/androidx/build/BuildOnServer.kt
new file mode 100644
index 0000000..f5697d1
--- /dev/null
+++ b/buildSrc/src/main/kotlin/androidx/build/BuildOnServer.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 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.build
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.TaskAction
+import java.io.File
+import java.io.FileNotFoundException
+
+/**
+ * Task for building all of Androidx libraries and documentation
+ *
+ * AndroidXPlugin configuration add dependencies to BuildOnServer for all of the tasks that
+ * produce artifacts that we want to build on server builds
+ * When BuildOnServer executes, it double-checks that all expected artifacts were built
+ */
+open class BuildOnServer : DefaultTask() {
+
+    init {
+        group = "Build"
+        description = "Builds all of the Androidx libraries and documentation"
+    }
+
+    @InputFiles
+    fun getRequiredFiles(): List<File> {
+        val distributionDirectory = project.getDistributionDirectory()
+        val buildId = getBuildId()
+        return listOf(
+            "android-support-public-docs-$buildId.zip",
+            "android-support-tipOfTree-docs-$buildId.zip",
+            "dokkaTipOfTreeDocs-$buildId.zip",
+            "dokkaPublicDocs-$buildId.zip",
+            "gmaven-diff-all-$buildId.zip",
+            "jetifier-standalone.zip",
+            "top-of-tree-m2repository-all-$buildId.zip",
+            "top-of-tree-m2repository-partially-dejetified-$buildId.zip"
+        ).map { fileName -> File(distributionDirectory, fileName) }
+    }
+
+    @TaskAction
+    fun checkAllBuildOutputs() {
+
+        val missingFiles = mutableListOf<String>()
+        getRequiredFiles().forEach { file ->
+            if (!file.exists()) {
+                missingFiles.add(file.name)
+            }
+        }
+
+        if (missingFiles.isNotEmpty()) {
+            val missingFileString = missingFiles.reduce { acc, s -> "$acc, $s" }
+            throw FileNotFoundException("buildOnServer required output missing: $missingFileString")
+        }
+    }
+}
\ No newline at end of file
diff --git a/buildSrc/jetify.gradle b/buildSrc/src/main/kotlin/androidx/build/Jetify.kt
similarity index 61%
rename from buildSrc/jetify.gradle
rename to buildSrc/src/main/kotlin/androidx/build/Jetify.kt
index 9efebca..1dcbc05 100644
--- a/buildSrc/jetify.gradle
+++ b/buildSrc/src/main/kotlin/androidx/build/Jetify.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright 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.
@@ -14,12 +14,16 @@
  * limitations under the License.
  */
 
-import androidx.build.BuildServerConfigurationKt
-import androidx.build.Release
-def standaloneProject = project(":jetifier-standalone")
-def jetifierBin = file("${standaloneProject.buildDir}/install/jetifier-standalone/bin/jetifier-standalone")
+package androidx.build
 
-def archivesToDejetify = [
+import org.gradle.api.Project
+import org.gradle.api.file.RegularFile
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.Exec
+import org.gradle.api.tasks.TaskProvider
+import org.gradle.api.tasks.bundling.Zip
+
+val archivesToDejetify = listOf(
     "m2repository/androidx/activity/**",
     "m2repository/androidx/annotation/**",
     "m2repository/androidx/appcompat/**",
@@ -80,41 +84,41 @@
     "m2repository/androidx/wear/**",
     "m2repository/androidx/media2/**",
     "m2repository/androidx/concurrent/**",
-    "m2repository/androidx/sharetarget/**"
-]
+    "m2repository/androidx/sharetarget/**")
 
-task stripArchiveForPartialDejetification(type: Zip) {
-    dependsOn tasks[Release.FULL_ARCHIVE_TASK_NAME]
-    from zipTree(project.tasks['createArchive'].archivePath)
-    destinationDir rootProject.buildDir
-    archiveName "stripped_archive_partial.zip"
-    include archivesToDejetify
-    doLast {
-        if (archivePath.exists()) {
-            project.logger.info("stripArchiveForPartialDejetification sees that " + archivePath + " exists")
-        } else {
-            throw new Exception("stripArchiveForPartialDejetification sees that " + archivePath + " does not exist!?")
-        }
+fun Project.partiallyDejetifyArchiveTask(archiveFile: Provider<RegularFile>): TaskProvider<Exec> {
+    val standaloneProject = project(":jetifier-standalone")
+    val stripTask = stripArchiveForPartialDejetificationTask(archiveFile)
+
+    return tasks.register("partiallyDejetifyArchive", Exec::class.java) {
+        val outputFileName = "${getDistributionDirectory().absolutePath}/" +
+                "top-of-tree-m2repository-partially-dejetified-${getBuildId()}.zip"
+        val jetifierBin = "${standaloneProject.buildDir}/install/jetifier-standalone/bin/" +
+                "jetifier-standalone"
+        val migrationConfig = "${standaloneProject.projectDir.getParentFile()}/migration.config"
+
+        it.dependsOn(stripTask)
+        it.inputs.file(stripTask.get().archiveFile)
+        it.outputs.file(outputFileName)
+
+        it.commandLine = listOf(
+            jetifierBin,
+            "-i", "${it.inputs.files.singleFile}",
+            "-o", "${it.outputs.files.singleFile}",
+            "-c", migrationConfig,
+            "--log", "warning",
+            "--reversed",
+            "--rebuildTopOfTree")
     }
 }
 
-task partiallyDejetifyArchive(type: Exec) {
-    description "Produces a zip of partially dejetified artifacts by running Dejetifier against refactored" +
-            " artifacts, for temporary migration purposes."
-
-    dependsOn ':jetifier-standalone:installDist'
-    dependsOn project.tasks['stripArchiveForPartialDejetification']
-    inputs.file project.tasks['stripArchiveForPartialDejetification'].archivePath
-
-    outputs.file "${BuildServerConfigurationKt.getDistributionDirectory(rootProject).absolutePath}/top-of-tree-m2repository-partially-dejetified-${BuildServerConfigurationKt.getBuildId()}.zip"
-
-    commandLine (
-        "${jetifierBin}",
-        "-i", "${inputs.files.singleFile}",
-        "-o", "${outputs.files.singleFile}",
-	"-c", "${standaloneProject.projectDir.getParentFile()}/migration.config",
-        "--log", "warning",
-        "--reversed",
-        "--rebuildTopOfTree"
-    )
+fun Project.stripArchiveForPartialDejetificationTask(archiveFile: Provider<RegularFile>):
+        TaskProvider<Zip> {
+    return tasks.register("stripArchiveForPartialDejetification", Zip::class.java) {
+        it.dependsOn(rootProject.tasks.named(Release.FULL_ARCHIVE_TASK_NAME))
+        it.from(zipTree(archiveFile))
+        it.destinationDirectory.set(rootProject.buildDir)
+        it.archiveFileName.set("stripped_archive_partial.zip")
+        it.include(archivesToDejetify)
+    }
 }
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index 273b8b2..3c294d2 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -25,24 +25,32 @@
 import androidx.build.Strategy.TipOfTree
 
 val RELEASE_RULE = docsRules("public", false) {
-    prebuilts(LibraryGroups.ACTIVITY, "1.0.0-alpha07")
-    prebuilts(LibraryGroups.ANNOTATION, "1.1.0-beta01")
-    prebuilts(LibraryGroups.APPCOMPAT, "1.1.0-alpha04")
-    prebuilts(LibraryGroups.ARCH_CORE, "2.1.0-alpha02")
+    prebuilts(LibraryGroups.ACTIVITY, "1.0.0-alpha08")
+    prebuilts(LibraryGroups.ANNOTATION, "1.1.0-rc01")
+    prebuilts(LibraryGroups.APPCOMPAT, "1.1.0-alpha05")
+    prebuilts(LibraryGroups.ARCH_CORE, "2.1.0-beta01")
     prebuilts(LibraryGroups.ASYNCLAYOUTINFLATER, "1.0.0")
+    ignore(LibraryGroups.BENCHMARK.group, "benchmark-gradle-plugin")
+    prebuilts(LibraryGroups.BENCHMARK, "1.0.0-alpha01")
     prebuilts(LibraryGroups.BIOMETRIC, "biometric", "1.0.0-alpha04")
     prebuilts(LibraryGroups.BROWSER, "1.0.0")
+    ignore(LibraryGroups.CAMERA.group, "camera-view")
+    ignore(LibraryGroups.CAMERA.group, "camera-testing")
+    ignore(LibraryGroups.CAMERA.group, "camera-extensions")
+    ignore(LibraryGroups.CAMERA.group, "camera-extensions-stub")
+    ignore(LibraryGroups.CAMERA.group, "camera-testlib-extensions")
+    prebuilts(LibraryGroups.CAMERA, "1.0.0-alpha01")
     ignore(LibraryGroups.CAR.group, "car-moderator")
     prebuilts(LibraryGroups.CAR, "car-cluster", "1.0.0-alpha5")
     prebuilts(LibraryGroups.CAR, "car", "1.0.0-alpha7")
             .addStubs("car/stubs/android.car.jar")
     prebuilts(LibraryGroups.CARDVIEW, "1.0.0")
-    prebuilts(LibraryGroups.COLLECTION, "1.1.0-beta01")
-    prebuilts(LibraryGroups.CONCURRENT, "1.0.0-alpha03")
+    prebuilts(LibraryGroups.COLLECTION, "1.1.0-rc01")
+    prebuilts(LibraryGroups.CONCURRENT, "1.0.0-beta01")
     prebuilts(LibraryGroups.CONTENTPAGER, "1.0.0")
     prebuilts(LibraryGroups.COORDINATORLAYOUT, "1.1.0-alpha01")
-    prebuilts(LibraryGroups.CORE, "core", "1.1.0-alpha05")
-    prebuilts(LibraryGroups.CORE, "core-ktx", "1.1.0-alpha05")
+    prebuilts(LibraryGroups.CORE, "core", "1.1.0-beta01")
+    prebuilts(LibraryGroups.CORE, "core-ktx", "1.1.0-beta01")
     prebuilts(LibraryGroups.CURSORADAPTER, "1.0.0")
     prebuilts(LibraryGroups.CUSTOMVIEW, "1.0.0")
     prebuilts(LibraryGroups.DOCUMENTFILE, "1.0.0")
@@ -52,11 +60,11 @@
     prebuilts(LibraryGroups.EMOJI, "1.0.0")
     prebuilts(LibraryGroups.ENTERPRISE, "1.0.0-alpha01")
     prebuilts(LibraryGroups.EXIFINTERFACE, "1.1.0-alpha01")
-    prebuilts(LibraryGroups.FRAGMENT, "1.1.0-alpha07")
+    prebuilts(LibraryGroups.FRAGMENT, "1.1.0-alpha08")
     prebuilts(LibraryGroups.GRIDLAYOUT, "1.0.0")
     prebuilts(LibraryGroups.HEIFWRITER, "1.0.0")
     prebuilts(LibraryGroups.INTERPOLATOR, "1.0.0")
-    prebuilts(LibraryGroups.LEANBACK, "1.1.0-alpha01")
+    prebuilts(LibraryGroups.LEANBACK, "1.1.0-alpha02")
     prebuilts(LibraryGroups.LEGACY, "1.0.0")
     ignore(LibraryGroups.LIFECYCLE.group, "lifecycle-savedstate-core")
     ignore(LibraryGroups.LIFECYCLE.group, "lifecycle-savedstate-fragment")
@@ -67,57 +75,54 @@
     ignore(LibraryGroups.LIFECYCLE.group, "lifecycle-runtime-ktx")
     ignore(LibraryGroups.LIFECYCLE.group, "lifecycle-runtime-ktx-lint")
     prebuilts(LibraryGroups.LIFECYCLE, "lifecycle-viewmodel-savedstate", "1.0.0-alpha01")
-    prebuilts(LibraryGroups.LIFECYCLE, "2.1.0-alpha04")
+    prebuilts(LibraryGroups.LIFECYCLE, "2.2.0-alpha01")
     prebuilts(LibraryGroups.LOADER, "1.1.0-beta01")
     prebuilts(LibraryGroups.LOCALBROADCASTMANAGER, "1.1.0-alpha01")
-    prebuilts(LibraryGroups.MEDIA, "media", "1.1.0-alpha04")
+    prebuilts(LibraryGroups.MEDIA, "media", "1.1.0-beta01")
     // TODO: Rename media-widget to media2-widget after 1.0.0-alpha06
     prebuilts(LibraryGroups.MEDIA, "media-widget", "1.0.0-alpha06")
     ignore(LibraryGroups.MEDIA2.group, "media2-widget")
     ignore(LibraryGroups.MEDIA2.group, "media2-exoplayer")
-    // TODO: Reenable to use media2-{common,player,session} prebuilts (b/130839413)
-    ignore(LibraryGroups.MEDIA2.group, "media2-common")
-    ignore(LibraryGroups.MEDIA2.group, "media2-player")
-    ignore(LibraryGroups.MEDIA2.group, "media2-session")
-    prebuilts(LibraryGroups.MEDIA2, "1.0.0-alpha04")
-    prebuilts(LibraryGroups.MEDIAROUTER, "1.1.0-alpha03")
+    prebuilts(LibraryGroups.MEDIA2, "1.0.0-beta01")
+    prebuilts(LibraryGroups.MEDIAROUTER, "1.1.0-beta01")
     ignore(LibraryGroups.NAVIGATION.group, "navigation-testing")
-    prebuilts(LibraryGroups.NAVIGATION, "2.1.0-alpha02")
+    prebuilts(LibraryGroups.NAVIGATION, "2.1.0-alpha03")
     prebuilts(LibraryGroups.PAGING, "2.1.0")
     prebuilts(LibraryGroups.PALETTE, "1.0.0")
     prebuilts(LibraryGroups.PERCENTLAYOUT, "1.0.0")
     prebuilts(LibraryGroups.PERSISTENCE, "2.0.0")
-    prebuilts(LibraryGroups.PREFERENCE, "preference-ktx", "1.1.0-alpha04")
-    prebuilts(LibraryGroups.PREFERENCE, "1.1.0-alpha04")
+    prebuilts(LibraryGroups.PREFERENCE, "preference-ktx", "1.1.0-alpha05")
+    prebuilts(LibraryGroups.PREFERENCE, "1.1.0-alpha05")
     prebuilts(LibraryGroups.PRINT, "1.0.0")
     prebuilts(LibraryGroups.RECOMMENDATION, "1.0.0")
-    prebuilts(LibraryGroups.RECYCLERVIEW, "recyclerview", "1.1.0-alpha04")
-    prebuilts(LibraryGroups.RECYCLERVIEW, "recyclerview-selection", "1.1.0-alpha01")
-    prebuilts(LibraryGroups.REMOTECALLBACK, "1.0.0-alpha01")
+    prebuilts(LibraryGroups.RECYCLERVIEW, "recyclerview", "1.1.0-alpha05")
+    prebuilts(LibraryGroups.RECYCLERVIEW, "recyclerview-selection", "1.1.0-alpha05")
+    prebuilts(LibraryGroups.REMOTECALLBACK, "1.0.0-alpha02")
     ignore(LibraryGroups.ROOM.group, "room-common-java8")
-    prebuilts(LibraryGroups.ROOM, "2.1.0-alpha07")
-    prebuilts(LibraryGroups.SAVEDSTATE, "1.0.0-alpha02")
+    prebuilts(LibraryGroups.ROOM, "2.1.0-beta01")
+    prebuilts(LibraryGroups.SAVEDSTATE, "1.0.0-beta01")
+    prebuilts(LibraryGroups.SECURITY, "1.0.0-alpha01")
     prebuilts(LibraryGroups.SHARETARGET, "1.0.0-alpha01")
-    prebuilts(LibraryGroups.SLICE, "slice-builders", "1.0.0")
-    prebuilts(LibraryGroups.SLICE, "slice-builders-ktx", "1.0.0-alpha6")
-    prebuilts(LibraryGroups.SLICE, "slice-core", "1.0.0")
+    prebuilts(LibraryGroups.SLICE, "slice-builders", "1.1.0-alpha01")
+    prebuilts(LibraryGroups.SLICE, "slice-builders-ktx", "1.0.0-alpha07")
+    prebuilts(LibraryGroups.SLICE, "slice-core", "1.1.0-alpha01")
     // TODO: land prebuilts
 //    prebuilts(LibraryGroups.SLICE.group, "slice-test", "1.0.0")
     ignore(LibraryGroups.SLICE.group, "slice-test")
-    prebuilts(LibraryGroups.SLICE, "slice-view", "1.0.0")
+    prebuilts(LibraryGroups.SLICE, "slice-view", "1.1.0-alpha01")
     prebuilts(LibraryGroups.SLIDINGPANELAYOUT, "1.0.0")
     prebuilts(LibraryGroups.SWIPEREFRESHLAYOUT, "1.1.0-alpha01")
     prebuilts(LibraryGroups.TEXTCLASSIFIER, "1.0.0-alpha02")
-    prebuilts(LibraryGroups.TRANSITION, "1.1.0-beta01")
+    prebuilts(LibraryGroups.TRANSITION, "1.1.0-rc01")
     prebuilts(LibraryGroups.TVPROVIDER, "1.0.0")
-    prebuilts(LibraryGroups.VECTORDRAWABLE, "1.1.0-alpha01")
-    prebuilts(LibraryGroups.VECTORDRAWABLE, "vectordrawable-animated", "1.1.0-alpha01")
-    prebuilts(LibraryGroups.VERSIONEDPARCELABLE, "1.1.0-alpha02")
+    prebuilts(LibraryGroups.VECTORDRAWABLE, "1.1.0-beta01")
+    prebuilts(LibraryGroups.VECTORDRAWABLE, "vectordrawable-animated", "1.1.0-beta01")
+    prebuilts(LibraryGroups.VERSIONEDPARCELABLE, "1.1.0-beta01")
     prebuilts(LibraryGroups.VIEWPAGER, "1.0.0")
-    prebuilts(LibraryGroups.VIEWPAGER2, "1.0.0-alpha03")
+    prebuilts(LibraryGroups.VIEWPAGER2, "1.0.0-alpha04")
     prebuilts(LibraryGroups.WEAR, "1.0.0")
             .addStubs("wear/wear_stubs/com.google.android.wearable-stubs.jar")
-    prebuilts(LibraryGroups.WEBKIT, "1.0.0")
+    prebuilts(LibraryGroups.WEBKIT, "1.1.0-alpha01")
     ignore(LibraryGroups.WORKMANAGER.group, "work-gcm")
     prebuilts(LibraryGroups.WORKMANAGER, "2.1.0-alpha01")
     default(Ignore)
diff --git a/buildSrc/src/main/kotlin/androidx/build/Release.kt b/buildSrc/src/main/kotlin/androidx/build/Release.kt
index 46640be..a00c08f 100644
--- a/buildSrc/src/main/kotlin/androidx/build/Release.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/Release.kt
@@ -273,7 +273,7 @@
     /**
      * Creates and returns the task that includes all projects regardless of their release status.
      */
-    private fun getGlobalFullZipTask(project: Project): TaskProvider<GMavenZipTask> {
+    fun getGlobalFullZipTask(project: Project): TaskProvider<GMavenZipTask> {
         return project.rootProject.maybeRegister(
             name = FULL_ARCHIVE_TASK_NAME,
             onConfigure = {
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index 51d04f6..a60a099 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -17,7 +17,7 @@
 package androidx.build.dependencies
 
 const val ANDROIDX_TEST_CORE = "androidx.test:core:1.1.0"
-const val ANDROIDX_TEST_EXT_JUNIT = "androidx.test.ext:junit:1.0.0"
+const val ANDROIDX_TEST_EXT_JUNIT = "androidx.test.ext:junit:1.1.0"
 const val ANDROIDX_TEST_EXT_KTX = "androidx.test.ext:junit-ktx:1.1.0"
 const val ANDROIDX_TEST_RULES = "androidx.test:rules:1.1.0"
 const val ANDROIDX_TEST_RUNNER = "androidx.test:runner:1.1.1"
diff --git a/camera/camera2/src/androidTest/java/androidx/camera/camera2/ImageAnalysisTest.java b/camera/camera2/src/androidTest/java/androidx/camera/camera2/ImageAnalysisTest.java
index b408d9b..ad346eb 100644
--- a/camera/camera2/src/androidTest/java/androidx/camera/camera2/ImageAnalysisTest.java
+++ b/camera/camera2/src/androidTest/java/androidx/camera/camera2/ImageAnalysisTest.java
@@ -42,7 +42,7 @@
 import androidx.camera.testing.CameraUtil;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
+import androidx.test.filters.LargeTest;
 
 import org.junit.After;
 import org.junit.Before;
@@ -57,7 +57,7 @@
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
-@MediumTest
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public final class ImageAnalysisTest {
     // Use most supported resolution for different supported hardware level devices,
diff --git a/camera/camera2/src/androidTest/java/androidx/camera/camera2/ImageCaptureTest.java b/camera/camera2/src/androidTest/java/androidx/camera/camera2/ImageCaptureTest.java
index a7b81cc..819b3b6 100644
--- a/camera/camera2/src/androidTest/java/androidx/camera/camera2/ImageCaptureTest.java
+++ b/camera/camera2/src/androidTest/java/androidx/camera/camera2/ImageCaptureTest.java
@@ -480,7 +480,8 @@
         }
     }
 
-    @Test
+    // Skipping test due to b/132108192. Add back once test has been fixed.
+    // @Test
     public void camera2InteropCaptureSessionCallbacks() throws InterruptedException {
         ImageCaptureConfig.Builder configBuilder =
                 new ImageCaptureConfig.Builder().setCallbackHandler(mHandler);
@@ -506,6 +507,8 @@
                 requestCaptor.capture(),
                 any(TotalCaptureResult.class));
         CaptureRequest captureRequest = requestCaptor.getValue(); // Obtains the last value.
+        // TODO This method removed temporary due to the side effect of aosp/943904. It's needed
+        //  keep tracking.
         assertThat(captureRequest.get(CaptureRequest.CONTROL_CAPTURE_INTENT))
                 .isEqualTo(CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE);
     }
diff --git a/camera/camera2/src/androidTest/java/androidx/camera/camera2/UseCaseCombinationTest.java b/camera/camera2/src/androidTest/java/androidx/camera/camera2/UseCaseCombinationTest.java
index 25b1feb..d81d3cf 100644
--- a/camera/camera2/src/androidTest/java/androidx/camera/camera2/UseCaseCombinationTest.java
+++ b/camera/camera2/src/androidTest/java/androidx/camera/camera2/UseCaseCombinationTest.java
@@ -49,7 +49,7 @@
 import androidx.lifecycle.Observer;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
+import androidx.test.filters.LargeTest;
 import androidx.test.rule.GrantPermissionRule;
 
 import org.junit.After;
@@ -65,7 +65,7 @@
  * Contains tests for {@link androidx.camera.core.CameraX} which varies use case combinations to
  * run.
  */
-@MediumTest
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public final class UseCaseCombinationTest {
     private static final LensFacing DEFAULT_LENS_FACING = LensFacing.BACK;
diff --git a/camera/camera2/src/androidTest/java/androidx/camera/camera2/VideoCaptureTest.java b/camera/camera2/src/androidTest/java/androidx/camera/camera2/VideoCaptureTest.java
index bd27bd6..c15d2cf 100644
--- a/camera/camera2/src/androidTest/java/androidx/camera/camera2/VideoCaptureTest.java
+++ b/camera/camera2/src/androidTest/java/androidx/camera/camera2/VideoCaptureTest.java
@@ -37,7 +37,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.LargeTest;
 import androidx.test.rule.GrantPermissionRule;
 
 import org.junit.Before;
@@ -58,7 +58,7 @@
  * <p>TODO(b/112325215): The VideoCapture will be more thoroughly tested via integration
  * tests
  */
-@SmallTest
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public final class VideoCaptureTest {
     // Use most supported resolution for different supported hardware level devices,
diff --git a/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2CaptureCallbacksTest.java b/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2CaptureCallbacksTest.java
index 8912678..ec743bc 100644
--- a/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2CaptureCallbacksTest.java
+++ b/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2CaptureCallbacksTest.java
@@ -28,13 +28,13 @@
 import android.view.Surface;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.LargeTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
-@SmallTest
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public final class Camera2CaptureCallbacksTest {
 
diff --git a/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2ImplCameraRepositoryTest.java b/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2ImplCameraRepositoryTest.java
index 48fd51a..781755a 100644
--- a/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2ImplCameraRepositoryTest.java
+++ b/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2ImplCameraRepositoryTest.java
@@ -34,7 +34,7 @@
 import androidx.camera.testing.fakes.FakeUseCaseConfig;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.LargeTest;
 import androidx.test.rule.GrantPermissionRule;
 
 import org.junit.After;
@@ -49,7 +49,7 @@
  * Contains tests for {@link androidx.camera.core.CameraRepository} which require an actual
  * implementation to run.
  */
-@SmallTest
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public final class Camera2ImplCameraRepositoryTest {
     private CameraRepository mCameraRepository;
diff --git a/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2ImplCameraXTest.java b/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2ImplCameraXTest.java
index d43876a..76e748c 100644
--- a/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2ImplCameraXTest.java
+++ b/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2ImplCameraXTest.java
@@ -47,7 +47,7 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.FlakyTest;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.LargeTest;
 import androidx.test.rule.GrantPermissionRule;
 
 import org.junit.After;
@@ -64,7 +64,7 @@
  * run.
  */
 @FlakyTest
-@SmallTest
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public final class Camera2ImplCameraXTest {
     private static final LensFacing DEFAULT_LENS_FACING = LensFacing.BACK;
diff --git a/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2InitializerTest.java b/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2InitializerTest.java
index e3c65e7..15d8636 100644
--- a/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2InitializerTest.java
+++ b/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2InitializerTest.java
@@ -24,7 +24,7 @@
 import androidx.camera.testing.fakes.FakeActivity;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.LargeTest;
 import androidx.test.rule.ActivityTestRule;
 
 import org.junit.Before;
@@ -35,7 +35,7 @@
 /**
  * Unit tests for {@link Camera2Initializer}.
  */
-@SmallTest
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public final class Camera2InitializerTest {
 
diff --git a/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/CameraTest.java b/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/CameraTest.java
index cfd89ab..0e03783 100644
--- a/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/CameraTest.java
+++ b/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/CameraTest.java
@@ -40,7 +40,7 @@
 import androidx.camera.testing.fakes.FakeUseCaseConfig;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.LargeTest;
 
 import org.junit.After;
 import org.junit.Before;
@@ -53,7 +53,7 @@
 import java.util.HashMap;
 import java.util.Map;
 
-@SmallTest
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public final class CameraTest {
     private static final LensFacing DEFAULT_LENS_FACING = LensFacing.BACK;
diff --git a/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/CaptureCallbackContainerTest.java b/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/CaptureCallbackContainerTest.java
index 458addf..48966e9 100644
--- a/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/CaptureCallbackContainerTest.java
+++ b/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/CaptureCallbackContainerTest.java
@@ -21,13 +21,13 @@
 import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.LargeTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
-@SmallTest
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public final class CaptureCallbackContainerTest {
 
diff --git a/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/CaptureSessionTest.java b/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/CaptureSessionTest.java
index 6f5ca39..cf1c821 100644
--- a/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/CaptureSessionTest.java
+++ b/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/CaptureSessionTest.java
@@ -44,7 +44,7 @@
 import androidx.camera.core.SessionConfig;
 import androidx.camera.testing.CameraUtil;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.LargeTest;
 
 import org.junit.After;
 import org.junit.Before;
@@ -62,7 +62,7 @@
  * android.hardware.camera2.CameraDevice} can be opened since it is used to open a {@link
  * android.hardware.camera2.CaptureRequest}.
  */
-@SmallTest
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public final class CaptureSessionTest {
     private CaptureSessionTestParameters mTestParameters0;
diff --git a/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatDeviceTest.java b/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatDeviceTest.java
new file mode 100644
index 0000000..e544f71
--- /dev/null
+++ b/camera/camera2/src/androidTest/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatDeviceTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 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.camera.camera2.impl.compat.params;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.SurfaceTexture;
+import android.view.Surface;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests some of the methods of OutputConfigurationCompat on device.
+ *
+ * <p>These need to run on device since they rely on native implementation details of the
+ * {@link Surface} class.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class OutputConfigurationCompatDeviceTest {
+
+    private static final int DEFAULT_WIDTH = 1024;
+    private static final int DEFAULT_HEIGHT = 768;
+
+    private static final int SECONDARY_WIDTH = 640;
+    private static final int SECONDARY_HEIGHT = 480;
+
+    // Same surface
+    private OutputConfigurationCompat mOutputConfigCompat0;
+    private OutputConfigurationCompat mOutputConfigCompat1;
+
+    // Different surface, same SurfaceTexture
+    private OutputConfigurationCompat mOutputConfigCompat2;
+
+    // Different Surface and SurfaceTexture
+    private OutputConfigurationCompat mOutputConfigCompat3;
+
+    @Before
+    public void setUp() {
+        SurfaceTexture surfaceTexture0 = new SurfaceTexture(0);
+        surfaceTexture0.setDefaultBufferSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
+        Surface surface0 = new Surface(surfaceTexture0);
+        Surface surface1 = new Surface(surfaceTexture0);
+
+        SurfaceTexture surfaceTexture1 = new SurfaceTexture(0);
+        surfaceTexture0.setDefaultBufferSize(SECONDARY_WIDTH, SECONDARY_HEIGHT);
+        Surface surface2 = new Surface(surfaceTexture1);
+
+        mOutputConfigCompat0 = new OutputConfigurationCompat(surface0);
+        mOutputConfigCompat1 = new OutputConfigurationCompat(surface0);
+        mOutputConfigCompat2 = new OutputConfigurationCompat(surface1);
+        mOutputConfigCompat3 = new OutputConfigurationCompat(surface2);
+    }
+
+    @Test
+    public void hashCode_producesExpectedResults() {
+        assertThat(mOutputConfigCompat0.hashCode()).isEqualTo(mOutputConfigCompat1.hashCode());
+        assertThat(mOutputConfigCompat0.hashCode()).isNotEqualTo(mOutputConfigCompat2.hashCode());
+        assertThat(mOutputConfigCompat0.hashCode()).isNotEqualTo(mOutputConfigCompat3.hashCode());
+        assertThat(mOutputConfigCompat2.hashCode()).isNotEqualTo(mOutputConfigCompat3.hashCode());
+    }
+
+    @Test
+    public void equals_producesExpectedResults() {
+        assertThat(mOutputConfigCompat0).isEqualTo(mOutputConfigCompat1);
+        assertThat(mOutputConfigCompat0).isNotEqualTo(mOutputConfigCompat2);
+        assertThat(mOutputConfigCompat0).isNotEqualTo(mOutputConfigCompat3);
+        assertThat(mOutputConfigCompat2).isNotEqualTo(mOutputConfigCompat3);
+    }
+}
diff --git a/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/CameraDeviceCompat.java b/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/CameraDeviceCompat.java
new file mode 100644
index 0000000..d6bc064
--- /dev/null
+++ b/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/CameraDeviceCompat.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 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.camera.camera2.impl.compat;
+
+import android.annotation.TargetApi;
+import android.hardware.camera2.CameraDevice;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+/**
+ * Helper for accessing features in {@link CameraDevice} in a backwards compatible fashion.
+ *
+ * @hide Will be unhidden once some methods are implemented
+ */
+@RestrictTo(Scope.LIBRARY)
+@TargetApi(21)
+public final class CameraDeviceCompat {
+
+    /**
+     * Standard camera operation mode.
+     *
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY)
+    public static final int SESSION_OPERATION_MODE_NORMAL =
+            0; // ICameraDeviceUser.NORMAL_MODE;
+
+    /**
+     * Constrained high-speed operation mode.
+     *
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY)
+    public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED =
+            1; // ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE;
+}
diff --git a/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/package-info.java b/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/package-info.java
new file mode 100644
index 0000000..f6409d6
--- /dev/null
+++ b/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 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.
+ */
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+package androidx.camera.camera2.impl.compat;
+
+import androidx.annotation.RestrictTo;
diff --git a/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompat.java b/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompat.java
new file mode 100644
index 0000000..a76687d
--- /dev/null
+++ b/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompat.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright 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.camera.camera2.impl.compat.params;
+
+import android.annotation.TargetApi;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.os.Build;
+import android.util.Size;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+import java.util.List;
+
+/**
+ * Helper for accessing features in OutputConfiguration in a backwards compatible fashion.
+ */
+@TargetApi(21)
+public final class OutputConfigurationCompat {
+
+    /**
+     * Invalid surface group ID.
+     *
+     * <p>An OutputConfiguration with this value indicates that the included surface
+     * doesn't belong to any surface group.</p>
+     */
+    public static final int SURFACE_GROUP_ID_NONE = -1;
+
+    private final OutputConfigurationCompatImpl mImpl;
+
+    public OutputConfigurationCompat(@NonNull Surface surface) {
+        if (Build.VERSION.SDK_INT >= 28) {
+            mImpl = new OutputConfigurationCompatApi28Impl(surface);
+        } else if (Build.VERSION.SDK_INT >= 26) {
+            mImpl = new OutputConfigurationCompatApi26Impl(surface);
+        } else if (Build.VERSION.SDK_INT >= 24) {
+            mImpl = new OutputConfigurationCompatApi24Impl(surface);
+        } else {
+            mImpl = new OutputConfigurationCompatBaseImpl(surface);
+        }
+    }
+
+
+    /**
+     * Create a new {@link OutputConfiguration} instance, with desired Surface size and Surface
+     * source class.
+     *
+     * <p>
+     * This constructor takes an argument for desired Surface size and the Surface source class
+     * without providing the actual output Surface. This is used to setup an output configuration
+     * with a deferred Surface. The application can use this output configuration to create a
+     * session.
+     * </p>
+     * <p>
+     * However, the actual output Surface must be set via {@link #addSurface} and the deferred
+     * Surface configuration must be finalized via {@link
+     * CameraCaptureSession#finalizeOutputConfigurations} before submitting a request with this
+     * Surface target. The deferred Surface can only be obtained either from {@link
+     * android.view.SurfaceView} by calling {@link android.view.SurfaceHolder#getSurface}, or from
+     * {@link android.graphics.SurfaceTexture} via
+     * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}).
+     * </p>
+     *
+     * @param surfaceSize Size for the deferred surface.
+     * @param klass       a non-{@code null} {@link Class} object reference that indicates the
+     *                    source of
+     *                    this surface. Only {@link android.view.SurfaceHolder SurfaceHolder
+     *                    .class} and
+     *                    {@link android.graphics.SurfaceTexture SurfaceTexture.class} are
+     *                    supported.
+     * @throws IllegalArgumentException if the Surface source class is not supported, or Surface
+     *                                  size is zero.
+     */
+    @RequiresApi(26)
+    public <T> OutputConfigurationCompat(@NonNull Size surfaceSize, @NonNull Class<T> klass) {
+        OutputConfiguration deferredConfig = new OutputConfiguration(surfaceSize, klass);
+        if (Build.VERSION.SDK_INT >= 28) {
+            mImpl = new OutputConfigurationCompatApi28Impl(deferredConfig);
+        } else {
+            mImpl = new OutputConfigurationCompatApi26Impl(deferredConfig);
+        }
+    }
+
+    private OutputConfigurationCompat(@NonNull OutputConfigurationCompatImpl impl) {
+        mImpl = impl;
+    }
+
+    /**
+     * Creates an instance from a framework android.hardware.camera2.params.OutputConfiguration
+     * object.
+     *
+     * <p>This method always returns {@code null} on API &lt;= 23.</p>
+     *
+     * @param outputConfiguration an android.hardware.camera2.params.OutputConfiguration object, or
+     *                            {@code null} if none.
+     * @return an equivalent {@link OutputConfigurationCompat} object, or {@code null} if not
+     * supported.
+     */
+    @Nullable
+    public static OutputConfigurationCompat wrap(@Nullable Object outputConfiguration) {
+        if (outputConfiguration == null) {
+            return null;
+        }
+
+        OutputConfigurationCompatImpl outputConfigurationCompatImpl = null;
+        if (Build.VERSION.SDK_INT >= 28) {
+            outputConfigurationCompatImpl = new OutputConfigurationCompatApi28Impl(
+                    outputConfiguration);
+        } else if (Build.VERSION.SDK_INT >= 26) {
+            outputConfigurationCompatImpl = new OutputConfigurationCompatApi26Impl(
+                    outputConfiguration);
+        } else if (Build.VERSION.SDK_INT >= 24) {
+            outputConfigurationCompatImpl = new OutputConfigurationCompatApi24Impl(
+                    outputConfiguration);
+        }
+
+        if (outputConfigurationCompatImpl == null) {
+            return null;
+        }
+
+        return new OutputConfigurationCompat(outputConfigurationCompatImpl);
+    }
+
+    /**
+     * Enable multiple surfaces sharing the same OutputConfiguration.
+     *
+     * <p>For advanced use cases, a camera application may require more streams than the combination
+     * guaranteed by {@code CameraDevice.createCaptureSession}. In this case, more than one
+     * compatible surface can be attached to an OutputConfiguration so that they map to one
+     * camera stream, and the outputs share memory buffers when possible. Due to buffer sharing
+     * clients should be careful when adding surface outputs that modify their input data. If such
+     * case exists, camera clients should have an additional mechanism to synchronize read and write
+     * access between individual consumers.</p>
+     *
+     * <p>Two surfaces are compatible in the below cases:</p>
+     *
+     * <li> Surfaces with the same size, format, dataSpace, and Surface source class. In this case,
+     * {@code CameraDevice.createCaptureSessionByOutputConfigurations} is guaranteed to succeed.
+     *
+     * <li> Surfaces with the same size, format, and dataSpace, but different Surface source classes
+     * that are generally not compatible. However, on some devices, the underlying camera device is
+     * able to use the same buffer layout for both surfaces. The only way to discover if this is the
+     * case is to create a capture session with that output configuration. For example, if the
+     * camera device uses the same private buffer format between a SurfaceView/SurfaceTexture and a
+     * MediaRecorder/MediaCodec, {@code CameraDevice.createCaptureSessionByOutputConfigurations}
+     * will succeed. Otherwise, it fails with {@link
+     * CameraCaptureSession.StateCallback#onConfigureFailed}.
+     * </ol>
+     *
+     * <p>To enable surface sharing, this function must be called before {@code
+     * CameraDevice.createCaptureSessionByOutputConfigurations} or {@code
+     * CameraDevice.createReprocessableCaptureSessionByConfigurations}. Calling this function after
+     * {@code CameraDevice.createCaptureSessionByOutputConfigurations} has no effect.</p>
+     *
+     * <p>Up to {@link #getMaxSharedSurfaceCount} surfaces can be shared for an OutputConfiguration.
+     * The supported surfaces for sharing must be of type SurfaceTexture, SurfaceView,
+     * MediaRecorder, MediaCodec, or implementation defined ImageReader.</p>
+     */
+    public void enableSurfaceSharing() {
+        mImpl.enableSurfaceSharing();
+    }
+
+    /**
+     * Set the id of the physical camera for this OutputConfiguration.
+     *
+     * <p>This method is a no-op on API &lt;= 27, as these APIs do not support logical cameras.
+     *
+     * <p>In the case one logical camera is made up of multiple physical cameras, it could be
+     * desirable for the camera application to request streams from individual physical cameras.
+     * This call achieves it by mapping the OutputConfiguration to the physical camera id.</p>
+     *
+     * <p>The valid physical camera ids can be queried by {@code
+     * CameraCharacteristics.getPhysicalCameraIds} on API &gt;= 28.
+     * </p>
+     *
+     * <p>Passing in a null physicalCameraId means that the OutputConfiguration is for a logical
+     * stream.</p>
+     *
+     * <p>This function must be called before {@code
+     * CameraDevice.createCaptureSessionByOutputConfigurations} or {@code
+     * CameraDevice.createReprocessableCaptureSessionByConfigurations}. Calling this function
+     * after {@code CameraDevice.createCaptureSessionByOutputConfigurations} or {@code
+     * CameraDevice.createReprocessableCaptureSessionByConfigurations} has no effect.</p>
+     *
+     * <p>The surface belonging to a physical camera OutputConfiguration must not be used as input
+     * or output of a reprocessing request. </p>
+     */
+    public void setPhysicalCameraId(@Nullable String physicalCameraId) {
+        mImpl.setPhysicalCameraId(physicalCameraId);
+    }
+
+    /**
+     * Add a surface to this OutputConfiguration.
+     *
+     * <p> This method will always throw on API &lt;= 25, as these API levels do not support surface
+     * sharing. Users should always check {@link #getMaxSharedSurfaceCount} before attempting to
+     * add a surface.
+     *
+     * <p> This function can be called before or after {@code
+     * CameraDevice#createCaptureSessionByOutputConfigurations}. If it's called after,
+     * the application must finalize the capture session with
+     * {@code CameraCaptureSession.finalizeOutputConfigurations}. It is possible to call this method
+     * after the output configurations have been finalized only in cases of enabled surface sharing
+     * see {@link #enableSurfaceSharing}. The modified output configuration must be updated with
+     * {@code CameraCaptureSession.updateOutputConfiguration}.</p>
+     *
+     * <p> If the OutputConfiguration was constructed with a deferred surface by {@link
+     * OutputConfigurationCompat#OutputConfigurationCompat(Size, Class)}, the added surface must
+     * be obtained
+     * from {@link android.view.SurfaceView} by calling
+     * {@link android.view.SurfaceHolder#getSurface},
+     * or from {@link android.graphics.SurfaceTexture} via
+     * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}).</p>
+     *
+     * <p> If the OutputConfiguration was constructed by other constructors, the added
+     * surface must be compatible with the existing surface. See {@link #enableSurfaceSharing} for
+     * details of compatible surfaces.</p>
+     *
+     * <p> If the OutputConfiguration already contains a Surface, {@link #enableSurfaceSharing} must
+     * be called before calling this function to add a new Surface.</p>
+     *
+     * @param surface The surface to be added.
+     * @throws IllegalArgumentException if the Surface is invalid, the Surface's
+     *                                  dataspace/format doesn't match, or adding the Surface
+     *                                  would exceed number of
+     *                                  shared surfaces supported.
+     * @throws IllegalStateException    if the Surface was already added to this
+     * OutputConfiguration,
+     *                                  or if the OutputConfiguration is not shared and it
+     *                                  already has a surface associated
+     *                                  with it.
+     */
+    public void addSurface(@NonNull Surface surface) {
+        mImpl.addSurface(surface);
+    }
+
+    /**
+     * Remove a surface from this OutputConfiguration.
+     *
+     * <p> Surfaces added via calls to {@link #addSurface} can also be removed from the
+     * OutputConfiguration. The only notable exception is the surface associated with
+     * the OutputConfigration see {@link #getSurface} which was passed as part of the constructor
+     * or was added first in the deferred case
+     * {@link OutputConfigurationCompat#OutputConfigurationCompat(Size, Class)}.</p>
+     *
+     * @param surface The surface to be removed.
+     * @throws IllegalArgumentException If the surface is associated with this OutputConfiguration
+     *                                  (see {@link #getSurface}) or the surface didn't get added
+     *                                  with {@link #addSurface}.
+     */
+    public void removeSurface(@NonNull Surface surface) {
+        mImpl.removeSurface(surface);
+    }
+
+    /**
+     * Get the maximum supported shared {@link Surface} count.
+     *
+     * @return the maximum number of surfaces that can be added per each OutputConfiguration.
+     * @see #enableSurfaceSharing
+     */
+    public int getMaxSharedSurfaceCount() {
+        return mImpl.getMaxSharedSurfaceCount();
+    }
+
+    /**
+     * Get the {@link Surface} associated with this {@link OutputConfigurationCompat}.
+     *
+     * If more than one surface is associated with this {@link OutputConfigurationCompat}, return
+     * the
+     * first one as specified in the constructor or {@link OutputConfigurationCompat#addSurface}.
+     */
+    @Nullable
+    public Surface getSurface() {
+        return mImpl.getSurface();
+    }
+
+    /**
+     * Get the immutable list of surfaces associated with this {@link OutputConfigurationCompat}.
+     *
+     * @return the list of surfaces associated with this {@link OutputConfigurationCompat} as
+     * specified in
+     * the constructor and {@link OutputConfigurationCompat#addSurface}. The list should not be
+     * modified.
+     */
+    @NonNull
+    public List<Surface> getSurfaces() {
+        return mImpl.getSurfaces();
+    }
+
+    /**
+     * Get the surface group ID associated with this {@link OutputConfigurationCompat}.
+     *
+     * @return the surface group ID associated with this {@link OutputConfigurationCompat}.
+     * The default value is {@value #SURFACE_GROUP_ID_NONE}.
+     */
+    public int getSurfaceGroupId() {
+        return mImpl.getSurfaceGroupId();
+    }
+
+    /**
+     * Check if this {@link OutputConfigurationCompat} is equal to another
+     * {@link OutputConfigurationCompat}.
+     *
+     * <p>Two output configurations are only equal if and only if the underlying surfaces, surface
+     * properties (width, height, format, dataspace) when the output configurations are created,
+     * and all other configuration parameters are equal. </p>
+     *
+     * @return {@code true} if the objects were equal, {@code false} otherwise
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof OutputConfigurationCompat)) {
+            return false;
+        }
+
+        return mImpl.equals(((OutputConfigurationCompat) obj).mImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        return mImpl.hashCode();
+    }
+
+    /**
+     * Gets the underlying framework android.hardware.camera2.params.OutputConfiguration object.
+     *
+     * <p>This method always returns {@code null} on API &lt;= 23.</p>
+     *
+     * @return an equivalent android.hardware.camera2.params.OutputConfiguration object, or {@code
+     * null} if not supported.
+     */
+    @Nullable
+    public Object unwrap() {
+        return mImpl.getOutputConfiguration();
+    }
+
+    interface OutputConfigurationCompatImpl {
+        void enableSurfaceSharing();
+
+        void setPhysicalCameraId(@Nullable String physicalCameraId);
+
+        void addSurface(@NonNull Surface surface);
+
+        void removeSurface(@NonNull Surface surface);
+
+        int getMaxSharedSurfaceCount();
+
+        @Nullable
+        Surface getSurface();
+
+        List<Surface> getSurfaces();
+
+        int getSurfaceGroupId();
+
+        @Nullable
+        Object getOutputConfiguration();
+    }
+}
diff --git a/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatApi24Impl.java b/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatApi24Impl.java
new file mode 100644
index 0000000..550ae75
--- /dev/null
+++ b/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatApi24Impl.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 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.camera.camera2.impl.compat.params;
+
+import android.hardware.camera2.params.OutputConfiguration;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.core.util.Preconditions;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Implementation of the OutputConfiguration compat methods for API 24 and above.
+ */
+@RequiresApi(24)
+class OutputConfigurationCompatApi24Impl extends OutputConfigurationCompatBaseImpl{
+
+    OutputConfigurationCompatApi24Impl(@NonNull Surface surface) {
+        super(new OutputConfiguration(surface));
+    }
+
+    OutputConfigurationCompatApi24Impl(@NonNull Object outputConfiguration) {
+        super(outputConfiguration);
+    }
+
+    @Override
+    @Nullable
+    public Surface getSurface() {
+        OutputConfiguration outputConfig = (OutputConfiguration) mObject;
+        return outputConfig.getSurface();
+    }
+
+    @Override
+    @NonNull
+    public List<Surface> getSurfaces() {
+        return Collections.singletonList(getSurface());
+    }
+
+    @Override
+    public int getSurfaceGroupId() {
+        OutputConfiguration outputConfig = (OutputConfiguration) mObject;
+        return outputConfig.getSurfaceGroupId();
+    }
+
+    @Nullable
+    @Override
+    public Object getOutputConfiguration() {
+        Preconditions.checkArgument(mObject instanceof OutputConfiguration);
+        return mObject;
+    }
+}
+
diff --git a/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatApi26Impl.java b/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatApi26Impl.java
new file mode 100644
index 0000000..0e13a83
--- /dev/null
+++ b/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatApi26Impl.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 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.camera.camera2.impl.compat.params;
+
+import android.hardware.camera2.params.OutputConfiguration;
+import android.util.Log;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+/**
+ * Implementation of the OutputConfiguration compat methods for API 26 and above.
+ */
+@RequiresApi(26)
+class OutputConfigurationCompatApi26Impl extends OutputConfigurationCompatApi24Impl {
+
+    private static final String MAX_SHARED_SURFACES_COUNT_FIELD = "MAX_SURFACES_COUNT";
+    private static final String SURFACES_FIELD = "mSurfaces";
+
+    OutputConfigurationCompatApi26Impl(@NonNull Surface surface) {
+        super(new OutputConfiguration(surface));
+    }
+
+    OutputConfigurationCompatApi26Impl(@NonNull Object outputConfiguration) {
+        super(outputConfiguration);
+    }
+
+    // The following methods use reflection to call into the framework code, These methods are
+    // only between API 26 and API 28, and are not guaranteed to work on API levels greater than 27.
+    //=========================================================================================
+
+    private static int getMaxSharedSurfaceCountApi26()
+            throws NoSuchFieldException, IllegalAccessException {
+        Field maxSurfacesCountField = OutputConfiguration.class.getDeclaredField(
+                MAX_SHARED_SURFACES_COUNT_FIELD);
+        maxSurfacesCountField.setAccessible(true);
+        return maxSurfacesCountField.getInt(null);
+    }
+
+    private static List<Surface> getMutableSurfaceListApi26(OutputConfiguration outputConfiguration)
+            throws NoSuchFieldException, IllegalAccessException {
+        Field surfacesField = OutputConfiguration.class.getDeclaredField(SURFACES_FIELD);
+        surfacesField.setAccessible(true);
+        return (List<Surface>) surfacesField.get(outputConfiguration);
+    }
+
+    //=========================================================================================
+
+    /**
+     * Enable multiple surfaces sharing the same OutputConfiguration
+     */
+    @Override
+    public void enableSurfaceSharing() {
+        OutputConfiguration outputConfig = (OutputConfiguration) mObject;
+        outputConfig.enableSurfaceSharing();
+    }
+
+    /**
+     * Add a surface to this OutputConfiguration.
+     */
+    @Override
+    public void addSurface(@NonNull Surface surface) {
+        OutputConfiguration outputConfig = (OutputConfiguration) mObject;
+        outputConfig.addSurface(surface);
+    }
+
+    /**
+     * Remove a surface from this OutputConfiguration.
+     */
+    @Override
+    public void removeSurface(@NonNull Surface surface) {
+        if (getSurface() == surface) {
+            throw new IllegalArgumentException(
+                    "Cannot remove surface associated with this output configuration");
+        }
+
+        try {
+            List<Surface> surfaces = getMutableSurfaceListApi26((OutputConfiguration) mObject);
+            if (!surfaces.remove(surface)) {
+                throw new IllegalArgumentException(
+                        "Surface is not part of this output configuration");
+            }
+        } catch (IllegalAccessException | NoSuchFieldException e) {
+            Log.e(TAG, "Unable to remove surface from this output configuration.", e);
+        }
+
+    }
+
+    /**
+     * Get the maximum supported shared {@link Surface} count.
+     */
+    @Override
+    public int getMaxSharedSurfaceCount() {
+        try {
+            return getMaxSharedSurfaceCountApi26();
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            Log.e(TAG, "Unable to retrieve max shared surface count.", e);
+            return super.getMaxSharedSurfaceCount();
+        }
+    }
+
+    /**
+     * Get the immutable list of surfaces associated with this {@link OutputConfigurationCompat}.
+     */
+    @Override
+    @NonNull
+    public List<Surface> getSurfaces() {
+        OutputConfiguration outputConfig = (OutputConfiguration) mObject;
+        return outputConfig.getSurfaces();
+    }
+}
+
diff --git a/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatApi28Impl.java b/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatApi28Impl.java
new file mode 100644
index 0000000..f83ebb7
--- /dev/null
+++ b/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatApi28Impl.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 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.camera.camera2.impl.compat.params;
+
+import android.hardware.camera2.params.OutputConfiguration;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+/**
+ * Implementation of the OutputConfiguration compat methods for API 28 and above.
+ */
+@RequiresApi(28)
+class OutputConfigurationCompatApi28Impl extends OutputConfigurationCompatApi26Impl {
+
+    OutputConfigurationCompatApi28Impl(@NonNull Surface surface) {
+        super(new OutputConfiguration(surface));
+    }
+
+    OutputConfigurationCompatApi28Impl(@NonNull Object outputConfiguration) {
+        super(outputConfiguration);
+    }
+
+    /**
+     * Remove a surface from this OutputConfiguration.
+     */
+    @Override
+    public void removeSurface(@NonNull Surface surface) {
+        OutputConfiguration outputConfig = (OutputConfiguration) mObject;
+        outputConfig.removeSurface(surface);
+    }
+
+    /**
+     * Get the maximum supported shared {@link Surface} count.
+     */
+    @Override
+    public int getMaxSharedSurfaceCount() {
+        OutputConfiguration outputConfig = (OutputConfiguration) mObject;
+        return outputConfig.getMaxSharedSurfaceCount();
+    }
+
+    /**
+     * Set the id of the physical camera for this OutputConfiguration.
+     */
+    @Override
+    public void setPhysicalCameraId(@Nullable String physicalCameraId) {
+        OutputConfiguration outputConfiguration = (OutputConfiguration) mObject;
+        outputConfiguration.setPhysicalCameraId(physicalCameraId);
+    }
+}
+
diff --git a/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatBaseImpl.java b/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatBaseImpl.java
new file mode 100644
index 0000000..79c9d99
--- /dev/null
+++ b/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatBaseImpl.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright 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.camera.camera2.impl.compat.params;
+
+import android.graphics.ImageFormat;
+import android.util.Log;
+import android.util.Size;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.core.util.Preconditions;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Implementation of the OutputConfiguration compat methods for API 21 and above.
+ */
+@RequiresApi(21) // Needed for LegacyCameraDevice reflection
+class OutputConfigurationCompatBaseImpl implements
+        OutputConfigurationCompat.OutputConfigurationCompatImpl {
+    static final String TAG = "OutputConfigCompat";
+
+    final Object mObject;
+
+    OutputConfigurationCompatBaseImpl(@NonNull Surface surface) {
+        mObject = new OutputConfigurationParamsApi21(surface);
+    }
+
+    /**
+     * Sets the underlying implementation object.
+     */
+    OutputConfigurationCompatBaseImpl(@NonNull Object outputConfiguration) {
+        mObject = outputConfiguration;
+    }
+
+    /**
+     * Enable multiple surfaces sharing the same OutputConfiguration
+     *
+     * <p>This is always a no-op on API &lt;= 25.
+     */
+    @Override
+    public void enableSurfaceSharing() {
+        // No-op. Surface sharing not possible on less than API 26.
+        Log.w(TAG, "enableSurfaceSharing: surface sharing not supported on current API level");
+    }
+
+    /**
+     * Set the id of the physical camera for this OutputConfiguration.
+     *
+     * <p>This value is unused by this implementation. Added in API 28.
+     */
+    @Override
+    public void setPhysicalCameraId(@Nullable String physicalCameraId) {
+        // No-op. Physical camera ID is not supported on API < 28.
+        Log.w(TAG, "setPhysicalCameraId: physical camera id not supported on current API level");
+    }
+
+    /**
+     * Add a surface to this OutputConfiguration.
+     *
+     * <p>Since surface sharing is not supported in on API &lt;= 25, this will always throw.
+     */
+    @Override
+    public void addSurface(@NonNull Surface surface) {
+        Preconditions.checkNotNull(surface, "Surface must not be null");
+        if (getSurface() == surface) {
+            throw new IllegalStateException("Surface is already added!");
+        }
+
+        // Surface sharing not possible on API < 26
+        throw new IllegalStateException("Cannot have 2 surfaces for a non-sharing configuration");
+    }
+
+    /**
+     * Remove a surface from this OutputConfiguration.
+     *
+     * <p>removeSurface is not supported in on API &lt;= 25, this will always throw.
+     */
+    @Override
+    public void removeSurface(@NonNull Surface surface) {
+        if (getSurface() == surface) {
+            throw new IllegalArgumentException(
+                    "Cannot remove surface associated with this output configuration");
+        }
+
+        // Only a single surface is allowed in this implementation.
+        throw new IllegalArgumentException("Surface is not part of this output configuration");
+    }
+
+    /**
+     * Get the maximum supported shared {@link Surface} count.
+     *
+     * <p>Since surface sharing is not supported in on API &lt;= 25, always returns 1.
+     */
+    @Override
+    public int getMaxSharedSurfaceCount() {
+        return OutputConfigurationParamsApi21.MAX_SURFACES_COUNT;
+    }
+
+    /**
+     * Get the {@link Surface} associated with this {@link OutputConfigurationCompat}.
+     */
+    @Override
+    @Nullable
+    public Surface getSurface() {
+        List<Surface> surfaces = ((OutputConfigurationParamsApi21) mObject).mSurfaces;
+        if (surfaces.size() == 0) {
+            return null;
+        }
+
+        return surfaces.get(0);
+    }
+
+    /**
+     * Get the immutable list of surfaces associated with this {@link OutputConfigurationCompat}.
+     */
+    @Override
+    @NonNull
+    public List<Surface> getSurfaces() {
+        // mSurfaces is a singleton list, so return it directly.
+        return ((OutputConfigurationParamsApi21) mObject).mSurfaces;
+    }
+
+    @Override
+    public int getSurfaceGroupId() {
+        // Surface groups not supported on < API 24
+        return OutputConfigurationCompat.SURFACE_GROUP_ID_NONE;
+    }
+
+    @Nullable
+    @Override
+    public Object getOutputConfiguration() {
+        return null;
+    }
+
+    /**
+     * Check if this {@link OutputConfigurationCompatBaseImpl} is equal to another
+     * {@link OutputConfigurationCompatBaseImpl}.
+     *
+     * <p>Two output configurations are only equal if and only if the underlying surfaces, surface
+     * properties (width, height, format) when the output configurations are created,
+     * and all other configuration parameters are equal. </p>
+     *
+     * @return {@code true} if the objects were equal, {@code false} otherwise
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof OutputConfigurationCompatBaseImpl)) {
+            return false;
+        }
+
+        return Objects.equals(mObject, ((OutputConfigurationCompatBaseImpl) obj).mObject);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        return mObject.hashCode();
+    }
+
+    private static final class OutputConfigurationParamsApi21 {
+        /**
+         * Maximum number of surfaces supported by one {@link OutputConfigurationCompat}.
+         *
+         * <p>Always only 1 on API &lt;= 25.
+         */
+        static final int MAX_SURFACES_COUNT = 1;
+        private static final String LEGACY_CAMERA_DEVICE_CLASS =
+                "android.hardware.camera2.legacy.LegacyCameraDevice";
+        private static final String GET_SURFACE_SIZE_METHOD = "getSurfaceSize";
+        private static final String DETECT_SURFACE_TYPE_METHOD = "detectSurfaceType";
+        // Used on class Surface
+        private static final String GET_GENERATION_ID_METHOD = "getGenerationId";
+        final List<Surface> mSurfaces;
+        // The size and format of the surface when OutputConfiguration is created.
+        final Size mConfiguredSize;
+        final int mConfiguredFormat;
+        // Surface generation ID to distinguish changes to Surface native internals
+        final int mConfiguredGenerationId;
+
+        OutputConfigurationParamsApi21(@NonNull Surface surface) {
+            Preconditions.checkNotNull(surface, "Surface must not be null");
+            mSurfaces = Collections.singletonList(surface);
+            mConfiguredSize = getSurfaceSize(surface);
+            mConfiguredFormat = getSurfaceFormat(surface);
+            mConfiguredGenerationId = getSurfaceGenerationId(surface);
+        }
+
+        // The following methods use reflection to call into the framework code, These methods are
+        // only valid up to API 24, and are not guaranteed to work on API levels greater than 23.
+        //=========================================================================================
+
+        private static Size getSurfaceSize(@NonNull Surface surface) {
+            try {
+                Class<?> legacyCameraDeviceClass = Class.forName(LEGACY_CAMERA_DEVICE_CLASS);
+                Method getSurfaceSize = legacyCameraDeviceClass.getDeclaredMethod(
+                        GET_SURFACE_SIZE_METHOD, Surface.class);
+                getSurfaceSize.setAccessible(true);
+                return (Size) getSurfaceSize.invoke(null, surface);
+            } catch (ClassNotFoundException
+                    | NoSuchMethodException
+                    | IllegalAccessException
+                    | InvocationTargetException e) {
+                Log.e(TAG, "Unable to retrieve surface size.", e);
+                return null;
+            }
+        }
+
+        private static int getSurfaceFormat(@NonNull Surface surface) {
+            try {
+                Class<?> legacyCameraDeviceClass = Class.forName(LEGACY_CAMERA_DEVICE_CLASS);
+                Method detectSurfaceType = legacyCameraDeviceClass.getDeclaredMethod(
+                        DETECT_SURFACE_TYPE_METHOD, Surface.class);
+                return (int) detectSurfaceType.invoke(null, surface);
+            } catch (ClassNotFoundException
+                    | NoSuchMethodException
+                    | IllegalAccessException
+                    | InvocationTargetException e) {
+                Log.e(TAG, "Unable to retrieve surface format.", e);
+                return ImageFormat.UNKNOWN;
+            }
+
+
+        }
+
+        private static int getSurfaceGenerationId(@NonNull Surface surface) {
+            try {
+                Method getGenerationId = Surface.class.getDeclaredMethod(GET_GENERATION_ID_METHOD);
+                return (int) getGenerationId.invoke(surface);
+            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+                Log.e(TAG, "Unable to retrieve surface generation id.", e);
+                return -1;
+            }
+        }
+
+        //=========================================================================================
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof OutputConfigurationParamsApi21)) {
+                return false;
+            }
+
+            OutputConfigurationParamsApi21 otherOutputConfig = (OutputConfigurationParamsApi21) obj;
+
+            if (!mConfiguredSize.equals(otherOutputConfig.mConfiguredSize)
+                    || mConfiguredFormat != otherOutputConfig.mConfiguredFormat
+                    || mConfiguredGenerationId != otherOutputConfig.mConfiguredGenerationId) {
+                return false;
+            }
+
+            int minLen = Math.min(mSurfaces.size(), otherOutputConfig.mSurfaces.size());
+            for (int i = 0; i < minLen; i++) {
+                if (mSurfaces.get(i) != otherOutputConfig.mSurfaces.get(i)) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int h = 1;
+            // Strength reduction; in case the compiler has illusions about divisions being faster
+            h = ((h << 5) - h) ^ mSurfaces.hashCode(); // (h * 31) XOR mSurfaces.hashCode()
+            h = ((h << 5) - h) ^ mConfiguredGenerationId; // (h * 31) XOR mConfiguredGenerationId
+            h = ((h << 5) - h)
+                    ^ mConfiguredSize.hashCode(); // (h * 31) XOR mConfiguredSize.hashCode()
+            h = ((h << 5) - h) ^ mConfiguredFormat; // (h * 31) XOR mConfiguredFormat
+
+            return h;
+        }
+    }
+}
diff --git a/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/SessionConfigurationCompat.java b/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/SessionConfigurationCompat.java
new file mode 100644
index 0000000..7afc02a
--- /dev/null
+++ b/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/SessionConfigurationCompat.java
@@ -0,0 +1,480 @@
+/*
+ * Copyright 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.camera.camera2.impl.compat.params;
+
+import android.annotation.TargetApi;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.InputConfiguration;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
+import android.os.Build;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.camera.camera2.impl.compat.CameraDeviceCompat;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Helper for accessing features in SessionConfiguration in a backwards compatible fashion.
+ */
+@TargetApi(21)
+public final class SessionConfigurationCompat {
+
+    /**
+     * A regular session type containing instances of {@link OutputConfigurationCompat} running
+     * at regular non high speed FPS ranges and optionally {@link InputConfigurationCompat} for
+     * reprocessable sessions.
+     *
+     * @see CameraDevice#createCaptureSession
+     * @see CameraDevice#createReprocessableCaptureSession
+     */
+    public static final int SESSION_REGULAR = CameraDeviceCompat.SESSION_OPERATION_MODE_NORMAL;
+    /**
+     * A high speed session type that can only contain instances of
+     * {@link OutputConfigurationCompat}.
+     * The outputs can run using high speed FPS ranges. Calls to {@link #setInputConfiguration}
+     * are not supported.
+     *
+     * @see CameraDevice#createConstrainedHighSpeedCaptureSession
+     */
+    public static final int SESSION_HIGH_SPEED =
+            CameraDeviceCompat.SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED;
+    private final SessionConfigurationCompatImpl mImpl;
+
+    /**
+     * Create a new {@link SessionConfigurationCompat}.
+     *
+     * @param sessionType   The session type.
+     * @param outputsCompat A list of output configurations for the capture session.
+     * @param executor      The executor which should be used to invoke the callback. In general
+     *                      it is
+     *                      recommended that camera operations are not done on the main (UI) thread.
+     * @param cb            A state callback interface implementation.
+     * @see #SESSION_REGULAR
+     * @see #SESSION_HIGH_SPEED
+     */
+    public SessionConfigurationCompat(@SessionMode int sessionType,
+            @NonNull List<OutputConfigurationCompat> outputsCompat,
+            @NonNull /* @CallbackExecutor */ Executor executor,
+            @NonNull CameraCaptureSession.StateCallback cb) {
+        if (Build.VERSION.SDK_INT < 28) {
+            mImpl = new SessionConfigurationCompatBaseImpl(sessionType, outputsCompat, executor,
+                    cb);
+        } else {
+            mImpl = new SessionConfigurationCompatApi28Impl(sessionType, outputsCompat, executor,
+                    cb);
+        }
+    }
+
+    private SessionConfigurationCompat(@NonNull SessionConfigurationCompatImpl impl) {
+        mImpl = impl;
+    }
+
+    /**
+     * Creates an instance from a framework android.hardware.camera2.params.SessionConfiguration
+     * object.
+     *
+     * <p>This method always returns {@code null} on API &lt;= 27.</p>
+     *
+     * @param sessionConfiguration an android.hardware.camera2.params.SessionConfiguration object,
+     *                             or {@code null} if none.
+     * @return an equivalent {@link SessionConfigurationCompat} object, or {@code null} if not
+     * supported.
+     */
+    @Nullable
+    public static SessionConfigurationCompat wrap(@Nullable Object sessionConfiguration) {
+        if (sessionConfiguration == null) {
+            return null;
+        }
+        if (Build.VERSION.SDK_INT < 28) {
+            return null;
+        }
+
+        return new SessionConfigurationCompat(
+                new SessionConfigurationCompatApi28Impl(sessionConfiguration));
+    }
+
+    @RequiresApi(24)
+    static List<OutputConfigurationCompat> transformToCompat(
+            @NonNull List<OutputConfiguration> outputConfigurations) {
+        ArrayList<OutputConfigurationCompat> outList = new ArrayList<>(outputConfigurations.size());
+        for (OutputConfiguration outputConfiguration : outputConfigurations) {
+            outList.add(OutputConfigurationCompat.wrap(outputConfiguration));
+        }
+
+        return outList;
+    }
+
+    @RequiresApi(24)
+    static List<OutputConfiguration> transformFromCompat(
+            @NonNull List<OutputConfigurationCompat> outputConfigurations) {
+        ArrayList<OutputConfiguration> outList = new ArrayList<>(outputConfigurations.size());
+        for (OutputConfigurationCompat outputConfiguration : outputConfigurations) {
+            outList.add((OutputConfiguration) outputConfiguration.unwrap());
+        }
+
+        return outList;
+    }
+
+    /**
+     * Retrieve the type of the capture session.
+     *
+     * @return The capture session type.
+     */
+    @SessionMode
+    public int getSessionType() {
+        return mImpl.getSessionType();
+    }
+
+    /**
+     * Retrieve the {@link OutputConfigurationCompat} list for the capture session.
+     *
+     * @return A list of output configurations for the capture session.
+     */
+    public List<OutputConfigurationCompat> getOutputConfigurations() {
+        return mImpl.getOutputConfigurations();
+    }
+
+    /**
+     * Retrieve the {@link CameraCaptureSession.StateCallback} for the capture session.
+     *
+     * @return A state callback interface implementation.
+     */
+    public CameraCaptureSession.StateCallback getStateCallback() {
+        return mImpl.getStateCallback();
+    }
+
+    /**
+     * Retrieve the {@link Executor} for the capture session.
+     *
+     * @return The Executor on which the callback will be invoked.
+     */
+    public Executor getExecutor() {
+        return mImpl.getExecutor();
+    }
+
+    /**
+     * Retrieve the {@link InputConfigurationCompat}.
+     *
+     * @return The capture session input configuration.
+     */
+    public InputConfigurationCompat getInputConfiguration() {
+        return mImpl.getInputConfiguration();
+    }
+
+    /**
+     * Sets the {@link InputConfigurationCompat} for a reprocessable session. Input configuration
+     * are not supported for {@link #SESSION_HIGH_SPEED}.
+     *
+     * @param input Input configuration.
+     * @throws UnsupportedOperationException In case it is called for {@link #SESSION_HIGH_SPEED}
+     *                                       type session configuration.
+     */
+    public void setInputConfiguration(@NonNull InputConfigurationCompat input) {
+        mImpl.setInputConfiguration(input);
+    }
+
+    /**
+     * Retrieve the session wide camera parameters (see {@link CaptureRequest}).
+     *
+     * @return A capture request that includes the initial values for any available
+     * session wide capture keys.
+     */
+    public CaptureRequest getSessionParameters() {
+        return mImpl.getSessionParameters();
+    }
+
+    /**
+     * Sets the session wide camera parameters (see {@link CaptureRequest}). This argument can
+     * be set for every supported session type and will be passed to the camera device as part
+     * of the capture session initialization. Session parameters are a subset of the available
+     * capture request parameters (see {@code CameraCharacteristics.getAvailableSessionKeys})
+     * and their application can introduce internal camera delays. To improve camera performance
+     * it is suggested to change them sparingly within the lifetime of the capture session and
+     * to pass their initial values as part of this method.
+     *
+     * @param params A capture request that includes the initial values for any available
+     *               session wide capture keys. Tags (see {@link CaptureRequest.Builder#setTag}) and
+     *               output targets (see {@link CaptureRequest.Builder#addTarget}) are ignored if
+     *               set. Parameter values not part of
+     *               {@code CameraCharacteristics.getAvailableSessionKeys} will also be ignored. It
+     *               is recommended to build the session parameters using the same template type as
+     *               the initial capture request, so that the session and initial request parameters
+     *               match as much as possible.
+     */
+    public void setSessionParameters(CaptureRequest params) {
+        mImpl.setSessionParameters(params);
+    }
+
+    /**
+     * Gets the underlying framework android.hardware.camera2.params.SessionConfiguration object.
+     *
+     * <p>This method always returns {@code null} on API &lt;= 27.</p>
+     *
+     * @return an equivalent android.hardware.camera2.params.SessionConfiguration object, or
+     * {@code null} if not supported.
+     */
+    @Nullable
+    public Object unwrap() {
+        return mImpl.getSessionConfiguration();
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (!(obj instanceof SessionConfigurationCompat)) {
+            return false;
+        }
+
+        return mImpl.equals(((SessionConfigurationCompat) obj).mImpl);
+    }
+
+    /** @hide */
+    @RestrictTo(Scope.LIBRARY)
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value =
+            {SESSION_REGULAR, SESSION_HIGH_SPEED})
+    public @interface SessionMode {
+    }
+
+    private interface SessionConfigurationCompatImpl {
+        @SessionMode
+        int getSessionType();
+
+        List<OutputConfigurationCompat> getOutputConfigurations();
+
+        CameraCaptureSession.StateCallback getStateCallback();
+
+        Executor getExecutor();
+
+        InputConfigurationCompat getInputConfiguration();
+
+        void setInputConfiguration(@NonNull InputConfigurationCompat input);
+
+        CaptureRequest getSessionParameters();
+
+        void setSessionParameters(CaptureRequest params);
+
+        @Nullable
+        Object getSessionConfiguration();
+    }
+
+    private static final class SessionConfigurationCompatBaseImpl implements
+            SessionConfigurationCompatImpl {
+
+        private final List<OutputConfigurationCompat> mOutputConfigurations;
+        private final CameraCaptureSession.StateCallback mStateCallback;
+        private final Executor mExecutor;
+        private int mSessionType;
+        private InputConfigurationCompat mInputConfig = null;
+        private CaptureRequest mSessionParameters = null;
+
+        SessionConfigurationCompatBaseImpl(@SessionMode int sessionType,
+                @NonNull List<OutputConfigurationCompat> outputs,
+                @NonNull /* @CallbackExecutor */ Executor executor,
+                @NonNull CameraCaptureSession.StateCallback cb) {
+            mSessionType = sessionType;
+            mOutputConfigurations = Collections.unmodifiableList(new ArrayList<>(outputs));
+            mStateCallback = cb;
+            mExecutor = executor;
+        }
+
+        @Override
+        public int getSessionType() {
+            return mSessionType;
+        }
+
+        @Override
+        public List<OutputConfigurationCompat> getOutputConfigurations() {
+            return mOutputConfigurations;
+        }
+
+        @Override
+        public CameraCaptureSession.StateCallback getStateCallback() {
+            return mStateCallback;
+        }
+
+        @Override
+        public Executor getExecutor() {
+            return mExecutor;
+        }
+
+        @Nullable
+        @Override
+        public InputConfigurationCompat getInputConfiguration() {
+            return mInputConfig;
+        }
+
+        @Override
+        public void setInputConfiguration(@NonNull InputConfigurationCompat input) {
+            if (mSessionType != SESSION_HIGH_SPEED) {
+                mInputConfig = input;
+            } else {
+                throw new UnsupportedOperationException(
+                        "Method not supported for high speed session types");
+            }
+        }
+
+        @Override
+        public CaptureRequest getSessionParameters() {
+            return mSessionParameters;
+        }
+
+        @Override
+        public void setSessionParameters(CaptureRequest params) {
+            mSessionParameters = params;
+        }
+
+        @Nullable
+        @Override
+        public Object getSessionConfiguration() {
+            return null;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object obj) {
+            if (this == obj) {
+                return true;
+            } else if (obj instanceof SessionConfigurationCompatBaseImpl) {
+                SessionConfigurationCompatBaseImpl other = (SessionConfigurationCompatBaseImpl) obj;
+                if (mInputConfig != other.mInputConfig
+                        || mSessionType != other.mSessionType
+                        || mOutputConfigurations.size() != other.mOutputConfigurations.size()) {
+                    return false;
+                }
+
+                for (int i = 0; i < mOutputConfigurations.size(); i++) {
+                    if (!mOutputConfigurations.get(i).equals(other.mOutputConfigurations.get(i))) {
+                        return false;
+                    }
+                }
+
+                return true;
+            }
+
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            int h = 1;
+            // Strength reduction; in case the compiler has illusions about divisions being faster
+            h = ((h << 5) - h)
+                    ^ mOutputConfigurations.hashCode(); // (h * 31) XOR mOutputConfigurations
+            // .hashCode()
+            h = ((h << 5) - h) ^ (mInputConfig == null ? 0
+                    : mInputConfig.hashCode()); // (h * 31) XOR mInputConfig.hashCode()
+            h = ((h << 5) - h) ^ mSessionType; // (h * 31) XOR mSessionType
+
+            return h;
+        }
+    }
+
+    @RequiresApi(28)
+    private static final class SessionConfigurationCompatApi28Impl implements
+            SessionConfigurationCompatImpl {
+
+        private final SessionConfiguration mObject;
+        private final List<OutputConfigurationCompat> mOutputConfigurations;
+
+        SessionConfigurationCompatApi28Impl(@NonNull Object sessionConfiguration) {
+            mObject = (SessionConfiguration) sessionConfiguration;
+            mOutputConfigurations = Collections.unmodifiableList(transformToCompat(
+                    ((SessionConfiguration) sessionConfiguration).getOutputConfigurations()));
+        }
+
+        SessionConfigurationCompatApi28Impl(@SessionMode int sessionType,
+                @NonNull List<OutputConfigurationCompat> outputs,
+                @NonNull /* @CallbackExecutor */ Executor executor,
+                @NonNull CameraCaptureSession.StateCallback cb) {
+            this(new SessionConfiguration(sessionType, transformFromCompat(outputs), executor, cb));
+        }
+
+        @Override
+        public int getSessionType() {
+            return mObject.getSessionType();
+        }
+
+        @Override
+        public List<OutputConfigurationCompat> getOutputConfigurations() {
+            // Return cached compat version of list
+            return mOutputConfigurations;
+        }
+
+        @Override
+        public CameraCaptureSession.StateCallback getStateCallback() {
+            return mObject.getStateCallback();
+        }
+
+        @Override
+        public Executor getExecutor() {
+            return mObject.getExecutor();
+        }
+
+        @Override
+        public InputConfigurationCompat getInputConfiguration() {
+            return InputConfigurationCompat.wrap(mObject.getInputConfiguration());
+        }
+
+        @Override
+        public void setInputConfiguration(@NonNull InputConfigurationCompat input) {
+            mObject.setInputConfiguration((InputConfiguration) input.unwrap());
+        }
+
+        @Override
+        public CaptureRequest getSessionParameters() {
+            return mObject.getSessionParameters();
+        }
+
+        @Override
+        public void setSessionParameters(CaptureRequest params) {
+            mObject.setSessionParameters(params);
+        }
+
+        @Nullable
+        @Override
+        public Object getSessionConfiguration() {
+            return mObject;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object obj) {
+            if (!(obj instanceof SessionConfigurationCompatApi28Impl)) {
+                return false;
+            }
+
+            return Objects.equals(mObject, ((SessionConfigurationCompatApi28Impl) obj).mObject);
+        }
+
+        @Override
+        public int hashCode() {
+            return mObject.hashCode();
+        }
+    }
+}
diff --git a/camera/camera2/src/test/java/androidx/camera/camera2/impl/compat/params/InputConfigurationCompatTest.java b/camera/camera2/src/test/java/androidx/camera/camera2/impl/compat/params/InputConfigurationCompatTest.java
index 3470302..6c421e4 100644
--- a/camera/camera2/src/test/java/androidx/camera/camera2/impl/compat/params/InputConfigurationCompatTest.java
+++ b/camera/camera2/src/test/java/androidx/camera/camera2/impl/compat/params/InputConfigurationCompatTest.java
@@ -34,7 +34,7 @@
 @RunWith(RobolectricTestRunner.class)
 @DoNotInstrument
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class InputConfigurationCompatTest {
+public final class InputConfigurationCompatTest {
 
     private static final int WIDTH = 1024;
     private static final int HEIGHT = 768;
diff --git a/camera/camera2/src/test/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatTest.java b/camera/camera2/src/test/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatTest.java
new file mode 100644
index 0000000..0b99551
--- /dev/null
+++ b/camera/camera2/src/test/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatTest.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 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.camera.camera2.impl.compat.params;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.os.Build;
+import android.util.Size;
+import android.view.Surface;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+public final class OutputConfigurationCompatTest {
+
+    private static final int TEST_GROUP_ID = 100;
+    private static final String PHYSICAL_CAMERA_ID = "1";
+
+    private static void assumeSurfaceSharingAvailable(
+            OutputConfigurationCompat outputConfigCompat) {
+        Assume.assumeTrue("API level does not support surface sharing.",
+                outputConfigCompat.getMaxSharedSurfaceCount() > 1);
+    }
+
+    @Test
+    public void canRetrieveSurface() {
+        Surface surface = mock(Surface.class);
+        OutputConfigurationCompat outputConfigCompat = new OutputConfigurationCompat(surface);
+
+        assertThat(outputConfigCompat.getSurface()).isSameInstanceAs(surface);
+    }
+
+    @Test
+    public void defaultSurfaceGroupIdIsSet() {
+        Surface surface = mock(Surface.class);
+        OutputConfigurationCompat outputConfigCompat = new OutputConfigurationCompat(surface);
+
+        assertThat(outputConfigCompat.getSurfaceGroupId()).isEqualTo(
+                OutputConfigurationCompat.SURFACE_GROUP_ID_NONE);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void addSurfaceThrows_whenAddingMoreThanMax() {
+        Surface surface = mock(Surface.class);
+        OutputConfigurationCompat outputConfigCompat = new OutputConfigurationCompat(surface);
+
+        assumeSurfaceSharingAvailable(outputConfigCompat);
+        outputConfigCompat.enableSurfaceSharing();
+
+        // Since we already have 1 surface added, if we try to add the max we will be adding a
+        // total of max surfaces + 1
+        for (int i = 0; i < outputConfigCompat.getMaxSharedSurfaceCount(); ++i) {
+            outputConfigCompat.addSurface(mock(Surface.class));
+        }
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void addSurfaceThrows_whenSurfaceSharingNotEnabled() {
+        Surface surface = mock(Surface.class);
+        OutputConfigurationCompat outputConfigCompat = new OutputConfigurationCompat(surface);
+
+        // Adding a second surface should fail if OutputConfigurationCompat#enableSurfaceSharing
+        // () has not been called.
+        outputConfigCompat.addSurface(mock(Surface.class));
+    }
+
+    @Test
+    public void maxSurfaces_canBeAdded_andRetrieved() {
+        Surface surface = mock(Surface.class);
+        OutputConfigurationCompat outputConfigCompat = new OutputConfigurationCompat(surface);
+
+        outputConfigCompat.enableSurfaceSharing();
+        List<Surface> allSurfaces = new ArrayList<Surface>();
+        allSurfaces.add(surface);
+
+        for (int i = 0; i < outputConfigCompat.getMaxSharedSurfaceCount() - 1; ++i) {
+            Surface newSurface = mock(Surface.class);
+            allSurfaces.add(newSurface);
+            outputConfigCompat.addSurface(newSurface);
+        }
+
+        assertThat(outputConfigCompat.getSurfaces()).containsExactlyElementsIn(allSurfaces);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void cannotRemoveMainSurface() {
+        Surface surface = mock(Surface.class);
+        OutputConfigurationCompat outputConfigCompat = new OutputConfigurationCompat(surface);
+        outputConfigCompat.removeSurface(surface);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void removeNonAddedSurfaceThrows() {
+        Surface surface = mock(Surface.class);
+        OutputConfigurationCompat outputConfigCompat = new OutputConfigurationCompat(surface);
+        outputConfigCompat.removeSurface(mock(Surface.class));
+    }
+
+    @Test
+    public void canRemoveSharedSurface() {
+        Surface surface = mock(Surface.class);
+        OutputConfigurationCompat outputConfigCompat = new OutputConfigurationCompat(surface);
+
+        assumeSurfaceSharingAvailable(outputConfigCompat);
+        outputConfigCompat.enableSurfaceSharing();
+        List<Surface> allSurfaces = new ArrayList<Surface>();
+        allSurfaces.add(surface);
+
+        for (int i = 0; i < outputConfigCompat.getMaxSharedSurfaceCount() - 1; ++i) {
+            Surface newSurface = mock(Surface.class);
+            allSurfaces.add(newSurface);
+            outputConfigCompat.addSurface(newSurface);
+        }
+
+        Surface lastSurface = allSurfaces.remove(allSurfaces.size() - 1);
+        boolean containedBeforeRemoval = outputConfigCompat.getSurfaces().contains(lastSurface);
+
+        outputConfigCompat.removeSurface(lastSurface);
+
+        assertThat(containedBeforeRemoval).isTrue();
+        assertThat(outputConfigCompat.getSurfaces()).doesNotContain(lastSurface);
+        assertThat(outputConfigCompat.getSurfaces()).containsExactlyElementsIn(allSurfaces);
+    }
+
+    @Test
+    @Config(maxSdk = 23)
+    public void cannotRetrieveOutputConfiguration_onApi23AndBelow() {
+        Surface surface = mock(Surface.class);
+        OutputConfigurationCompat outputConfigCompat = new OutputConfigurationCompat(surface);
+
+        Object outputConfig = outputConfigCompat.unwrap();
+        assertThat(outputConfig).isNull();
+    }
+
+    @Test
+    @Config(minSdk = 24)
+    public void canRetrieveOutputConfiguration_onApi24AndUp() {
+        Surface surface = mock(Surface.class);
+        OutputConfigurationCompat outputConfigCompat = new OutputConfigurationCompat(surface);
+
+        Object outputConfig = outputConfigCompat.unwrap();
+        assertThat(outputConfig).isNotNull();
+        assertThat(outputConfig).isInstanceOf(OutputConfiguration.class);
+    }
+
+    @Test
+    @Config(minSdk = 24)
+    public void canWrapOutputConfiguration() {
+        Surface surface = mock(Surface.class);
+        OutputConfiguration outputConfig = new OutputConfiguration(surface);
+        OutputConfigurationCompat outputConfigCompat = OutputConfigurationCompat.wrap(outputConfig);
+
+        assertThat(outputConfigCompat.unwrap()).isSameInstanceAs(outputConfig);
+    }
+
+    @Test
+    @Config(minSdk = 24)
+    public void canSetGroupId_andRetrieveThroughCompatObject() {
+        Surface surface = mock(Surface.class);
+        OutputConfiguration outputConfig = new OutputConfiguration(TEST_GROUP_ID, surface);
+        OutputConfigurationCompat outputConfigCompat = OutputConfigurationCompat.wrap(outputConfig);
+
+        assertThat(outputConfigCompat.getSurfaceGroupId()).isEqualTo(TEST_GROUP_ID);
+    }
+
+    @Test
+    @Config(minSdk = 26)
+    public void canCreateDeferredOutputConfiguration_andRetrieveNullSurface() {
+        Surface surface = mock(Surface.class);
+        OutputConfigurationCompat outputConfigCompat = new OutputConfigurationCompat(
+                new Size(1024, 768), SurfaceTexture.class);
+
+        assertThat(outputConfigCompat.getSurface()).isNull();
+    }
+
+    @Test
+    @Config(minSdk = 26, maxSdk = 26)
+    public void sharedSurfaceCount_canBeRetrievedOnApi26() {
+        Surface surface = mock(Surface.class);
+        OutputConfigurationCompat outputConfigCompat = new OutputConfigurationCompat(surface);
+
+        // API 26 hard-codes max shared surface count to 2, but we have to retrieve that via
+        // reflection
+        assertThat(outputConfigCompat.getMaxSharedSurfaceCount()).isEqualTo(2);
+    }
+
+    @Test
+    @Config(minSdk = 28)
+    public void canSetPhysicalCameraId() {
+        OutputConfiguration outputConfig = mock(OutputConfiguration.class);
+
+        OutputConfigurationCompat outputConfigCompat = OutputConfigurationCompat.wrap(outputConfig);
+
+        outputConfigCompat.setPhysicalCameraId(PHYSICAL_CAMERA_ID);
+
+        verify(outputConfig, times(1)).setPhysicalCameraId(PHYSICAL_CAMERA_ID);
+    }
+}
diff --git a/camera/camera2/src/test/java/androidx/camera/camera2/impl/compat/params/SessionConfigurationCompatTest.java b/camera/camera2/src/test/java/androidx/camera/camera2/impl/compat/params/SessionConfigurationCompatTest.java
new file mode 100644
index 0000000..e122ef4
--- /dev/null
+++ b/camera/camera2/src/test/java/androidx/camera/camera2/impl/compat/params/SessionConfigurationCompatTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 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.camera.camera2.impl.compat.params;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
+import android.os.Build;
+import android.view.Surface;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+public final class SessionConfigurationCompatTest {
+
+    private static final int WIDTH = 1024;
+    private static final int HEIGHT = 768;
+    private static final int FORMAT = ImageFormat.YUV_420_888;
+    private static final int DEFAULT_SESSION_TYPE = SessionConfigurationCompat.SESSION_REGULAR;
+
+    private List<OutputConfigurationCompat> mOutputs;
+    private Executor mCallbackExecutor;
+    private CameraCaptureSession.StateCallback mStateCallback;
+
+    @Before
+    public void setUp() {
+        mOutputs = new ArrayList<>();
+        for (int i = 0; i < 3; ++i) {
+            Surface surface = mock(Surface.class);
+            OutputConfigurationCompat outputConfigCompat = new OutputConfigurationCompat(surface);
+            mOutputs.add(outputConfigCompat);
+        }
+
+        mCallbackExecutor = mock(Executor.class);
+
+        mStateCallback = mock(CameraCaptureSession.StateCallback.class);
+    }
+
+    private SessionConfigurationCompat createDefaultSessionConfig() {
+        return new SessionConfigurationCompat(
+                DEFAULT_SESSION_TYPE,
+                mOutputs,
+                mCallbackExecutor,
+                mStateCallback);
+    }
+
+    @Test
+    public void canCreateSessionConfiguration() {
+        SessionConfigurationCompat sessionConfigCompat = createDefaultSessionConfig();
+
+        assertThat(sessionConfigCompat.getSessionType()).isEqualTo(DEFAULT_SESSION_TYPE);
+        assertThat(sessionConfigCompat.getOutputConfigurations()).containsExactlyElementsIn(
+                mOutputs);
+        assertThat(sessionConfigCompat.getExecutor()).isSameInstanceAs(mCallbackExecutor);
+        assertThat(sessionConfigCompat.getStateCallback()).isSameInstanceAs(mStateCallback);
+    }
+
+    @Test
+    @Config(minSdk = 28)
+    public void canWrapAndUnwrapSessionConfiguration() {
+        List<OutputConfiguration> outputConfigs = new ArrayList<>(mOutputs.size());
+        for (OutputConfigurationCompat outputConfigCompat : mOutputs) {
+            outputConfigs.add((OutputConfiguration) outputConfigCompat.unwrap());
+        }
+
+        SessionConfiguration sessionConfig = new SessionConfiguration(
+                SessionConfiguration.SESSION_REGULAR,
+                outputConfigs,
+                mCallbackExecutor,
+                mStateCallback);
+
+        SessionConfigurationCompat sessionConfigCompat = SessionConfigurationCompat.wrap(
+                sessionConfig);
+
+        assertThat(sessionConfigCompat.getSessionType()).isEqualTo(
+                SessionConfigurationCompat.SESSION_REGULAR);
+        assertThat(sessionConfigCompat.getOutputConfigurations()).containsExactlyElementsIn(
+                mOutputs);
+        assertThat(sessionConfigCompat.getExecutor()).isSameInstanceAs(mCallbackExecutor);
+        assertThat(sessionConfigCompat.getStateCallback()).isSameInstanceAs(mStateCallback);
+
+        assertThat(sessionConfigCompat.unwrap()).isSameInstanceAs(sessionConfig);
+        assertThat(
+                ((SessionConfiguration) sessionConfigCompat.unwrap()).getOutputConfigurations())
+                .containsExactlyElementsIn(
+                outputConfigs);
+    }
+
+    @Test
+    public void canSetAndRetrieveInputConfiguration() {
+        SessionConfigurationCompat sessionConfigCompat = createDefaultSessionConfig();
+
+        InputConfigurationCompat inputConfigurationCompat = new InputConfigurationCompat(WIDTH,
+                HEIGHT, FORMAT);
+
+        sessionConfigCompat.setInputConfiguration(inputConfigurationCompat);
+
+        // getInputConfiguration() is not necessarily the same instance, but should be equivalent
+        // by comparison
+        assertThat(sessionConfigCompat.getInputConfiguration()).isEqualTo(inputConfigurationCompat);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void cannotSetInputConfiguration_onHighSpeedSession() {
+        SessionConfigurationCompat sessionConfigCompat = new SessionConfigurationCompat(
+                SessionConfigurationCompat.SESSION_HIGH_SPEED,
+                mOutputs,
+                mCallbackExecutor,
+                mStateCallback);
+
+        InputConfigurationCompat inputConfigurationCompat = new InputConfigurationCompat(WIDTH,
+                HEIGHT, FORMAT);
+
+        sessionConfigCompat.setInputConfiguration(inputConfigurationCompat);
+    }
+
+    @Test
+    @Config(minSdk = 28)
+    public void constantsMatchNonCompatVersion() {
+        assertThat(SessionConfigurationCompat.SESSION_REGULAR).isEqualTo(
+                SessionConfiguration.SESSION_REGULAR);
+        assertThat(SessionConfigurationCompat.SESSION_HIGH_SPEED).isEqualTo(
+                SessionConfiguration.SESSION_HIGH_SPEED);
+    }
+}
diff --git a/camera/core/src/androidTest/java/androidx/camera/core/CaptureConfigTest.java b/camera/core/src/androidTest/java/androidx/camera/core/CaptureConfigTest.java
index 1a332d5..b4e0c52 100644
--- a/camera/core/src/androidTest/java/androidx/camera/core/CaptureConfigTest.java
+++ b/camera/core/src/androidTest/java/androidx/camera/core/CaptureConfigTest.java
@@ -27,7 +27,7 @@
 import android.view.Surface;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.LargeTest;
 
 import com.google.common.collect.Lists;
 
@@ -38,7 +38,7 @@
 import java.util.List;
 import java.util.Map;
 
-@SmallTest
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public class CaptureConfigTest {
     private DeferrableSurface mMockSurface0;
diff --git a/camera/core/src/androidTest/java/androidx/camera/core/ForwardingImageReaderListenerTest.java b/camera/core/src/androidTest/java/androidx/camera/core/ForwardingImageReaderListenerTest.java
index 6e9020e..cec4086 100644
--- a/camera/core/src/androidTest/java/androidx/camera/core/ForwardingImageReaderListenerTest.java
+++ b/camera/core/src/androidTest/java/androidx/camera/core/ForwardingImageReaderListenerTest.java
@@ -33,7 +33,7 @@
 import android.view.Surface;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.LargeTest;
 
 import org.junit.After;
 import org.junit.Before;
@@ -44,7 +44,7 @@
 import java.util.List;
 import java.util.concurrent.Semaphore;
 
-@SmallTest
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public final class ForwardingImageReaderListenerTest {
     private static final int IMAGE_WIDTH = 640;
diff --git a/camera/core/src/androidTest/java/androidx/camera/core/ImageSaverTest.java b/camera/core/src/androidTest/java/androidx/camera/core/ImageSaverTest.java
index 3cce59c..96173bc 100644
--- a/camera/core/src/androidTest/java/androidx/camera/core/ImageSaverTest.java
+++ b/camera/core/src/androidTest/java/androidx/camera/core/ImageSaverTest.java
@@ -37,7 +37,7 @@
 import androidx.camera.core.ImageSaver.OnImageSavedListener;
 import androidx.camera.core.ImageSaver.SaveError;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.MediumTest;
 
 import org.junit.After;
 import org.junit.Before;
@@ -50,7 +50,7 @@
 import java.nio.ByteBuffer;
 import java.util.concurrent.Semaphore;
 
-@SmallTest
+@MediumTest
 @RunWith(AndroidJUnit4.class)
 public class ImageSaverTest {
 
diff --git a/camera/core/src/androidTest/java/androidx/camera/core/QueuedImageReaderProxyTest.java b/camera/core/src/androidTest/java/androidx/camera/core/QueuedImageReaderProxyTest.java
index 21403c2..d7028b6 100644
--- a/camera/core/src/androidTest/java/androidx/camera/core/QueuedImageReaderProxyTest.java
+++ b/camera/core/src/androidTest/java/androidx/camera/core/QueuedImageReaderProxyTest.java
@@ -30,7 +30,7 @@
 import android.view.Surface;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.LargeTest;
 
 import org.junit.After;
 import org.junit.Before;
@@ -41,7 +41,7 @@
 import java.util.List;
 import java.util.concurrent.Semaphore;
 
-@SmallTest
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public final class QueuedImageReaderProxyTest {
     private static final int IMAGE_WIDTH = 640;
diff --git a/camera/core/src/androidTest/java/androidx/camera/core/SessionConfigTest.java b/camera/core/src/androidTest/java/androidx/camera/core/SessionConfigTest.java
index 67fe0fe..df85c17a 100644
--- a/camera/core/src/androidTest/java/androidx/camera/core/SessionConfigTest.java
+++ b/camera/core/src/androidTest/java/androidx/camera/core/SessionConfigTest.java
@@ -27,7 +27,7 @@
 import android.view.Surface;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.MediumTest;
 
 import com.google.common.collect.Lists;
 
@@ -38,7 +38,7 @@
 import java.util.List;
 import java.util.Map;
 
-@SmallTest
+@MediumTest
 @RunWith(AndroidJUnit4.class)
 public class SessionConfigTest {
     private DeferrableSurface mMockSurface0;
diff --git a/camera/core/src/androidTest/java/androidx/camera/core/UseCaseAttachStateTest.java b/camera/core/src/androidTest/java/androidx/camera/core/UseCaseAttachStateTest.java
index d48db54..7b398af 100644
--- a/camera/core/src/androidTest/java/androidx/camera/core/UseCaseAttachStateTest.java
+++ b/camera/core/src/androidTest/java/androidx/camera/core/UseCaseAttachStateTest.java
@@ -33,7 +33,7 @@
 import androidx.camera.testing.fakes.FakeUseCaseConfig;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.LargeTest;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -43,7 +43,7 @@
 import java.util.HashMap;
 import java.util.Map;
 
-@SmallTest
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public class UseCaseAttachStateTest {
     private final LensFacing mCameraLensFacing0 = LensFacing.BACK;
diff --git a/camera/core/src/main/java/androidx/camera/core/ImageAnalysis.java b/camera/core/src/main/java/androidx/camera/core/ImageAnalysis.java
index 259b692..6c5e425 100644
--- a/camera/core/src/main/java/androidx/camera/core/ImageAnalysis.java
+++ b/camera/core/src/main/java/androidx/camera/core/ImageAnalysis.java
@@ -103,8 +103,8 @@
      *
      * <p>In most cases this should be set to the current rotation returned by {@link
      * Display#getRotation()}.  In that case, the rotation parameter sent to the analyzer will be
-     * the rotation, which if applied to the output image, will make it match a correctly configured
-     * preview.
+     * the rotation, which if applied to the output image, will make the image match the display
+     * orientation.
      *
      * <p>While rotation can also be set via
      * {@link ImageAnalysisConfig.Builder#setTargetRotation(int)}, using
@@ -115,9 +115,11 @@
      *
      * <p>If no target rotation is set by the application, it is set to the value of
      * {@link Display#getRotation()} of the default display at the time the
-     * {@link ImageAnalysis} is created.
+     * use case is created.
      *
-     * @param rotation Desired rotation of the output image.
+     * @param rotation Target rotation of the output image, expressed as one of
+     *                 {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
+     *                 {@link Surface#ROTATION_180}, or {@link Surface#ROTATION_270}.
      */
     public void setTargetRotation(@RotationValue int rotation) {
         ImageAnalysisConfig oldConfig = (ImageAnalysisConfig) getUseCaseConfig();
@@ -344,6 +346,9 @@
         /**
          * Analyzes an image to produce a result.
          *
+         * <p>This method is called once for each image from the camera, and called at the
+         * frame rate of the camera.  Each analyze call is executed sequentially.
+         *
          * <p>The caller is responsible for ensuring this analysis method can be executed quickly
          * enough to prevent stalls in the image acquisition pipeline. Otherwise, newly available
          * images will not be acquired and analyzed.
diff --git a/camera/core/src/main/java/androidx/camera/core/ImageCapture.java b/camera/core/src/main/java/androidx/camera/core/ImageCapture.java
index 10fb18a..7aac19e 100644
--- a/camera/core/src/main/java/androidx/camera/core/ImageCapture.java
+++ b/camera/core/src/main/java/androidx/camera/core/ImageCapture.java
@@ -293,8 +293,7 @@
      *
      * <p>In most cases this should be set to the current rotation returned by {@link
      * Display#getRotation()}.  In that case, the output rotation from takePicture calls will be the
-     * rotation, which if applied to the output image, will make it match a correctly configured
-     * preview.
+     * rotation, which if applied to the output image, will make it match the display orientation.
      *
      * <p>While rotation can also be set via
      * {@link ImageCaptureConfig.Builder#setTargetRotation(int)}, using
@@ -304,9 +303,9 @@
      *
      * <p>If no target rotation is set by the application, it is set to the value of
      * {@link Display#getRotation()} of the default display at the time the
-     * {@link ImageCapture} is created.
+     * use case is created.
      *
-     * @param rotation Desired rotation of the output image. rotation is expressed as one of
+     * @param rotation Target rotation of the output image, expressed as one of
      *                 {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
      *                 {@link Surface#ROTATION_180}, or {@link Surface#ROTATION_270}.
      */
@@ -1004,10 +1003,10 @@
          * rotation applied.  rotationDegrees describes the magnitude of clockwise rotation, which
          * if applied to the image will make it match the currently configured target rotation.
          *
-         * <p>For example, if the current target rotation is set to the display rotation, then for a
-         * correctly configured preview, rotationDegrees is the rotation to apply to the image to
-         * match what is seen in the preview.  A rotation of 90 degrees would mean rotating the
-         * image 90 degrees clockwise produces an image that will match the preview.
+         * <p>For example, if the current target rotation is set to the display rotation,
+         * rotationDegrees is the rotation to apply to the image to match the display orientation.
+         * A rotation of 90 degrees would mean rotating the image 90 degrees clockwise produces an
+         * image that will match the display orientation.
          *
          * <p>See also {@link ImageCaptureConfig.Builder#setTargetRotation(int)} and
          * {@link #setTargetRotation(int)}.
diff --git a/camera/core/src/main/java/androidx/camera/core/Preview.java b/camera/core/src/main/java/androidx/camera/core/Preview.java
index 100ecce..94d371e 100644
--- a/camera/core/src/main/java/androidx/camera/core/Preview.java
+++ b/camera/core/src/main/java/androidx/camera/core/Preview.java
@@ -40,10 +40,31 @@
 import java.util.Objects;
 
 /**
- * A use case that provides a camera preview stream for a view finder.
+ * A use case that provides a camera preview stream for displaying on-screen.
  *
- * <p>The preview stream is connected to an underlying {@link SurfaceTexture}. The caller is still
- * responsible for deciding how this texture is shown.
+ * <p>The preview stream is connected to an underlying {@link SurfaceTexture}.  This SurfaceTexture
+ * is created by the Preview use case and provided as an output after it is configured and attached
+ * to the camera.  The application receives the SurfaceTexture by setting an output listener with
+ * {@link Preview#setOnPreviewOutputUpdateListener(OnPreviewOutputUpdateListener)}. When the
+ * lifecycle becomes active, the camera will start and images will be streamed to the
+ * SurfaceTexture.
+ * {@link OnPreviewOutputUpdateListener#onUpdated(PreviewOutput)} is called when a
+ * new SurfaceTexture is created.  A SurfaceTexture is created each time the use case becomes
+ * active and no previous SurfaceTexture exists.
+ *
+ * <p>The application can then decide how this texture is shown.  The texture data is as received
+ * by the camera system with no rotation applied.  To display the SurfaceTexture with the correct
+ * orientation, the rotation parameter sent to {@link Preview.OnPreviewOutputUpdateListener} can
+ * be used to create a correct transformation matrix for display. See
+ * {@link #setTargetRotation(int)} and {@link PreviewConfig.Builder#setTargetRotation(int)} for
+ * details.  See {@link Preview#setOnPreviewOutputUpdateListener(OnPreviewOutputUpdateListener)} for
+ * notes if attaching the SurfaceTexture to {@link android.view.TextureView}.
+ *
+ * <p>The application is responsible for managing SurfaceTexture after receiving it.  See
+ * {@link Preview#setOnPreviewOutputUpdateListener(OnPreviewOutputUpdateListener)} for notes on
+ * if overriding {@link
+ * android.view.TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed(SurfaceTexture)}.
+ *
  */
 public class Preview extends UseCase {
     /**
@@ -72,7 +93,7 @@
     private boolean mSurfaceDispatched = false;
 
     /**
-     * Creates a new view finder use case from the given configuration.
+     * Creates a new preview use case from the given configuration.
      *
      * @param config for this use case instance
      */
@@ -126,11 +147,14 @@
      * data. Setting the listener to {@code null} will signal to the camera that the camera should
      * no longer stream data to the last {@link PreviewOutput}.
      *
-     * <p>Once {@link OnPreviewOutputUpdateListener#onUpdated(PreviewOutput)} is called,
+     * <p>Once {@link OnPreviewOutputUpdateListener#onUpdated(PreviewOutput)}  is called,
      * ownership of the {@link PreviewOutput} and its contents is transferred to the application. It
      * is the application's responsibility to release the last {@link SurfaceTexture} returned by
      * {@link PreviewOutput#getSurfaceTexture()} when a new SurfaceTexture is provided via an update
-     * or when the user is finished with the use case.
+     * or when the user is finished with the use case.  A SurfaceTexture is created each time the
+     * use case becomes active and no previous SurfaceTexture exists.
+     * {@link OnPreviewOutputUpdateListener#onUpdated(PreviewOutput)} is called when a new
+     * SurfaceTexture is created.
      *
      * <p>Calling {@link android.view.TextureView#setSurfaceTexture(SurfaceTexture)} when the
      * TextureView's SurfaceTexture is already created, should be preceded by calling
@@ -138,8 +162,8 @@
      * {@link android.view.ViewGroup#addView(View)} on the parent view of the TextureView to ensure
      * the setSurfaceTexture() call succeeds.
      *
-     * <p>Since {@link OnPreviewOutputUpdateListener} is called when the underlying SurfaceTexture
-     * is created, applications that override and return false from {@link
+     * <p>Since {@link OnPreviewOutputUpdateListener#onUpdated(PreviewOutput)} is called when the
+     * underlying SurfaceTexture is created, applications that override and return false from {@link
      * android.view.TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed(SurfaceTexture)}
      * should be sure to call {@link android.view.TextureView#setSurfaceTexture(SurfaceTexture)}
      * with the output from the previous {@link PreviewOutput} to attach it to a new TextureView,
@@ -176,11 +200,13 @@
     }
 
     /**
-     * Adjusts the view finder according to the properties in some local regions.
+     * Adjusts the preview according to the properties in some local regions.
      *
      * <p>The auto-focus (AF) and auto-exposure (AE) properties will be recalculated from the local
      * regions.
      *
+     * <p>Dimensions of the sensor coordinate frame can be found using Camera2.
+     *
      * @param focus    rectangle with dimensions in sensor coordinate frame for focus
      * @param metering rectangle with dimensions in sensor coordinate frame for metering
      */
@@ -189,12 +215,14 @@
     }
 
     /**
-     * Adjusts the view finder according to the properties in some local regions with a callback
+     * Adjusts the preview according to the properties in some local regions with a callback
      * called once focus scan has completed.
      *
      * <p>The auto-focus (AF) and auto-exposure (AE) properties will be recalculated from the local
      * regions.
      *
+     * <p>Dimensions of the sensor coordinate frame can be found using Camera2.
+     *
      * @param focus    rectangle with dimensions in sensor coordinate frame for focus
      * @param metering rectangle with dimensions in sensor coordinate frame for metering
      * @param listener listener for when focus has completed
@@ -204,7 +232,12 @@
     }
 
     /**
-     * Adjusts the view finder to zoom to a local region.
+     * Adjusts the preview to zoom to a local region.
+     *
+     * <p>Setting the zoom is equivalent to setting a scalar crop region (digital zoom), and zoom
+     * occurs about the center of the image.
+     *
+     * <p>Dimensions of the sensor coordinate frame can be found using Camera2.
      *
      * @param crop rectangle with dimensions in sensor coordinate frame for zooming
      */
@@ -215,6 +248,9 @@
     /**
      * Sets torch on/off.
      *
+     * When the torch is on, the torch will remain on during photo capture regardless of flash
+     * setting.  When the torch is off, flash will function as set by {@link ImageCapture}.
+     *
      * @param torch True if turn on torch, otherwise false
      */
     public void enableTorch(boolean torch) {
@@ -227,13 +263,23 @@
     }
 
     /**
-     * Sets the rotation of the surface texture consumer.
+     * Sets the target rotation.
+     *
+     * <p>This informs the use case so it can adjust the rotation value sent to
+     * {@link Preview.OnPreviewOutputUpdateListener}.
      *
      * <p>In most cases this should be set to the current rotation returned by {@link
-     * Display#getRotation()}. This will update the rotation value in {@link PreviewOutput} to
-     * reflect the angle the PreviewOutput should be rotated to match the supplied rotation.
+     * Display#getRotation()}. In that case, the rotation values output by the use case will be
+     * the rotation, which if applied to the output image, will make the image match the display
+     * orientation.
      *
-     * @param rotation Rotation of the surface texture consumer.
+     * <p>If no target rotation is set by the application, it is set to the value of
+     * {@link Display#getRotation()} of the default display at the time the
+     * use case is created.
+     *
+     * @param rotation Rotation of the surface texture consumer expressed as one of
+     *                 {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
+     *                 {@link Surface#ROTATION_180}, or {@link Surface#ROTATION_270}.
      */
     public void setTargetRotation(@RotationValue int rotation) {
         ImageOutputConfig oldConfig = (ImageOutputConfig) getUseCaseConfig();
diff --git a/camera/extensions-stub/src/main/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java b/camera/extensions-stub/src/main/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
index 110cadd..540ed5d 100644
--- a/camera/extensions-stub/src/main/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
+++ b/camera/extensions-stub/src/main/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
@@ -40,4 +40,14 @@
     public CaptureStageImpl getCaptureStage() {
         throw new RuntimeException("Stub, replace with implementation.");
     }
+
+    @Override
+    public ProcessorType getProcessorType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public RequestUpdateProcessorImpl getRequestUpdatePreviewProcessor() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
 }
diff --git a/camera/extensions-stub/src/main/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java b/camera/extensions-stub/src/main/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java
index 15c0e76..d3336d9 100644
--- a/camera/extensions-stub/src/main/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java
+++ b/camera/extensions-stub/src/main/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java
@@ -29,7 +29,7 @@
      * This gets called to update where the CaptureProcessor should write the output of {@link
      * #process(Map)}.
      *
-     * @param surface The {@link Surface} that the CaptureProcessor should write data into.
+     * @param surface     The {@link Surface} that the CaptureProcessor should write data into.
      * @param imageFormat The format of that the surface expects.
      */
     void onOutputSurface(Surface surface, int imageFormat);
@@ -39,9 +39,10 @@
      *
      * <p> The result of the processing step should be written to the {@link Surface} that was
      * received by {@link #onOutputSurface(Surface, int)}.
-     * @param images The map of images to process. The {@link Image} that are contained within the
-     *               map will become invalid after this method completes, so no references to them
-     *               should be kept.
+     *
+     * @param images The map of images to process. The {@link Image} that are
+     *                contained within the map will become invalid after this method completes,
+     *                so no references to them should be kept.
      */
     void process(Map<Integer, Image> images);
 }
diff --git a/camera/extensions-stub/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java b/camera/extensions-stub/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
index 377f215..ff1206d 100644
--- a/camera/extensions-stub/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
+++ b/camera/extensions-stub/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
@@ -42,4 +42,14 @@
     public CaptureStageImpl getCaptureStage() {
         throw new RuntimeException("Stub, replace with implementation.");
     }
+
+    @Override
+    public ProcessorType getProcessorType() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public RequestUpdateProcessorImpl getRequestUpdatePreviewProcessor() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
 }
diff --git a/camera/extensions-stub/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java b/camera/extensions-stub/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
index 6e11eca..9402782 100644
--- a/camera/extensions-stub/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
+++ b/camera/extensions-stub/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
@@ -19,10 +19,16 @@
 import android.hardware.camera2.CameraCharacteristics;
 
 /**
- * Provides abstract methods that the OEM needs to implement to enable extensions in the view
- * finder.
+ * Provides abstract methods that the OEM needs to implement to enable extensions in the preview.
  */
 public interface PreviewExtenderImpl {
+    /** The different types of the preview processing. */
+    enum ProcessorType {
+        /** Processing which only updates the {@link CaptureStageImpl}. */
+        PROCESSOR_TYPE_REQUEST_UPDATE_ONLY,
+        PROCESSOR_TYPE_NONE
+    }
+
     /**
      * Indicates whether the extension is supported on the device.
      *
@@ -40,6 +46,21 @@
      */
     void enableExtension(String cameraId, CameraCharacteristics cameraCharacteristics);
 
-    /** The set of parameters required to produce the effect on images. */
+    /**
+     * The set of parameters required to produce the effect on the preview stream.
+     *
+     * <p> This will be the initial set of parameters used for the preview
+     * {@link android.hardware.camera2.CaptureRequest}. Once the {@link RequestUpdateProcessorImpl}
+     * from {@link #getRequestUpdatePreviewProcessor()} has been called, this should be updated to
+     * reflect the new {@link CaptureStageImpl}. If the processing step returns a {@code null},
+     * meaning the required parameters has not changed, then calling this will return the previous
+     * non-null value.
+     */
     CaptureStageImpl getCaptureStage();
+
+    /** The type of preview processing to use. */
+    ProcessorType getProcessorType();
+
+    /** Returns a processor which only updates the {@link CaptureStageImpl}. */
+    RequestUpdateProcessorImpl getRequestUpdatePreviewProcessor();
 }
diff --git a/camera/extensions-stub/src/main/java/androidx/camera/extensions/impl/RequestUpdateProcessorImpl.java b/camera/extensions-stub/src/main/java/androidx/camera/extensions/impl/RequestUpdateProcessorImpl.java
new file mode 100644
index 0000000..5c2e952
--- /dev/null
+++ b/camera/extensions-stub/src/main/java/androidx/camera/extensions/impl/RequestUpdateProcessorImpl.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 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.camera.extensions.impl;
+
+import android.hardware.camera2.TotalCaptureResult;
+
+/**
+ * Processing a {@link TotalCaptureResult} to update a CaptureStage.
+ */
+public interface RequestUpdateProcessorImpl {
+    /**
+     * Process the {@link TotalCaptureResult} to update the {@link CaptureStageImpl}
+     *
+     * @param result The metadata associated with the image. Can be null if the image and meta have
+     *               not been synced.
+     * @return The updated parameters used for the repeating requests. If this is {@code null} then
+     * the previous parameters will be used.
+     */
+    CaptureStageImpl process(TotalCaptureResult result);
+}
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
index 2eadb3a..aa3516a 100644
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
@@ -50,4 +50,14 @@
 
         return captureStage;
     }
+
+    @Override
+    public ProcessorType getProcessorType() {
+        return ProcessorType.PROCESSOR_TYPE_REQUEST_UPDATE_ONLY;
+    }
+
+    @Override
+    public RequestUpdateProcessorImpl getRequestUpdatePreviewProcessor() {
+        return RequestUpdateProcessorImpls.noUpdateProcessor();
+    }
 }
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
index 3602a4b..4f3cf65 100644
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
@@ -51,4 +51,14 @@
 
         return captureStage;
     }
+
+    @Override
+    public ProcessorType getProcessorType() {
+        return ProcessorType.PROCESSOR_TYPE_REQUEST_UPDATE_ONLY;
+    }
+
+    @Override
+    public RequestUpdateProcessorImpl getRequestUpdatePreviewProcessor() {
+        return RequestUpdateProcessorImpls.noUpdateProcessor();
+    }
 }
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
index 6e11eca..9402782 100644
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
@@ -19,10 +19,16 @@
 import android.hardware.camera2.CameraCharacteristics;
 
 /**
- * Provides abstract methods that the OEM needs to implement to enable extensions in the view
- * finder.
+ * Provides abstract methods that the OEM needs to implement to enable extensions in the preview.
  */
 public interface PreviewExtenderImpl {
+    /** The different types of the preview processing. */
+    enum ProcessorType {
+        /** Processing which only updates the {@link CaptureStageImpl}. */
+        PROCESSOR_TYPE_REQUEST_UPDATE_ONLY,
+        PROCESSOR_TYPE_NONE
+    }
+
     /**
      * Indicates whether the extension is supported on the device.
      *
@@ -40,6 +46,21 @@
      */
     void enableExtension(String cameraId, CameraCharacteristics cameraCharacteristics);
 
-    /** The set of parameters required to produce the effect on images. */
+    /**
+     * The set of parameters required to produce the effect on the preview stream.
+     *
+     * <p> This will be the initial set of parameters used for the preview
+     * {@link android.hardware.camera2.CaptureRequest}. Once the {@link RequestUpdateProcessorImpl}
+     * from {@link #getRequestUpdatePreviewProcessor()} has been called, this should be updated to
+     * reflect the new {@link CaptureStageImpl}. If the processing step returns a {@code null},
+     * meaning the required parameters has not changed, then calling this will return the previous
+     * non-null value.
+     */
     CaptureStageImpl getCaptureStage();
+
+    /** The type of preview processing to use. */
+    ProcessorType getProcessorType();
+
+    /** Returns a processor which only updates the {@link CaptureStageImpl}. */
+    RequestUpdateProcessorImpl getRequestUpdatePreviewProcessor();
 }
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/RequestUpdateProcessorImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/RequestUpdateProcessorImpl.java
new file mode 100644
index 0000000..0c8d49b
--- /dev/null
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/RequestUpdateProcessorImpl.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 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.camera.extensions.impl;
+
+import android.hardware.camera2.TotalCaptureResult;
+
+/**
+ * The interface for processing a {@link TotalCaptureResult} only.
+ */
+public interface RequestUpdateProcessorImpl {
+    /**
+     * Process the {@link TotalCaptureResult} to update the {@link CaptureStageImpl}
+     *
+     * @param result The metadata associated with the image. Can be null if the image and meta have
+     *               not been synced.
+     * @return The updated parameters used for the repeating requests. If this is {@code null} then
+     * the previous parameters will be used.
+     */
+    CaptureStageImpl process(TotalCaptureResult result);
+}
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/RequestUpdateProcessorImpls.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/RequestUpdateProcessorImpls.java
new file mode 100644
index 0000000..86382f5
--- /dev/null
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/RequestUpdateProcessorImpls.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 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.camera.extensions.impl;
+
+import android.hardware.camera2.TotalCaptureResult;
+
+class RequestUpdateProcessorImpls {
+    private static final RequestUpdateProcessorImpl sNoUpdateProcessor =
+            new RequestUpdateProcessorImpl() {
+                @Override
+                public CaptureStageImpl process(TotalCaptureResult result) {
+                    return null;
+                }
+            };
+
+    static RequestUpdateProcessorImpl noUpdateProcessor() {
+        return sNoUpdateProcessor;
+    }
+
+    private RequestUpdateProcessorImpls() {
+    }
+}
diff --git a/camera/testing/src/main/java/androidx/camera/testing/fakes/FakeCamera.java b/camera/testing/src/main/java/androidx/camera/testing/fakes/FakeCamera.java
index 5e77531..98c1feb 100644
--- a/camera/testing/src/main/java/androidx/camera/testing/fakes/FakeCamera.java
+++ b/camera/testing/src/main/java/androidx/camera/testing/fakes/FakeCamera.java
@@ -16,65 +16,171 @@
 
 package androidx.camera.testing.fakes;
 
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Surface;
+
+import androidx.annotation.Nullable;
 import androidx.camera.core.BaseCamera;
 import androidx.camera.core.CameraControl;
 import androidx.camera.core.CameraInfo;
 import androidx.camera.core.CaptureConfig;
+import androidx.camera.core.DeferrableSurface;
+import androidx.camera.core.DeferrableSurfaces;
 import androidx.camera.core.SessionConfig;
 import androidx.camera.core.UseCase;
+import androidx.camera.core.UseCaseAttachState;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
-/** A fake camera which will not produce any data. */
+/**
+ * A fake camera which will not produce any data, but provides a valid BaseCamera implementation.
+ */
 public class FakeCamera implements BaseCamera {
+    private static final String TAG = "FakeCamera";
+    private static final String DEFAULT_CAMERA_ID = "0";
     private final CameraControl mCameraControl;
-
     private final CameraInfo mCameraInfo;
+    private String mCameraId;
+    private UseCaseAttachState mUseCaseAttachState;
+    private State mState = State.INITIALIZED;
+
+    @Nullable
+    private SessionConfig mSessionConfig;
+    @Nullable
+    private SessionConfig mCameraControlSessionConfig;
+
+    private List<DeferrableSurface> mConfiguredDeferrableSurfaces = Collections.emptyList();
 
     public FakeCamera() {
-        this(new FakeCameraInfo(), CameraControl.DEFAULT_EMPTY_INSTANCE);
+        this(DEFAULT_CAMERA_ID, new FakeCameraInfo(), /*cameraControl=*/null);
     }
 
-    public FakeCamera(CameraInfo cameraInfo, CameraControl cameraControl) {
+    public FakeCamera(String cameraId) {
+        this(cameraId, new FakeCameraInfo(), /*cameraControl=*/null);
+    }
+
+    public FakeCamera(CameraInfo cameraInfo, @Nullable CameraControl cameraControl) {
+        this(DEFAULT_CAMERA_ID, cameraInfo, cameraControl);
+    }
+
+    public FakeCamera(String cameraId,
+            CameraInfo cameraInfo,
+            @Nullable CameraControl cameraControl) {
         mCameraInfo = cameraInfo;
-        mCameraControl = cameraControl;
+        mCameraId = cameraId;
+        mUseCaseAttachState = new UseCaseAttachState(cameraId);
+        mCameraControl = cameraControl == null ? new FakeCameraControl(this) : cameraControl;
     }
 
     @Override
     public void open() {
+        checkNotReleased();
+        if (mState == State.INITIALIZED) {
+            mState = State.OPENED;
+        }
     }
 
     @Override
     public void close() {
+        checkNotReleased();
+        if (mState == State.OPENED) {
+            mSessionConfig = null;
+            reconfigure();
+            mState = State.INITIALIZED;
+        }
     }
 
     @Override
     public void release() {
+        checkNotReleased();
+        if (mState == State.OPENED) {
+            close();
+        }
+
+        mState = State.RELEASED;
     }
 
     @Override
-    public void addOnlineUseCase(Collection<UseCase> useCases) {
+    public void onUseCaseActive(final UseCase useCase) {
+        Log.d(TAG, "Use case " + useCase + " ACTIVE for camera " + mCameraId);
+
+        mUseCaseAttachState.setUseCaseActive(useCase);
+        updateCaptureSessionConfig();
+    }
+
+    /** Removes the use case from a state of issuing capture requests. */
+    @Override
+    public void onUseCaseInactive(final UseCase useCase) {
+        Log.d(TAG, "Use case " + useCase + " INACTIVE for camera " + mCameraId);
+
+        mUseCaseAttachState.setUseCaseInactive(useCase);
+        updateCaptureSessionConfig();
+    }
+
+    /** Updates the capture requests based on the latest settings. */
+    @Override
+    public void onUseCaseUpdated(final UseCase useCase) {
+        Log.d(TAG, "Use case " + useCase + " UPDATED for camera " + mCameraId);
+
+        mUseCaseAttachState.updateUseCase(useCase);
+        updateCaptureSessionConfig();
     }
 
     @Override
-    public void removeOnlineUseCase(Collection<UseCase> useCases) {
+    public void onUseCaseReset(final UseCase useCase) {
+        Log.d(TAG, "Use case " + useCase + " RESET for camera " + mCameraId);
+
+        mUseCaseAttachState.updateUseCase(useCase);
+        updateCaptureSessionConfig();
+        openCaptureSession();
     }
 
+    /**
+     * Sets the use case to be in the state where the capture session will be configured to handle
+     * capture requests from the use case.
+     */
     @Override
-    public void onUseCaseActive(UseCase useCase) {
+    public void addOnlineUseCase(final Collection<UseCase> useCases) {
+        if (useCases.isEmpty()) {
+            return;
+        }
+
+        Log.d(TAG, "Use cases " + useCases + " ONLINE for camera " + mCameraId);
+        for (UseCase useCase : useCases) {
+            mUseCaseAttachState.setUseCaseOnline(useCase);
+        }
+
+        open();
+        updateCaptureSessionConfig();
+        openCaptureSession();
     }
 
+    /**
+     * Removes the use case to be in the state where the capture session will be configured to
+     * handle capture requests from the use case.
+     */
     @Override
-    public void onUseCaseInactive(UseCase useCase) {
-    }
+    public void removeOnlineUseCase(final Collection<UseCase> useCases) {
+        if (useCases.isEmpty()) {
+            return;
+        }
 
-    @Override
-    public void onUseCaseUpdated(UseCase useCase) {
-    }
+        Log.d(TAG, "Use cases " + useCases + " OFFLINE for camera " + mCameraId);
+        for (UseCase useCase : useCases) {
+            mUseCaseAttachState.setUseCaseOffline(useCase);
+        }
 
-    @Override
-    public void onUseCaseReset(UseCase useCase) {
+        if (mUseCaseAttachState.getOnlineUseCases().isEmpty()) {
+            close();
+            return;
+        }
+
+        openCaptureSession();
+        updateCaptureSessionConfig();
     }
 
     // Returns fixed CameraControl instance in order to verify the instance is correctly attached.
@@ -90,9 +196,104 @@
 
     @Override
     public void onCameraControlUpdateSessionConfig(SessionConfig sessionConfig) {
+        mCameraControlSessionConfig = sessionConfig;
+        updateCaptureSessionConfig();
     }
 
     @Override
     public void onCameraControlCaptureRequests(List<CaptureConfig> captureConfigs) {
+        Log.d(TAG, "Capture requests submitted:\n    " + TextUtils.join("\n    ", captureConfigs));
     }
+
+    private void checkNotReleased() {
+        if (mState == State.RELEASED) {
+            throw new IllegalStateException("Camera has been released.");
+        }
+    }
+
+    private void openCaptureSession() {
+        SessionConfig.ValidatingBuilder validatingBuilder;
+        validatingBuilder = mUseCaseAttachState.getOnlineBuilder();
+        if (!validatingBuilder.isValid()) {
+            Log.d(TAG, "Unable to create capture session due to conflicting configurations");
+            return;
+        }
+
+        if (mState != State.OPENED) {
+            Log.d(TAG, "CameraDevice is not opened");
+            return;
+        }
+
+        mSessionConfig = validatingBuilder.build();
+        reconfigure();
+    }
+
+    private void updateCaptureSessionConfig() {
+        SessionConfig.ValidatingBuilder validatingBuilder;
+        validatingBuilder = mUseCaseAttachState.getActiveAndOnlineBuilder();
+
+        if (validatingBuilder.isValid()) {
+            // Apply CameraControl's SessionConfig to let CameraControl be able to control
+            // Repeating Request and process results.
+            validatingBuilder.add(mCameraControlSessionConfig);
+
+            mSessionConfig = validatingBuilder.build();
+        }
+    }
+
+    private void reconfigure() {
+        notifySurfaceDetached();
+
+        if (mSessionConfig != null) {
+            List<DeferrableSurface> surfaces = mSessionConfig.getSurfaces();
+
+            // Before creating capture session, some surfaces may need to refresh.
+            DeferrableSurfaces.refresh(surfaces);
+
+            mConfiguredDeferrableSurfaces = new ArrayList<>(surfaces);
+
+            List<Surface> configuredSurfaces = new ArrayList<>(
+                    DeferrableSurfaces.surfaceSet(
+                            mConfiguredDeferrableSurfaces));
+            if (configuredSurfaces.isEmpty()) {
+                Log.e(TAG, "Unable to open capture session with no surfaces. ");
+                return;
+            }
+        }
+
+        notifySurfaceAttached();
+    }
+
+    // Notify the surface is attached to a new capture session.
+    private void notifySurfaceAttached() {
+        for (DeferrableSurface deferrableSurface : mConfiguredDeferrableSurfaces) {
+            deferrableSurface.notifySurfaceAttached();
+        }
+    }
+
+    // Notify the surface is detached from current capture session.
+    private void notifySurfaceDetached() {
+        for (DeferrableSurface deferredSurface : mConfiguredDeferrableSurfaces) {
+            deferredSurface.notifySurfaceDetached();
+        }
+        // Clears the mConfiguredDeferrableSurfaces to prevent from duplicate
+        // notifySurfaceDetached calls.
+        mConfiguredDeferrableSurfaces.clear();
+    }
+
+    enum State {
+        /**
+         * Stable state once the camera has been constructed.
+         */
+        INITIALIZED,
+        /**
+         * A stable state where the camera has been opened.
+         */
+        OPENED,
+        /**
+         * A stable state where the camera has been permanently closed.
+         */
+        RELEASED
+    }
+
 }
diff --git a/camera/testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java b/camera/testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java
new file mode 100644
index 0000000..e4c1da6
--- /dev/null
+++ b/camera/testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java
@@ -0,0 +1,118 @@
+/*
+ * 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.camera.testing.fakes;
+
+import android.graphics.Rect;
+import android.os.Handler;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.camera.core.CameraControl;
+import androidx.camera.core.CaptureConfig;
+import androidx.camera.core.FlashMode;
+import androidx.camera.core.OnFocusListener;
+import androidx.camera.core.SessionConfig;
+
+import java.util.List;
+
+/**
+ * A fake implementation for the CameraControl interface.
+ */
+public final class FakeCameraControl implements CameraControl {
+    private static final String TAG = "FakeCameraControl";
+    private final ControlUpdateListener mControlUpdateListener;
+    private final SessionConfig.Builder mSessionConfigBuilder = new SessionConfig.Builder();
+    private boolean mIsTorchOn = false;
+    private FlashMode mFlashMode = FlashMode.OFF;
+
+    public FakeCameraControl(ControlUpdateListener controlUpdateListener) {
+        mControlUpdateListener = controlUpdateListener;
+        updateSessionConfig();
+    }
+
+    @Override
+    public void setCropRegion(final Rect crop) {
+        Log.d(TAG, "setCropRegion(" + crop + ")");
+    }
+
+    @Override
+    public void focus(
+            final Rect focus,
+            final Rect metering,
+            @Nullable final OnFocusListener listener,
+            @Nullable final Handler listenerHandler) {
+        Log.d(TAG, "focus(\n    " + focus + ",\n    " + metering + ")");
+    }
+
+    @Override
+    public void focus(Rect focus, Rect metering) {
+        focus(focus, metering, null, null);
+    }
+
+    @Override
+    public FlashMode getFlashMode() {
+        return mFlashMode;
+    }
+
+    @Override
+    public void setFlashMode(FlashMode flashMode) {
+        mFlashMode = flashMode;
+        Log.d(TAG, "setFlashMode(" + mFlashMode + ")");
+    }
+
+    @Override
+    public void enableTorch(boolean torch) {
+        mIsTorchOn = torch;
+        Log.d(TAG, "enableTorch(" + torch + ")");
+    }
+
+    @Override
+    public boolean isTorchOn() {
+        return mIsTorchOn;
+    }
+
+    @Override
+    public boolean isFocusLocked() {
+        return false;
+    }
+
+    @Override
+    public void triggerAf() {
+        Log.d(TAG, "triggerAf()");
+    }
+
+    @Override
+    public void triggerAePrecapture() {
+        Log.d(TAG, "triggerAePrecapture()");
+    }
+
+    @Override
+    public void cancelAfAeTrigger(final boolean cancelAfTrigger,
+            final boolean cancelAePrecaptureTrigger) {
+        Log.d(TAG, "cancelAfAeTrigger(" + cancelAfTrigger + ", "
+                + cancelAePrecaptureTrigger + ")");
+    }
+
+    @Override
+    public void submitCaptureRequests(List<CaptureConfig> captureConfigs) {
+        mControlUpdateListener.onCameraControlCaptureRequests(captureConfigs);
+    }
+
+    private void updateSessionConfig() {
+        mControlUpdateListener.onCameraControlUpdateSessionConfig(mSessionConfigBuilder.build());
+    }
+}
diff --git a/camera/testing/src/main/java/androidx/camera/testing/fakes/FakeCameraDeviceSurfaceManager.java b/camera/testing/src/main/java/androidx/camera/testing/fakes/FakeCameraDeviceSurfaceManager.java
index 36a96be..8d59757 100644
--- a/camera/testing/src/main/java/androidx/camera/testing/fakes/FakeCameraDeviceSurfaceManager.java
+++ b/camera/testing/src/main/java/androidx/camera/testing/fakes/FakeCameraDeviceSurfaceManager.java
@@ -28,11 +28,27 @@
 import java.util.Map;
 
 /** A CameraDeviceSurfaceManager which has no supported SurfaceConfigs. */
-public class FakeCameraDeviceSurfaceManager implements CameraDeviceSurfaceManager {
+public final class FakeCameraDeviceSurfaceManager implements CameraDeviceSurfaceManager {
 
-    private static final Size MAX_OUTPUT_SIZE = new Size(0, 0);
+    private static final Size MAX_OUTPUT_SIZE = new Size(4032, 3024); // 12.2 MP
     private static final Size PREVIEW_SIZE = new Size(1920, 1080);
 
+    private Map<String, Map<Class<? extends UseCase>, Size>> mDefinedResolutions = new HashMap<>();
+
+    /**
+     * Sets the given suggested resolutions for the specified camera Id and use case type.
+     */
+    public void setSuggestedResolution(String cameraId, Class<? extends UseCase> type, Size size) {
+        Map<Class<? extends UseCase>, Size> useCaseTypeToSizeMap =
+                mDefinedResolutions.get(cameraId);
+        if (useCaseTypeToSizeMap == null) {
+            useCaseTypeToSizeMap = new HashMap<>();
+            mDefinedResolutions.put(cameraId, useCaseTypeToSizeMap);
+        }
+
+        useCaseTypeToSizeMap.put(type, size);
+    }
+
     @Override
     public boolean checkSupported(String cameraId, List<SurfaceConfig> surfaceConfigList) {
         return false;
@@ -54,7 +70,17 @@
             String cameraId, List<UseCase> originalUseCases, List<UseCase> newUseCases) {
         Map<UseCase, Size> suggestedSizes = new HashMap<>();
         for (UseCase useCase : newUseCases) {
-            suggestedSizes.put(useCase, MAX_OUTPUT_SIZE);
+            Size resolution = MAX_OUTPUT_SIZE;
+            Map<Class<? extends UseCase>, Size> definedResolutions =
+                    mDefinedResolutions.get(cameraId);
+            if (definedResolutions != null) {
+                Size definedResolution = definedResolutions.get(useCase.getClass());
+                if (definedResolution != null) {
+                    resolution = definedResolution;
+                }
+            }
+
+            suggestedSizes.put(useCase, resolution);
         }
 
         return suggestedSizes;
diff --git a/camera/testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfo.java b/camera/testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfo.java
index abba36a..091ba75 100644
--- a/camera/testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfo.java
+++ b/camera/testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfo.java
@@ -20,6 +20,7 @@
 
 import androidx.annotation.Nullable;
 import androidx.camera.core.CameraInfo;
+import androidx.camera.core.CameraOrientationUtil;
 import androidx.camera.core.CameraX.LensFacing;
 import androidx.camera.core.ImageOutputConfig.RotationValue;
 
@@ -28,7 +29,7 @@
  *
  * <p>This camera info can be constructed with fake values.
  */
-public class FakeCameraInfo implements CameraInfo {
+public final class FakeCameraInfo implements CameraInfo {
 
     private final int mSensorRotation;
     private final LensFacing mLensFacing;
@@ -50,7 +51,16 @@
 
     @Override
     public int getSensorRotationDegrees(@RotationValue int relativeRotation) {
-        return mSensorRotation;
+        int relativeRotationDegrees =
+                CameraOrientationUtil.surfaceRotationToDegrees(relativeRotation);
+        // Currently this assumes that a back-facing camera is always opposite to the screen.
+        // This may not be the case for all devices, so in the future we may need to handle that
+        // scenario.
+        boolean isOppositeFacingScreen = LensFacing.BACK.equals(getLensFacing());
+        return CameraOrientationUtil.getRelativeImageRotation(
+                relativeRotationDegrees,
+                mSensorRotation,
+                isOppositeFacingScreen);
     }
 
     @Override
diff --git a/camera/view/build.gradle b/camera/view/build.gradle
index f5e787a4..2a1caa1 100644
--- a/camera/view/build.gradle
+++ b/camera/view/build.gradle
@@ -36,7 +36,7 @@
 }
 androidx {
     name = "Jetpack Camera View Library"
-    publish = true
+    publish = false
     mavenVersion = LibraryVersions.CAMERA
     mavenGroup = LibraryGroups.CAMERA
     inceptionYear = "2019"
diff --git a/car/core/api/1.0.0-alpha8.txt b/car/core/api/1.0.0-alpha8.txt
index 6ee7690..90326b8 100644
--- a/car/core/api/1.0.0-alpha8.txt
+++ b/car/core/api/1.0.0-alpha8.txt
@@ -316,6 +316,24 @@
     method public void setTitleTextAppearance(@StyleRes int);
   }
 
+  public final class CheckBoxListItem extends androidx.car.widget.CompoundButtonListItem<androidx.car.widget.CheckBoxListItem.ViewHolder> {
+    ctor public CheckBoxListItem(android.content.Context);
+    method public static androidx.car.widget.CheckBoxListItem.ViewHolder createViewHolder(android.view.View);
+    method public int getViewType();
+    method public boolean isCompoundButtonPositionEnd();
+  }
+
+  public static final class CheckBoxListItem.ViewHolder extends androidx.car.widget.CompoundButtonListItem.ViewHolder {
+    ctor public CheckBoxListItem.ViewHolder(android.view.View);
+    method public android.widget.TextView getBody();
+    method public android.widget.CompoundButton getCompoundButton();
+    method public android.view.View getCompoundButtonDivider();
+    method public android.view.ViewGroup getContainerLayout();
+    method public android.widget.ImageView getPrimaryIcon();
+    method public android.widget.TextView getTitle();
+    method public void onUxRestrictionsChanged(androidx.car.uxrestrictions.CarUxRestrictions);
+  }
+
   public final class ColumnCardView extends androidx.cardview.widget.CardView {
     ctor public ColumnCardView(android.content.Context!);
     ctor public ColumnCardView(android.content.Context!, android.util.AttributeSet!);
@@ -325,6 +343,36 @@
     method public void setColumnSpan(int);
   }
 
+  public abstract class CompoundButtonListItem<VH extends androidx.car.widget.CompoundButtonListItem.ViewHolder> extends androidx.car.widget.ListItem<VH> {
+    ctor public CompoundButtonListItem(android.content.Context);
+    method public abstract boolean isCompoundButtonPositionEnd();
+    method public void onBind(VH);
+    method @CallSuper protected void resolveDirtyState();
+    method public void setBody(CharSequence?);
+    method public void setChecked(boolean);
+    method public void setClickable(boolean);
+    method public void setEnabled(boolean);
+    method public void setOnCheckedChangeListener(android.widget.CompoundButton.OnCheckedChangeListener?);
+    method public void setPrimaryActionEmptyIcon();
+    method public void setPrimaryActionIcon(android.graphics.drawable.Drawable, int);
+    method public void setPrimaryActionIcon(@DrawableRes int, int);
+    method public void setPrimaryActionNoIcon();
+    method public void setShowCompoundButtonDivider(boolean);
+    method public void setTitle(CharSequence?);
+    field public static final int PRIMARY_ACTION_ICON_SIZE_LARGE = 2; // 0x2
+    field public static final int PRIMARY_ACTION_ICON_SIZE_MEDIUM = 1; // 0x1
+    field public static final int PRIMARY_ACTION_ICON_SIZE_SMALL = 0; // 0x0
+  }
+
+  public abstract static class CompoundButtonListItem.ViewHolder extends androidx.car.widget.ListItem.ViewHolder {
+    ctor public CompoundButtonListItem.ViewHolder(android.view.View);
+    method public abstract android.widget.TextView getBody();
+    method public abstract android.widget.CompoundButton getCompoundButton();
+    method public abstract android.view.View getCompoundButtonDivider();
+    method public abstract android.widget.ImageView getPrimaryIcon();
+    method public abstract android.widget.TextView getTitle();
+  }
+
   public abstract class ListItem<VH extends androidx.car.widget.ListItem.ViewHolder> {
     ctor public ListItem();
     method public final void addViewBinder(androidx.car.widget.ListItem.ViewBinder<VH>!);
@@ -496,38 +544,22 @@
     field public static final int PAGE_UP = 0; // 0x0
   }
 
-  public class RadioButtonListItem extends androidx.car.widget.ListItem<androidx.car.widget.RadioButtonListItem.ViewHolder> {
+  public final class RadioButtonListItem extends androidx.car.widget.CompoundButtonListItem<androidx.car.widget.RadioButtonListItem.ViewHolder> {
     ctor public RadioButtonListItem(android.content.Context);
     method public static androidx.car.widget.RadioButtonListItem.ViewHolder createViewHolder(android.view.View);
-    method protected android.content.Context getContext();
     method public int getViewType();
-    method public boolean isChecked();
-    method protected void onBind(androidx.car.widget.RadioButtonListItem.ViewHolder!);
-    method protected void resolveDirtyState();
-    method public void setBody(CharSequence?);
-    method public void setChecked(boolean);
-    method public void setEnabled(boolean);
-    method public void setOnCheckedChangeListener(android.widget.CompoundButton.OnCheckedChangeListener);
-    method public void setPrimaryActionIcon(android.graphics.drawable.Drawable, int);
-    method public void setPrimaryActionIcon(@DrawableRes int, int);
-    method public void setPrimaryActionNoIcon();
-    method public void setShowRadioButtonDivider(boolean);
-    method public void setTextStartMargin(@DimenRes int);
-    method public void setTitle(CharSequence?);
-    field public static final int PRIMARY_ACTION_ICON_SIZE_LARGE = 2; // 0x2
-    field public static final int PRIMARY_ACTION_ICON_SIZE_MEDIUM = 1; // 0x1
-    field public static final int PRIMARY_ACTION_ICON_SIZE_SMALL = 0; // 0x0
+    method public boolean isCompoundButtonPositionEnd();
   }
 
-  public static final class RadioButtonListItem.ViewHolder extends androidx.car.widget.ListItem.ViewHolder {
+  public static final class RadioButtonListItem.ViewHolder extends androidx.car.widget.CompoundButtonListItem.ViewHolder {
     ctor public RadioButtonListItem.ViewHolder(android.view.View);
     method public android.widget.TextView getBody();
+    method public android.widget.CompoundButton getCompoundButton();
+    method public android.view.View getCompoundButtonDivider();
     method public android.view.ViewGroup getContainerLayout();
     method public android.widget.ImageView getPrimaryIcon();
-    method public android.widget.RadioButton getRadioButton();
-    method public android.view.View getRadioButtonDivider();
     method public android.widget.TextView getTitle();
-    method public void onUxRestrictionsChanged(androidx.car.uxrestrictions.CarUxRestrictions!);
+    method public void onUxRestrictionsChanged(androidx.car.uxrestrictions.CarUxRestrictions);
   }
 
   public class SeekbarListItem extends androidx.car.widget.ListItem<androidx.car.widget.SeekbarListItem.ViewHolder> {
@@ -588,38 +620,22 @@
     method public void onUxRestrictionsChanged(androidx.car.uxrestrictions.CarUxRestrictions!);
   }
 
-  public class SwitchListItem extends androidx.car.widget.ListItem<androidx.car.widget.SwitchListItem.ViewHolder> {
+  public final class SwitchListItem extends androidx.car.widget.CompoundButtonListItem<androidx.car.widget.SwitchListItem.ViewHolder> {
     ctor public SwitchListItem(android.content.Context);
-    method public static androidx.car.widget.SwitchListItem.ViewHolder createViewHolder(android.view.View!);
-    method protected final android.content.Context getContext();
+    method public static androidx.car.widget.SwitchListItem.ViewHolder createViewHolder(android.view.View);
     method public int getViewType();
-    method public void onBind(androidx.car.widget.SwitchListItem.ViewHolder!);
-    method @CallSuper protected void resolveDirtyState();
-    method public void setBody(CharSequence?);
-    method public void setChecked(boolean);
-    method public void setClickable(boolean);
-    method public void setEnabled(boolean);
-    method public void setPrimaryActionEmptyIcon();
-    method public void setPrimaryActionIcon(android.graphics.drawable.Drawable, int);
-    method public void setPrimaryActionIcon(@DrawableRes int, int);
-    method public void setPrimaryActionNoIcon();
-    method public void setShowSwitchDivider(boolean);
-    method public void setSwitchOnCheckedChangeListener(android.widget.CompoundButton.OnCheckedChangeListener?);
-    method @Deprecated public void setSwitchState(boolean);
-    method public void setTitle(CharSequence?);
-    field public static final int PRIMARY_ACTION_ICON_SIZE_LARGE = 2; // 0x2
-    field public static final int PRIMARY_ACTION_ICON_SIZE_MEDIUM = 1; // 0x1
-    field public static final int PRIMARY_ACTION_ICON_SIZE_SMALL = 0; // 0x0
+    method public boolean isCompoundButtonPositionEnd();
   }
 
-  public static final class SwitchListItem.ViewHolder extends androidx.car.widget.ListItem.ViewHolder {
+  public static final class SwitchListItem.ViewHolder extends androidx.car.widget.CompoundButtonListItem.ViewHolder {
     ctor public SwitchListItem.ViewHolder(android.view.View);
     method public android.widget.TextView getBody();
+    method public android.widget.CompoundButton getCompoundButton();
+    method public android.view.View getCompoundButtonDivider();
+    method public android.view.ViewGroup getContainerLayout();
     method public android.widget.ImageView getPrimaryIcon();
-    method public android.widget.Switch getSwitch();
-    method public android.view.View getSwitchDivider();
     method public android.widget.TextView getTitle();
-    method public void onUxRestrictionsChanged(androidx.car.uxrestrictions.CarUxRestrictions!);
+    method public void onUxRestrictionsChanged(androidx.car.uxrestrictions.CarUxRestrictions);
   }
 
   public class TextListItem extends androidx.car.widget.ListItem<androidx.car.widget.TextListItem.ViewHolder> {
diff --git a/car/core/api/current.txt b/car/core/api/current.txt
index 6ee7690..90326b8 100644
--- a/car/core/api/current.txt
+++ b/car/core/api/current.txt
@@ -316,6 +316,24 @@
     method public void setTitleTextAppearance(@StyleRes int);
   }
 
+  public final class CheckBoxListItem extends androidx.car.widget.CompoundButtonListItem<androidx.car.widget.CheckBoxListItem.ViewHolder> {
+    ctor public CheckBoxListItem(android.content.Context);
+    method public static androidx.car.widget.CheckBoxListItem.ViewHolder createViewHolder(android.view.View);
+    method public int getViewType();
+    method public boolean isCompoundButtonPositionEnd();
+  }
+
+  public static final class CheckBoxListItem.ViewHolder extends androidx.car.widget.CompoundButtonListItem.ViewHolder {
+    ctor public CheckBoxListItem.ViewHolder(android.view.View);
+    method public android.widget.TextView getBody();
+    method public android.widget.CompoundButton getCompoundButton();
+    method public android.view.View getCompoundButtonDivider();
+    method public android.view.ViewGroup getContainerLayout();
+    method public android.widget.ImageView getPrimaryIcon();
+    method public android.widget.TextView getTitle();
+    method public void onUxRestrictionsChanged(androidx.car.uxrestrictions.CarUxRestrictions);
+  }
+
   public final class ColumnCardView extends androidx.cardview.widget.CardView {
     ctor public ColumnCardView(android.content.Context!);
     ctor public ColumnCardView(android.content.Context!, android.util.AttributeSet!);
@@ -325,6 +343,36 @@
     method public void setColumnSpan(int);
   }
 
+  public abstract class CompoundButtonListItem<VH extends androidx.car.widget.CompoundButtonListItem.ViewHolder> extends androidx.car.widget.ListItem<VH> {
+    ctor public CompoundButtonListItem(android.content.Context);
+    method public abstract boolean isCompoundButtonPositionEnd();
+    method public void onBind(VH);
+    method @CallSuper protected void resolveDirtyState();
+    method public void setBody(CharSequence?);
+    method public void setChecked(boolean);
+    method public void setClickable(boolean);
+    method public void setEnabled(boolean);
+    method public void setOnCheckedChangeListener(android.widget.CompoundButton.OnCheckedChangeListener?);
+    method public void setPrimaryActionEmptyIcon();
+    method public void setPrimaryActionIcon(android.graphics.drawable.Drawable, int);
+    method public void setPrimaryActionIcon(@DrawableRes int, int);
+    method public void setPrimaryActionNoIcon();
+    method public void setShowCompoundButtonDivider(boolean);
+    method public void setTitle(CharSequence?);
+    field public static final int PRIMARY_ACTION_ICON_SIZE_LARGE = 2; // 0x2
+    field public static final int PRIMARY_ACTION_ICON_SIZE_MEDIUM = 1; // 0x1
+    field public static final int PRIMARY_ACTION_ICON_SIZE_SMALL = 0; // 0x0
+  }
+
+  public abstract static class CompoundButtonListItem.ViewHolder extends androidx.car.widget.ListItem.ViewHolder {
+    ctor public CompoundButtonListItem.ViewHolder(android.view.View);
+    method public abstract android.widget.TextView getBody();
+    method public abstract android.widget.CompoundButton getCompoundButton();
+    method public abstract android.view.View getCompoundButtonDivider();
+    method public abstract android.widget.ImageView getPrimaryIcon();
+    method public abstract android.widget.TextView getTitle();
+  }
+
   public abstract class ListItem<VH extends androidx.car.widget.ListItem.ViewHolder> {
     ctor public ListItem();
     method public final void addViewBinder(androidx.car.widget.ListItem.ViewBinder<VH>!);
@@ -496,38 +544,22 @@
     field public static final int PAGE_UP = 0; // 0x0
   }
 
-  public class RadioButtonListItem extends androidx.car.widget.ListItem<androidx.car.widget.RadioButtonListItem.ViewHolder> {
+  public final class RadioButtonListItem extends androidx.car.widget.CompoundButtonListItem<androidx.car.widget.RadioButtonListItem.ViewHolder> {
     ctor public RadioButtonListItem(android.content.Context);
     method public static androidx.car.widget.RadioButtonListItem.ViewHolder createViewHolder(android.view.View);
-    method protected android.content.Context getContext();
     method public int getViewType();
-    method public boolean isChecked();
-    method protected void onBind(androidx.car.widget.RadioButtonListItem.ViewHolder!);
-    method protected void resolveDirtyState();
-    method public void setBody(CharSequence?);
-    method public void setChecked(boolean);
-    method public void setEnabled(boolean);
-    method public void setOnCheckedChangeListener(android.widget.CompoundButton.OnCheckedChangeListener);
-    method public void setPrimaryActionIcon(android.graphics.drawable.Drawable, int);
-    method public void setPrimaryActionIcon(@DrawableRes int, int);
-    method public void setPrimaryActionNoIcon();
-    method public void setShowRadioButtonDivider(boolean);
-    method public void setTextStartMargin(@DimenRes int);
-    method public void setTitle(CharSequence?);
-    field public static final int PRIMARY_ACTION_ICON_SIZE_LARGE = 2; // 0x2
-    field public static final int PRIMARY_ACTION_ICON_SIZE_MEDIUM = 1; // 0x1
-    field public static final int PRIMARY_ACTION_ICON_SIZE_SMALL = 0; // 0x0
+    method public boolean isCompoundButtonPositionEnd();
   }
 
-  public static final class RadioButtonListItem.ViewHolder extends androidx.car.widget.ListItem.ViewHolder {
+  public static final class RadioButtonListItem.ViewHolder extends androidx.car.widget.CompoundButtonListItem.ViewHolder {
     ctor public RadioButtonListItem.ViewHolder(android.view.View);
     method public android.widget.TextView getBody();
+    method public android.widget.CompoundButton getCompoundButton();
+    method public android.view.View getCompoundButtonDivider();
     method public android.view.ViewGroup getContainerLayout();
     method public android.widget.ImageView getPrimaryIcon();
-    method public android.widget.RadioButton getRadioButton();
-    method public android.view.View getRadioButtonDivider();
     method public android.widget.TextView getTitle();
-    method public void onUxRestrictionsChanged(androidx.car.uxrestrictions.CarUxRestrictions!);
+    method public void onUxRestrictionsChanged(androidx.car.uxrestrictions.CarUxRestrictions);
   }
 
   public class SeekbarListItem extends androidx.car.widget.ListItem<androidx.car.widget.SeekbarListItem.ViewHolder> {
@@ -588,38 +620,22 @@
     method public void onUxRestrictionsChanged(androidx.car.uxrestrictions.CarUxRestrictions!);
   }
 
-  public class SwitchListItem extends androidx.car.widget.ListItem<androidx.car.widget.SwitchListItem.ViewHolder> {
+  public final class SwitchListItem extends androidx.car.widget.CompoundButtonListItem<androidx.car.widget.SwitchListItem.ViewHolder> {
     ctor public SwitchListItem(android.content.Context);
-    method public static androidx.car.widget.SwitchListItem.ViewHolder createViewHolder(android.view.View!);
-    method protected final android.content.Context getContext();
+    method public static androidx.car.widget.SwitchListItem.ViewHolder createViewHolder(android.view.View);
     method public int getViewType();
-    method public void onBind(androidx.car.widget.SwitchListItem.ViewHolder!);
-    method @CallSuper protected void resolveDirtyState();
-    method public void setBody(CharSequence?);
-    method public void setChecked(boolean);
-    method public void setClickable(boolean);
-    method public void setEnabled(boolean);
-    method public void setPrimaryActionEmptyIcon();
-    method public void setPrimaryActionIcon(android.graphics.drawable.Drawable, int);
-    method public void setPrimaryActionIcon(@DrawableRes int, int);
-    method public void setPrimaryActionNoIcon();
-    method public void setShowSwitchDivider(boolean);
-    method public void setSwitchOnCheckedChangeListener(android.widget.CompoundButton.OnCheckedChangeListener?);
-    method @Deprecated public void setSwitchState(boolean);
-    method public void setTitle(CharSequence?);
-    field public static final int PRIMARY_ACTION_ICON_SIZE_LARGE = 2; // 0x2
-    field public static final int PRIMARY_ACTION_ICON_SIZE_MEDIUM = 1; // 0x1
-    field public static final int PRIMARY_ACTION_ICON_SIZE_SMALL = 0; // 0x0
+    method public boolean isCompoundButtonPositionEnd();
   }
 
-  public static final class SwitchListItem.ViewHolder extends androidx.car.widget.ListItem.ViewHolder {
+  public static final class SwitchListItem.ViewHolder extends androidx.car.widget.CompoundButtonListItem.ViewHolder {
     ctor public SwitchListItem.ViewHolder(android.view.View);
     method public android.widget.TextView getBody();
+    method public android.widget.CompoundButton getCompoundButton();
+    method public android.view.View getCompoundButtonDivider();
+    method public android.view.ViewGroup getContainerLayout();
     method public android.widget.ImageView getPrimaryIcon();
-    method public android.widget.Switch getSwitch();
-    method public android.view.View getSwitchDivider();
     method public android.widget.TextView getTitle();
-    method public void onUxRestrictionsChanged(androidx.car.uxrestrictions.CarUxRestrictions!);
+    method public void onUxRestrictionsChanged(androidx.car.uxrestrictions.CarUxRestrictions);
   }
 
   public class TextListItem extends androidx.car.widget.ListItem<androidx.car.widget.TextListItem.ViewHolder> {
diff --git a/car/core/api/restricted_1.0.0-alpha8.txt b/car/core/api/restricted_1.0.0-alpha8.txt
index b88575a..d79bca5 100644
--- a/car/core/api/restricted_1.0.0-alpha8.txt
+++ b/car/core/api/restricted_1.0.0-alpha8.txt
@@ -75,6 +75,7 @@
 package androidx.car.widget {
 
 
+
 }
 
 package androidx.car.widget.itemdecorators {
diff --git a/car/core/api/restricted_current.txt b/car/core/api/restricted_current.txt
index b88575a..d79bca5 100644
--- a/car/core/api/restricted_current.txt
+++ b/car/core/api/restricted_current.txt
@@ -75,6 +75,7 @@
 package androidx.car.widget {
 
 
+
 }
 
 package androidx.car.widget.itemdecorators {
diff --git a/car/core/res/layout/car_list_item_check_box_content.xml b/car/core/res/layout/car_list_item_check_box_content.xml
new file mode 100644
index 0000000..7b1d8c6
--- /dev/null
+++ b/car/core/res/layout/car_list_item_check_box_content.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:foreground="@drawable/car_card_ripple_background"
+    android:minHeight="@dimen/car_single_line_list_item_height">
+
+    <!-- Primary Action. -->
+    <ImageView
+        android:id="@+id/primary_icon"
+        android:layout_width="@dimen/car_single_line_list_item_height"
+        android:layout_height="@dimen/car_single_line_list_item_height"
+        android:tint="?attr/listItemPrimaryIconTint"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <!-- Text. -->
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:ellipsize="end"
+        android:singleLine="true"
+        android:textAppearance="?attr/listItemTitleTextAppearance"
+        app:layout_constraintBottom_toTopOf="@+id/body"
+        app:layout_constraintEnd_toStartOf="@+id/barrier"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_chainStyle="packed" />
+
+    <TextView
+        android:id="@+id/body"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:textAppearance="?attr/listItemBodyTextAppearance"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/barrier"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/title" />
+
+    <!-- A barrier between the text and checkbox. -->
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/barrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierAllowsGoneWidgets="false"
+        app:barrierDirection="start"
+        app:constraint_referenced_ids="checkbox_divider,checkbox_widget" />
+
+    <!-- Guideline that the checkbox is centered upon. -->
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/supplemental_actions_guideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_percent="0.5" />
+
+    <!-- Checkbox with divider. -->
+    <View
+        android:id="@+id/checkbox_divider"
+        style="@style/CarListVerticalDivider"
+        android:layout_marginEnd="@dimen/car_padding_4"
+        android:background="?attr/listItemActionDividerColor"
+        app:layout_constraintBottom_toBottomOf="@+id/supplemental_actions_guideline"
+        app:layout_constraintEnd_toStartOf="@+id/checkbox_widget"
+        app:layout_constraintTop_toTopOf="@+id/supplemental_actions_guideline" />
+
+    <CheckBox
+        android:id="@+id/checkbox_widget"
+        style="?attr/checkboxStyle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/car_keyline_1"
+        app:layout_constraintBottom_toBottomOf="@+id/supplemental_actions_guideline"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@+id/supplemental_actions_guideline" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/car/core/res/layout/car_list_item_radio_content.xml b/car/core/res/layout/car_list_item_radio_content.xml
index df30fcb..dfb8a15 100644
--- a/car/core/res/layout/car_list_item_radio_content.xml
+++ b/car/core/res/layout/car_list_item_radio_content.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2018 The Android Open Source Project
+  ~ 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.
@@ -14,78 +14,85 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<!-- This layout should only be used by class RadioButtonListItem, as it requires layout params
-     being set programmatically depending on item data/view configuration. -->
 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/container"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:minHeight="@dimen/car_single_line_list_item_height"
-    android:paddingEnd="@dimen/car_keyline_1">
+    android:foreground="@drawable/car_card_ripple_background"
+    android:minHeight="@dimen/car_single_line_list_item_height">
 
-    <!-- Radio button. -->
+    <!-- Radio button with divider. -->
     <RadioButton
-        android:id="@+id/radio_button"
-        android:layout_width="@dimen/car_primary_icon_size"
-        android:layout_height="@dimen/car_primary_icon_size"
+        android:id="@+id/radiobutton_widget"
+        style="?attr/radioButtonStyle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
         android:layout_marginStart="@dimen/car_keyline_1"
-        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintBottom_toBottomOf="@+id/supplemental_actions_guideline"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
+        app:layout_constraintTop_toTopOf="@+id/supplemental_actions_guideline" />
 
     <View
-        android:id="@+id/radio_button_divider"
+        android:id="@+id/radiobutton_divider"
         style="@style/CarListVerticalDivider"
+        android:layout_marginEnd="@dimen/car_padding_4"
         android:background="?attr/listItemActionDividerColor"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintStart_toEndOf="@+id/radio_button"
-        app:layout_constraintTop_toTopOf="parent" />
+        app:layout_constraintBottom_toBottomOf="@+id/supplemental_actions_guideline"
+        app:layout_constraintStart_toEndOf="@+id/radiobutton_widget"
+        app:layout_constraintTop_toTopOf="@+id/supplemental_actions_guideline" />
 
     <!-- Text. -->
     <TextView
         android:id="@+id/title"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_marginEnd="@dimen/car_keyline_3"
-        android:layout_marginStart="@dimen/car_keyline_3"
-        android:layout_marginTop="@dimen/car_padding_2"
         android:ellipsize="end"
         android:singleLine="true"
         android:textAppearance="?attr/listItemTitleTextAppearance"
         app:layout_constraintBottom_toTopOf="@+id/body"
-        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/barrier"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_chainStyle="packed" />
 
     <TextView
         android:id="@+id/body"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_marginBottom="@dimen/car_padding_2"
-        android:layout_marginEnd="@dimen/car_keyline_3"
-        android:layout_marginStart="@dimen/car_keyline_3"
-        android:ellipsize="end"
-        android:singleLine="false"
         android:textAppearance="?attr/listItemBodyTextAppearance"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/barrier"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@+id/title" />
 
-    <!-- Primary Icon. -->
+    <!-- A barrier between the text and icon. -->
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/barrier"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierAllowsGoneWidgets="false"
+        app:barrierDirection="start"
+        app:constraint_referenced_ids="primary_icon" />
+
+    <!-- Guideline that the checkbox is centered upon. -->
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/supplemental_actions_guideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_percent="0.5" />
+
+    <!-- Primary Action. -->
     <ImageView
         android:id="@+id/primary_icon"
-        android:layout_width="@dimen/car_primary_icon_size"
-        android:layout_height="@dimen/car_primary_icon_size"
+        android:layout_width="@dimen/car_single_line_list_item_height"
+        android:layout_height="@dimen/car_single_line_list_item_height"
         android:layout_marginEnd="@dimen/car_keyline_1"
-        android:layout_marginStart="@dimen/car_padding_4"
         android:tint="?attr/listItemPrimaryIconTint"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintTop_toTopOf="parent" />
 
-
 </androidx.constraintlayout.widget.ConstraintLayout>
-
diff --git a/car/core/res/layout/car_list_item_switch_content.xml b/car/core/res/layout/car_list_item_switch_content.xml
index 5897a78..0f8edd6 100644
--- a/car/core/res/layout/car_list_item_switch_content.xml
+++ b/car/core/res/layout/car_list_item_switch_content.xml
@@ -17,13 +17,11 @@
 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    tools:ignore="MissingConstraints"
     android:id="@+id/container"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:minHeight="@dimen/car_single_line_list_item_height"
-    android:foreground="@drawable/car_card_ripple_background" >
+    android:foreground="@drawable/car_card_ripple_background"
+    android:minHeight="@dimen/car_single_line_list_item_height">
 
     <!-- Primary Action. -->
     <ImageView
@@ -31,41 +29,41 @@
         android:layout_width="@dimen/car_single_line_list_item_height"
         android:layout_height="@dimen/car_single_line_list_item_height"
         android:tint="?attr/listItemPrimaryIconTint"
-        app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintStart_toStartOf="parent"/>
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
 
     <!-- Text. -->
     <TextView
         android:id="@+id/title"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:singleLine="true"
         android:ellipsize="end"
+        android:singleLine="true"
         android:textAppearance="?attr/listItemTitleTextAppearance"
-        app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toTopOf="@+id/body"
-        app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toStartOf="@+id/barrier"
-        app:layout_constraintVertical_chainStyle="packed"/>
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_chainStyle="packed" />
 
     <TextView
         android:id="@+id/body"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:textAppearance="?attr/listItemBodyTextAppearance"
-        app:layout_constraintTop_toBottomOf="@+id/title"
         app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/barrier"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toStartOf="@+id/barrier"/>
+        app:layout_constraintTop_toBottomOf="@+id/title" />
 
     <!-- A barrier between the text and switch. -->
     <androidx.constraintlayout.widget.Barrier
         android:id="@+id/barrier"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        app:barrierDirection="start"
         app:barrierAllowsGoneWidgets="false"
+        app:barrierDirection="start"
         app:constraint_referenced_ids="switch_divider,switch_widget" />
 
     <!-- Guideline that the switch is centered upon. -->
@@ -79,20 +77,20 @@
     <!-- Switch with divider. -->
     <View
         android:id="@+id/switch_divider"
-        android:background="?attr/listItemActionDividerColor"
+        style="@style/CarListVerticalDivider"
         android:layout_marginEnd="@dimen/car_padding_4"
-        app:layout_constraintTop_toTopOf="@+id/supplemental_actions_guideline"
+        android:background="?attr/listItemActionDividerColor"
         app:layout_constraintBottom_toBottomOf="@+id/supplemental_actions_guideline"
         app:layout_constraintEnd_toStartOf="@+id/switch_widget"
-        style="@style/CarListVerticalDivider"/>
+        app:layout_constraintTop_toTopOf="@+id/supplemental_actions_guideline" />
 
     <Switch
         android:id="@+id/switch_widget"
+        style="?attr/switchStyle"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginEnd="@dimen/car_keyline_1"
-        app:layout_constraintTop_toTopOf="@+id/supplemental_actions_guideline"
         app:layout_constraintBottom_toBottomOf="@+id/supplemental_actions_guideline"
         app:layout_constraintEnd_toEndOf="parent"
-        style="?attr/switchStyle" />
+        app:layout_constraintTop_toTopOf="@+id/supplemental_actions_guideline" />
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/car/core/src/androidTest/java/androidx/car/widget/CheckBoxListItemTest.java b/car/core/src/androidTest/java/androidx/car/widget/CheckBoxListItemTest.java
new file mode 100644
index 0000000..c12a356
--- /dev/null
+++ b/car/core/src/androidTest/java/androidx/car/widget/CheckBoxListItemTest.java
@@ -0,0 +1,837 @@
+/*
+ * Copyright 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.car.widget;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
+import static androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.lessThan;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.number.IsCloseTo.closeTo;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.text.InputFilter;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+
+import androidx.car.test.R;
+import androidx.car.util.CarUxRestrictionsTestUtils;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.espresso.UiController;
+import androidx.test.espresso.ViewAction;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import org.hamcrest.Matcher;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Tests the layout configuration and checkbox functionality of {@link CheckBoxListItem}.
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class CheckBoxListItemTest {
+
+    @Rule
+    public ActivityTestRule<PagedListViewTestActivity> mActivityRule =
+            new ActivityTestRule<>(PagedListViewTestActivity.class);
+
+    private PagedListViewTestActivity mActivity;
+    private PagedListView mPagedListView;
+    private ListItemAdapter mAdapter;
+
+    private boolean isAutoDevice() {
+        PackageManager packageManager = mActivityRule.getActivity().getPackageManager();
+        return packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+    }
+
+    @Before
+    public void setUp() {
+        Assume.assumeTrue(isAutoDevice());
+
+        mActivity = mActivityRule.getActivity();
+        mPagedListView = mActivity.findViewById(R.id.paged_list_view);
+    }
+
+    @Test
+    public void testDefaultVisibility_EmptyItemShowsCheckBox() {
+        CheckBoxListItem item = new CheckBoxListItem(mActivity);
+        setupPagedListView(Arrays.asList(item));
+
+        ViewGroup itemView = (ViewGroup)
+                mPagedListView.getRecyclerView().getLayoutManager().getChildAt(0);
+        int childCount = itemView.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View view = itemView.getChildAt(i);
+            // |view| could be container in view holder, so exempt ViewGroup.
+            if (view instanceof CheckBox || view instanceof ViewGroup) {
+                assertThat(view.getVisibility(), is(equalTo(View.VISIBLE)));
+            } else {
+                assertThat("Visibility of view "
+                                + mActivity.getResources().getResourceEntryName(view.getId())
+                                + " by default should be GONE.",
+                        view.getVisibility(), is(equalTo(View.GONE)));
+            }
+        }
+    }
+
+    @Test
+    public void testItemIsEnabledByDefault() {
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+
+        List<CheckBoxListItem> items = Arrays.asList(item0);
+        setupPagedListView(items);
+
+        assertTrue(getViewHolderAtPosition(0).itemView.isEnabled());
+    }
+
+    @Test
+    public void testDisablingItem() {
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+
+        List<CheckBoxListItem> items = Arrays.asList(item0);
+        setupPagedListView(items);
+
+        item0.setEnabled(false);
+        refreshUi();
+
+        assertFalse(getViewHolderAtPosition(0).itemView.isEnabled());
+    }
+
+    @Test
+    public void testClickableItem_DefaultNotClickable() {
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+
+        List<CheckBoxListItem> items = Arrays.asList(item0);
+        setupPagedListView(items);
+
+        assertFalse(getViewHolderAtPosition(0).itemView.isClickable());
+    }
+
+    @Test
+    public void testClickableItem_setClickable() {
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        item0.setClickable(true);
+
+        List<CheckBoxListItem> items = Arrays.asList(item0);
+        setupPagedListView(items);
+
+        assertTrue(getViewHolderAtPosition(0).itemView.isClickable());
+    }
+
+    @Test
+    public void testClickableItem_ClickingTogglesCheckBox() {
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        item0.setClickable(true);
+
+        List<CheckBoxListItem> items = Arrays.asList(item0);
+        setupPagedListView(items);
+
+        onView(withId(R.id.recycler_view)).perform(actionOnItemAtPosition(0, click()));
+
+        assertTrue(getViewHolderAtPosition(0).getCompoundButton().isChecked());
+    }
+
+    @Test
+    public void testCheckBoxStatePersistsOnRebind() {
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        // CheckBox initially checked.
+        item0.setChecked(true);
+
+        setupPagedListView(Collections.singletonList(item0));
+        CheckBoxListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
+
+        toggleChecked(viewHolder.getCompoundButton());
+
+        viewHolder = getViewHolderAtPosition(0);
+        assertThat(viewHolder.getCompoundButton().isChecked(), is(equalTo(false)));
+    }
+
+    @Test
+    public void testSetCheckBoxState() {
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        item0.setChecked(true);
+
+        setupPagedListView(Arrays.asList(item0));
+
+        item0.setChecked(false);
+        refreshUi();
+
+        CheckBoxListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        assertThat(viewHolder.getCompoundButton().getVisibility(), is(equalTo(View.VISIBLE)));
+        assertThat(viewHolder.getCompoundButton().isChecked(), is(equalTo(false)));
+    }
+
+    @Test
+    public void testSetCheckBoxStateCallsListener() {
+        CompoundButton.OnCheckedChangeListener listener =
+                mock(CompoundButton.OnCheckedChangeListener.class);
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        item0.setOnCheckedChangeListener(listener);
+
+        setupPagedListView(Collections.singletonList(item0));
+
+        item0.setChecked(true);
+        refreshUi();
+        verify(listener).onCheckedChanged(any(CompoundButton.class), eq(true));
+    }
+
+    @Test
+    public void testRefreshingUiDoesNotCallListener() {
+        CompoundButton.OnCheckedChangeListener listener =
+                mock(CompoundButton.OnCheckedChangeListener.class);
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        item0.setOnCheckedChangeListener(listener);
+
+        setupPagedListView(Collections.singletonList(item0));
+
+        refreshUi();
+        verify(listener, never()).onCheckedChanged(any(CompoundButton.class), anyBoolean());
+    }
+
+    @Test
+    public void testSetCheckBoxStateBeforeFirstBindCallsListener() {
+        CompoundButton.OnCheckedChangeListener listener =
+                mock(CompoundButton.OnCheckedChangeListener.class);
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        item0.setOnCheckedChangeListener(listener);
+        item0.setChecked(true);
+
+        setupPagedListView(Collections.singletonList(item0));
+
+        verify(listener).onCheckedChanged(any(CompoundButton.class), eq(true));
+    }
+
+    @Test
+    public void testCheckBoxToggleCallsListener() {
+        CompoundButton.OnCheckedChangeListener listener =
+                mock(CompoundButton.OnCheckedChangeListener.class);
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        item0.setOnCheckedChangeListener(listener);
+
+        setupPagedListView(Collections.singletonList(item0));
+
+        CheckBoxListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        toggleChecked(viewHolder.getCompoundButton());
+
+        // Expect true because checkbox defaults to false.
+        verify(listener).onCheckedChanged(any(CompoundButton.class), eq(true));
+    }
+
+    @Test
+    public void testSetCheckBoxStateNotDirtyDoesNotCallListener() {
+        CompoundButton.OnCheckedChangeListener listener =
+                mock(CompoundButton.OnCheckedChangeListener.class);
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        item0.setChecked(true);
+        item0.setOnCheckedChangeListener(listener);
+
+        setupPagedListView(Collections.singletonList(item0));
+
+        item0.setChecked(true);
+        refreshUi();
+
+        verify(listener, never()).onCheckedChanged(any(CompoundButton.class), anyBoolean());
+    }
+
+    @Test
+    public void testCheckingCheckBox() {
+        final boolean[] clicked = {false};
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        item0.setOnCheckedChangeListener((button, isChecked) -> {
+            // Initial value is false.
+            assertTrue(isChecked);
+            clicked[0] = true;
+        });
+
+        List<CheckBoxListItem> items = Arrays.asList(item0);
+        setupPagedListView(items);
+
+        onView(withId(R.id.recycler_view)).perform(
+                actionOnItemAtPosition(0, clickChildViewWithId(R.id.checkbox_widget)));
+        assertTrue(clicked[0]);
+    }
+
+    @Test
+    public void testDividerVisibility() {
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        item0.setShowCompoundButtonDivider(true);
+
+        CheckBoxListItem item1 = new CheckBoxListItem(mActivity);
+        item0.setShowCompoundButtonDivider(false);
+
+        List<CheckBoxListItem> items = Arrays.asList(item0, item1);
+        setupPagedListView(items);
+
+        CheckBoxListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        assertThat(viewHolder.getCompoundButton().getVisibility(), is(equalTo(View.VISIBLE)));
+        assertThat(viewHolder.getCompoundButton().getVisibility(), is(equalTo(View.VISIBLE)));
+
+        viewHolder = getViewHolderAtPosition(1);
+        assertThat(viewHolder.getCompoundButton().getVisibility(), is(equalTo(View.VISIBLE)));
+        assertThat(viewHolder.getCompoundButtonDivider().getVisibility(), is(equalTo(View.GONE)));
+    }
+
+    @Test
+    public void testPrimaryActionVisible() {
+        CheckBoxListItem largeIcon = new CheckBoxListItem(mActivity);
+        largeIcon.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
+                CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_LARGE);
+
+        CheckBoxListItem mediumIcon = new CheckBoxListItem(mActivity);
+        mediumIcon.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
+                CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_MEDIUM);
+
+        CheckBoxListItem smallIcon = new CheckBoxListItem(mActivity);
+        smallIcon.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
+                CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
+
+        List<CheckBoxListItem> items = Arrays.asList(largeIcon, mediumIcon, smallIcon);
+        setupPagedListView(items);
+
+        assertThat(getViewHolderAtPosition(0).getPrimaryIcon().getVisibility(),
+                is(equalTo(View.VISIBLE)));
+        assertThat(getViewHolderAtPosition(1).getPrimaryIcon().getVisibility(),
+                is(equalTo(View.VISIBLE)));
+        assertThat(getViewHolderAtPosition(2).getPrimaryIcon().getVisibility(),
+                is(equalTo(View.VISIBLE)));
+    }
+
+    @Test
+    public void testTextVisible() {
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        item0.setTitle("title");
+
+        CheckBoxListItem item1 = new CheckBoxListItem(mActivity);
+        item1.setBody("body");
+
+        List<CheckBoxListItem> items = Arrays.asList(item0, item1);
+        setupPagedListView(items);
+
+        assertThat(getViewHolderAtPosition(0).getTitle().getVisibility(),
+                is(equalTo(View.VISIBLE)));
+        assertThat(getViewHolderAtPosition(1).getBody().getVisibility(),
+                is(equalTo(View.VISIBLE)));
+    }
+
+    @Test
+    public void testTextStartMarginMatchesPrimaryActionType() {
+        CheckBoxListItem largeIcon = new CheckBoxListItem(mActivity);
+        largeIcon.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
+                CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_LARGE);
+
+        CheckBoxListItem mediumIcon = new CheckBoxListItem(mActivity);
+        mediumIcon.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
+                CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_MEDIUM);
+
+        CheckBoxListItem smallIcon = new CheckBoxListItem(mActivity);
+        smallIcon.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
+                CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
+
+        CheckBoxListItem emptyIcon = new CheckBoxListItem(mActivity);
+        emptyIcon.setPrimaryActionEmptyIcon();
+
+        CheckBoxListItem noIcon = new CheckBoxListItem(mActivity);
+        noIcon.setPrimaryActionNoIcon();
+
+        List<CheckBoxListItem> items = Arrays.asList(
+                largeIcon, mediumIcon, smallIcon, emptyIcon, noIcon);
+        List<Integer> expectedStartMargin = Arrays.asList(
+                R.dimen.car_keyline_4,  // Large icon.
+                R.dimen.car_keyline_3,  // Medium icon.
+                R.dimen.car_keyline_3,  // Small icon.
+                R.dimen.car_keyline_3,  // Empty icon.
+                R.dimen.car_keyline_1); // No icon.
+        setupPagedListView(items);
+
+        for (int i = 0; i < items.size(); i++) {
+            CheckBoxListItem.ViewHolder viewHolder = getViewHolderAtPosition(i);
+
+            int expected = ApplicationProvider.getApplicationContext().getResources()
+                    .getDimensionPixelSize(expectedStartMargin.get(i));
+            assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getTitle().getLayoutParams())
+                    .getMarginStart(), is(equalTo(expected)));
+            assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getBody().getLayoutParams())
+                    .getMarginStart(), is(equalTo(expected)));
+        }
+    }
+
+    @Test
+    public void testItemWithOnlyTitleIsSingleLine() {
+        // Only space.
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        item0.setTitle(" ");
+
+        // Underscore.
+        CheckBoxListItem item1 = new CheckBoxListItem(mActivity);
+        item1.setTitle("______");
+
+        CheckBoxListItem item2 = new CheckBoxListItem(mActivity);
+        item2.setTitle("ALL UPPER CASE");
+
+        // String wouldn't fit in one line.
+        CheckBoxListItem item3 = new CheckBoxListItem(mActivity);
+        item3.setTitle(ApplicationProvider.getApplicationContext().getResources().getString(
+                R.string.over_uxr_text_length_limit));
+
+        List<CheckBoxListItem> items = Arrays.asList(item0, item1, item2, item3);
+        setupPagedListView(items);
+
+        double singleLineHeight =
+                ApplicationProvider.getApplicationContext().getResources().getDimension(
+                        R.dimen.car_single_line_list_item_height);
+
+        LinearLayoutManager layoutManager =
+                (LinearLayoutManager) mPagedListView.getRecyclerView().getLayoutManager();
+        for (int i = 0; i < items.size(); i++) {
+            assertThat((double) layoutManager.findViewByPosition(i).getHeight(),
+                    is(closeTo(singleLineHeight, 1.0d)));
+        }
+    }
+
+    @Test
+    public void testItemWithBodyTextIsAtLeastDoubleLine() {
+        // Only space.
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        item0.setBody(" ");
+
+        // Underscore.
+        CheckBoxListItem item1 = new CheckBoxListItem(mActivity);
+        item1.setBody("____");
+
+        // String wouldn't fit in one line.
+        CheckBoxListItem item2 = new CheckBoxListItem(mActivity);
+        item2.setBody(ApplicationProvider.getApplicationContext().getResources().getString(
+                R.string.over_uxr_text_length_limit));
+
+        List<CheckBoxListItem> items = Arrays.asList(item0, item1, item2);
+        setupPagedListView(items);
+
+        final int doubleLineHeight =
+                (int) ApplicationProvider.getApplicationContext().getResources().getDimension(
+                        R.dimen.car_double_line_list_item_height);
+
+        LinearLayoutManager layoutManager =
+                (LinearLayoutManager) mPagedListView.getRecyclerView().getLayoutManager();
+        for (int i = 0; i < items.size(); i++) {
+            assertThat(layoutManager.findViewByPosition(i).getHeight(),
+                    is(greaterThanOrEqualTo(doubleLineHeight)));
+        }
+    }
+
+    @Test
+    public void testSetPrimaryActionIcon_withIcon() {
+        CheckBoxListItem item = new CheckBoxListItem(mActivity);
+        item.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
+                CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_LARGE);
+
+        List<CheckBoxListItem> items = Arrays.asList(item);
+        setupPagedListView(items);
+
+        assertThat(getViewHolderAtPosition(0).getPrimaryIcon().getDrawable(), is(notNullValue()));
+    }
+
+    @Test
+    public void testSetPrimaryActionIcon_withDrawable() {
+        CheckBoxListItem item = new CheckBoxListItem(mActivity);
+        item.setPrimaryActionIcon(
+                mActivity.getDrawable(android.R.drawable.sym_def_app_icon),
+                CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_LARGE);
+
+        List<CheckBoxListItem> items = Arrays.asList(item);
+        setupPagedListView(items);
+
+        assertThat(getViewHolderAtPosition(0).getPrimaryIcon().getDrawable(), is(notNullValue()));
+    }
+
+    @Test
+    public void testPrimaryIconSizesInIncreasingOrder() {
+        CheckBoxListItem small = new CheckBoxListItem(mActivity);
+        small.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
+                CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
+
+        CheckBoxListItem medium = new CheckBoxListItem(mActivity);
+        medium.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
+                CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_MEDIUM);
+
+        CheckBoxListItem large = new CheckBoxListItem(mActivity);
+        large.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
+                CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_LARGE);
+
+        List<CheckBoxListItem> items = Arrays.asList(small, medium, large);
+        setupPagedListView(items);
+
+        CheckBoxListItem.ViewHolder smallVH = getViewHolderAtPosition(0);
+        CheckBoxListItem.ViewHolder mediumVH = getViewHolderAtPosition(1);
+        CheckBoxListItem.ViewHolder largeVH = getViewHolderAtPosition(2);
+
+        assertThat(largeVH.getPrimaryIcon().getHeight(), is(greaterThan(
+                mediumVH.getPrimaryIcon().getHeight())));
+        assertThat(mediumVH.getPrimaryIcon().getHeight(), is(greaterThan(
+                smallVH.getPrimaryIcon().getHeight())));
+    }
+
+    @Test
+    public void testLargePrimaryIconHasNoStartMargin() {
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        item0.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
+                CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_LARGE);
+
+        List<CheckBoxListItem> items = Arrays.asList(item0);
+        setupPagedListView(items);
+
+        CheckBoxListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getPrimaryIcon().getLayoutParams())
+                .getMarginStart(), is(equalTo(0)));
+    }
+
+    @Test
+    public void testSmallAndMediumPrimaryIconStartMargin() {
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        item0.setPrimaryActionIcon(
+                android.R.drawable.sym_def_app_icon,
+                CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
+
+        CheckBoxListItem item1 = new CheckBoxListItem(mActivity);
+        item1.setPrimaryActionIcon(
+                android.R.drawable.sym_def_app_icon,
+                CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_MEDIUM);
+
+        List<CheckBoxListItem> items = Arrays.asList(item0, item1);
+        setupPagedListView(items);
+
+        int expected =
+                ApplicationProvider.getApplicationContext().getResources().getDimensionPixelSize(
+                        R.dimen.car_keyline_1);
+
+        CheckBoxListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getPrimaryIcon().getLayoutParams())
+                .getMarginStart(), is(equalTo(expected)));
+
+        viewHolder = getViewHolderAtPosition(1);
+        assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getPrimaryIcon().getLayoutParams())
+                .getMarginStart(), is(equalTo(expected)));
+    }
+
+    @Test
+    public void testSmallPrimaryIconTopMarginRemainsTheSameRegardlessOfTextLength() {
+        final String longText =
+                ApplicationProvider.getApplicationContext().getResources().getString(
+                        R.string.over_uxr_text_length_limit);
+
+        // Single line item.
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        item0.setPrimaryActionIcon(
+                android.R.drawable.sym_def_app_icon,
+                CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
+        item0.setTitle("one line text");
+
+        // Double line item with one line text.
+        CheckBoxListItem item1 = new CheckBoxListItem(mActivity);
+        item1.setPrimaryActionIcon(
+                android.R.drawable.sym_def_app_icon,
+                CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
+        item1.setTitle("one line text");
+        item1.setBody("one line text");
+
+        // Double line item with long text.
+        CheckBoxListItem item2 = new CheckBoxListItem(mActivity);
+        item2.setPrimaryActionIcon(
+                android.R.drawable.sym_def_app_icon,
+                CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
+        item2.setTitle("one line text");
+        item2.setBody(longText);
+
+        // Body text only - long text.
+        CheckBoxListItem item3 = new CheckBoxListItem(mActivity);
+        item3.setPrimaryActionIcon(
+                android.R.drawable.sym_def_app_icon,
+                CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
+        item3.setBody(longText);
+
+        // Body text only - one line text.
+        CheckBoxListItem item4 = new CheckBoxListItem(mActivity);
+        item4.setPrimaryActionIcon(
+                android.R.drawable.sym_def_app_icon,
+                CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
+        item4.setBody("one line text");
+
+        List<CheckBoxListItem> items = Arrays.asList(item0, item1, item2, item3, item4);
+        setupPagedListView(items);
+
+        for (int i = 1; i < items.size(); i++) {
+            onView(withId(R.id.recycler_view)).perform(scrollToPosition(i));
+            // Implementation uses integer division so it may be off by 1 vs centered vertically.
+            assertThat((double) getViewHolderAtPosition(i - 1).getPrimaryIcon().getTop(),
+                    is(closeTo(
+                            (double) getViewHolderAtPosition(i).getPrimaryIcon().getTop(), 1.0d)));
+        }
+    }
+
+    @Test
+    public void testCustomViewBinderBindsLast() {
+        final String updatedTitle = "updated title";
+
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        item0.setTitle("original title");
+        item0.addViewBinder((viewHolder) -> viewHolder.getTitle().setText(updatedTitle));
+
+        List<CheckBoxListItem> items = Arrays.asList(item0);
+        setupPagedListView(items);
+
+        CheckBoxListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        assertThat(viewHolder.getTitle().getText(), is(equalTo(updatedTitle)));
+    }
+
+    @Test
+    public void testCustomViewBinderOnUnusedViewsHasNoEffect() {
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        item0.addViewBinder((viewHolder) -> viewHolder.getBody().setText("text"));
+
+        List<CheckBoxListItem> items = Arrays.asList(item0);
+        setupPagedListView(items);
+
+        CheckBoxListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        assertThat(viewHolder.getBody().getVisibility(), is(equalTo(View.GONE)));
+        // Custom binder interacts with body but has no effect.
+        // Expect card height to remain single line.
+        assertThat((double) viewHolder.itemView.getHeight(), is(closeTo(
+                ApplicationProvider.getApplicationContext().getResources().getDimension(
+                        R.dimen.car_single_line_list_item_height), 1.0d)));
+    }
+
+    @Test
+    public void testRevertingViewBinder() throws Throwable {
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        item0.setBody("one item");
+        item0.addViewBinder(
+                (viewHolder) -> viewHolder.getBody().setEllipsize(TextUtils.TruncateAt.END),
+                (viewHolder -> viewHolder.getBody().setEllipsize(null)));
+
+        List<CheckBoxListItem> items = Arrays.asList(item0);
+        setupPagedListView(items);
+
+        CheckBoxListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
+
+        // Bind view holder to a new item - the customization made by item0 should be reverted.
+        CheckBoxListItem item1 = new CheckBoxListItem(mActivity);
+        item1.setBody("new item");
+        mActivityRule.runOnUiThread(() -> item1.bind(viewHolder));
+
+        assertThat(viewHolder.getBody().getEllipsize(), is(equalTo(null)));
+    }
+
+    @Test
+    public void testRemovingViewBinder() {
+        CheckBoxListItem item0 = new CheckBoxListItem(mActivity);
+        item0.setBody("one item");
+        ListItem.ViewBinder<CheckBoxListItem.ViewHolder> binder =
+                (viewHolder) -> viewHolder.getTitle().setEllipsize(TextUtils.TruncateAt.END);
+        item0.addViewBinder(binder);
+
+        assertTrue(item0.removeViewBinder(binder));
+
+        List<CheckBoxListItem> items = Arrays.asList(item0);
+        setupPagedListView(items);
+
+        assertThat(getViewHolderAtPosition(0).getBody().getEllipsize(), is(equalTo(null)));
+    }
+
+    @Test
+    public void testUpdateItem() {
+        CheckBoxListItem item = new CheckBoxListItem(mActivity);
+        setupPagedListView(Arrays.asList(item));
+
+        String title = "updated title";
+        item.setTitle(title);
+
+        refreshUi();
+
+        CheckBoxListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        assertThat(viewHolder.getTitle().getText(), is(equalTo(title)));
+    }
+
+    @Test
+    public void testUxRestrictionsChange() {
+        String longText = mActivity.getString(R.string.over_uxr_text_length_limit);
+        CheckBoxListItem item = new CheckBoxListItem(mActivity);
+        item.setBody(longText);
+
+        setupPagedListView(Arrays.asList(item));
+
+        CheckBoxListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        // Default behavior without UXR is unrestricted.
+        assertThat(viewHolder.getBody().getText(), is(equalTo(longText)));
+
+        viewHolder.onUxRestrictionsChanged(CarUxRestrictionsTestUtils.getFullyRestricted());
+        refreshUi();
+
+        // Verify that the body text length is limited.
+        assertThat(viewHolder.getBody().getText().length(), is(lessThan(longText.length())));
+    }
+
+    @Test
+    public void testUxRestrictionsChangesDoNotAlterExistingInputFilters() {
+        InputFilter filter = new InputFilter.AllCaps(Locale.US);
+        String bodyText = "body_text";
+        CheckBoxListItem item = new CheckBoxListItem(mActivity);
+        item.setBody(bodyText);
+        item.addViewBinder(vh -> vh.getBody().setFilters(new InputFilter[]{filter}));
+
+        setupPagedListView(Arrays.asList(item));
+
+        CheckBoxListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
+
+        // Toggle UX restrictions between fully restricted and unrestricted should not affect
+        // existing filters.
+        viewHolder.onUxRestrictionsChanged(CarUxRestrictionsTestUtils.getFullyRestricted());
+        refreshUi();
+        assertTrue(Arrays.asList(viewHolder.getBody().getFilters()).contains(filter));
+
+        viewHolder.onUxRestrictionsChanged(CarUxRestrictionsTestUtils.getBaseline());
+        refreshUi();
+        assertTrue(Arrays.asList(viewHolder.getBody().getFilters()).contains(filter));
+    }
+
+    @Test
+    public void testDisabledItemDisablesViewHolder() {
+        CheckBoxListItem item = new CheckBoxListItem(mActivity);
+        item.setTitle("title");
+        item.setBody("body");
+        item.setEnabled(false);
+
+        setupPagedListView(Arrays.asList(item));
+
+        CheckBoxListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        assertFalse(viewHolder.getTitle().isEnabled());
+        assertFalse(viewHolder.getBody().isEnabled());
+        assertFalse(viewHolder.getCompoundButton().isEnabled());
+    }
+
+    @Test
+    public void testDisabledItemDoesNotRespondToClick() {
+        // Disabled view will not respond to touch event.
+        // Current test setup makes it hard to test, since clickChildViewWithId() directly calls
+        // performClick() on a view, bypassing the way UI handles disabled state.
+
+        // We are explicitly setting itemView so test it here.
+        boolean[] clicked = new boolean[]{false};
+        CheckBoxListItem item = new CheckBoxListItem(mActivity);
+        item.setEnabled(false);
+
+        setupPagedListView(Arrays.asList(item));
+
+        onView(withId(R.id.recycler_view)).perform(
+                actionOnItemAtPosition(0, click()));
+
+        assertFalse(clicked[0]);
+    }
+
+    private Context getContext() {
+        return mActivity;
+    }
+
+    private void refreshUi() {
+        try {
+            mActivityRule.runOnUiThread(() -> mAdapter.notifyDataSetChanged());
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+            throw new RuntimeException(throwable);
+        }
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    private void setupPagedListView(List<CheckBoxListItem> items) {
+        ListItemProvider provider = new ListItemProvider.ListProvider(new ArrayList<>(items));
+        try {
+            mAdapter = new ListItemAdapter(mActivity, provider);
+            mActivityRule.runOnUiThread(() -> mPagedListView.setAdapter(mAdapter));
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+            throw new RuntimeException(throwable);
+        }
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    private CheckBoxListItem.ViewHolder getViewHolderAtPosition(int position) {
+        return (CheckBoxListItem.ViewHolder) mPagedListView.getRecyclerView()
+                .findViewHolderForAdapterPosition(position);
+    }
+
+    private void toggleChecked(CompoundButton button) {
+        try {
+            mActivityRule.runOnUiThread(button::toggle);
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+            throw new RuntimeException(throwable);
+        }
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    private static ViewAction clickChildViewWithId(final int id) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return null;
+            }
+
+            @Override
+            public String getDescription() {
+                return "Click on a child view with specific id.";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                View v = view.findViewById(id);
+                v.performClick();
+            }
+        };
+    }
+}
diff --git a/car/core/src/androidTest/java/androidx/car/widget/RadioButtonListItemTest.java b/car/core/src/androidTest/java/androidx/car/widget/RadioButtonListItemTest.java
index dfcbd471..0a16219 100644
--- a/car/core/src/androidTest/java/androidx/car/widget/RadioButtonListItemTest.java
+++ b/car/core/src/androidTest/java/androidx/car/widget/RadioButtonListItemTest.java
@@ -82,7 +82,7 @@
         item.setEnabled(false);
         setupPagedListView(Arrays.asList(item));
 
-        assertFalse(getViewHolderAtPosition(0).getRadioButton().isEnabled());
+        assertFalse(getViewHolderAtPosition(0).getCompoundButton().isEnabled());
     }
 
     @Test
@@ -145,7 +145,7 @@
     }
 
     @Test
-    public void testSetTextStartMargin_DefaultMargin() {
+    public void testSetTextStartMargin_Margin() {
         RadioButtonListItem item = new RadioButtonListItem(mActivity);
         item.setPrimaryActionIcon(null, RadioButtonListItem.PRIMARY_ACTION_ICON_SIZE_LARGE);
         item.setTitle("text");
@@ -156,21 +156,7 @@
         assertThat(getViewHolderAtPosition(0).getTitle().getLeft(), is(equalTo(expected)));
     }
 
-    @Test
-    public void testSetTextStartMargin_CustomMargin() {
-        RadioButtonListItem item = new RadioButtonListItem(mActivity);
-        item.setPrimaryActionIcon(
-                android.R.drawable.sym_def_app_icon,
-                RadioButtonListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
-        item.setTitle("text");
-        item.setTextStartMargin(R.dimen.car_keyline_4);
 
-        setupPagedListView(Arrays.asList(item));
-        int expected = ApplicationProvider.getApplicationContext().getResources()
-                .getDimensionPixelSize(R.dimen.car_keyline_4);
-
-        assertThat(getViewHolderAtPosition(0).getTitle().getLeft(), is(equalTo(expected)));
-    }
 
     @Test
     public void testSetChecked() {
@@ -178,7 +164,7 @@
         item.setChecked(true);
         setupPagedListView(Arrays.asList(item));
 
-        assertTrue(getViewHolderAtPosition(0).getRadioButton().isChecked());
+        assertTrue(getViewHolderAtPosition(0).getCompoundButton().isChecked());
     }
 
     @Test
@@ -190,28 +176,28 @@
         item.setChecked(false);
         refreshUi();
 
-        assertFalse(getViewHolderAtPosition(0).getRadioButton().isChecked());
+        assertFalse(getViewHolderAtPosition(0).getCompoundButton().isChecked());
     }
 
     @Test
     public void testSetShowRadioButtonDivider() {
         RadioButtonListItem show = new RadioButtonListItem(mActivity);
-        show.setShowRadioButtonDivider(true);
+        show.setShowCompoundButtonDivider(true);
 
         setupPagedListView(Arrays.asList(show));
 
-        assertThat(getViewHolderAtPosition(0).getRadioButtonDivider().getVisibility(),
+        assertThat(getViewHolderAtPosition(0).getCompoundButtonDivider().getVisibility(),
                 is(equalTo(View.VISIBLE)));
     }
 
     @Test
     public void testSetShowRadioButtonDivider_noDivider() {
         RadioButtonListItem noShow = new RadioButtonListItem(mActivity);
-        noShow.setShowRadioButtonDivider(false);
+        noShow.setShowCompoundButtonDivider(false);
 
         setupPagedListView(Arrays.asList(noShow));
 
-        assertThat(getViewHolderAtPosition(0).getRadioButtonDivider().getVisibility(),
+        assertThat(getViewHolderAtPosition(0).getCompoundButtonDivider().getVisibility(),
                 is(equalTo(View.GONE)));
     }
 
@@ -220,24 +206,7 @@
         RadioButtonListItem item = new RadioButtonListItem(mActivity);
         setupPagedListView(Arrays.asList(item));
 
-        assertFalse(getViewHolderAtPosition(0).getRadioButton().isChecked());
-    }
-
-    @Test
-    public void testClickingItemAlwaysCheckRadioButton() {
-        boolean[] clicked = new boolean[]{false};
-
-        RadioButtonListItem item = new RadioButtonListItem(mActivity);
-        // Set radio button listener, but we will click the item.
-        item.setOnCheckedChangeListener((compoundButton, checked) -> clicked[0] = true);
-        setupPagedListView(Arrays.asList(item));
-
-        onView(withId(R.id.recycler_view)).perform(
-                RecyclerViewActions.actionOnItemAtPosition(0, click()));
-
-        assertTrue(getViewHolderAtPosition(0).getRadioButton().isChecked());
-        // Verify the listener is also triggered.
-        assertTrue(clicked[0]);
+        assertFalse(getViewHolderAtPosition(0).getCompoundButton().isChecked());
     }
 
     @Test
@@ -252,7 +221,7 @@
         item.setChecked(false);
         refreshUi();
 
-        assertFalse(getViewHolderAtPosition(0).getRadioButton().isChecked());
+        assertFalse(getViewHolderAtPosition(0).getCompoundButton().isChecked());
     }
 
     @Test
@@ -263,7 +232,7 @@
         setupPagedListView(Arrays.asList(item));
 
         onView(withId(R.id.recycler_view)).perform(
-                actionOnItemAtPosition(0, clickChildViewWithId(R.id.radio_button)));
+                actionOnItemAtPosition(0, clickChildViewWithId(R.id.radiobutton_widget)));
 
         assertTrue(clicked[0]);
     }
diff --git a/car/core/src/androidTest/java/androidx/car/widget/SwitchListItemTest.java b/car/core/src/androidTest/java/androidx/car/widget/SwitchListItemTest.java
index ffc6ed1..f5b2e5b 100644
--- a/car/core/src/androidTest/java/androidx/car/widget/SwitchListItemTest.java
+++ b/car/core/src/androidTest/java/androidx/car/widget/SwitchListItemTest.java
@@ -176,7 +176,7 @@
 
         onView(withId(R.id.recycler_view)).perform(actionOnItemAtPosition(0, click()));
 
-        assertTrue(getViewHolderAtPosition(0).getSwitch().isChecked());
+        assertTrue(getViewHolderAtPosition(0).getCompoundButton().isChecked());
     }
 
     @Test
@@ -188,10 +188,10 @@
         setupPagedListView(Collections.singletonList(item0));
         SwitchListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
 
-        toggleChecked(viewHolder.getSwitch());
+        toggleChecked(viewHolder.getCompoundButton());
 
         viewHolder = getViewHolderAtPosition(0);
-        assertThat(viewHolder.getSwitch().isChecked(), is(equalTo(false)));
+        assertThat(viewHolder.getCompoundButton().isChecked(), is(equalTo(false)));
     }
 
     @Test
@@ -205,8 +205,8 @@
         refreshUi();
 
         SwitchListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
-        assertThat(viewHolder.getSwitch().getVisibility(), is(equalTo(View.VISIBLE)));
-        assertThat(viewHolder.getSwitch().isChecked(), is(equalTo(false)));
+        assertThat(viewHolder.getCompoundButton().getVisibility(), is(equalTo(View.VISIBLE)));
+        assertThat(viewHolder.getCompoundButton().isChecked(), is(equalTo(false)));
     }
 
     @Test
@@ -214,7 +214,7 @@
         CompoundButton.OnCheckedChangeListener listener =
                 mock(CompoundButton.OnCheckedChangeListener.class);
         SwitchListItem item0 = new SwitchListItem(mActivity);
-        item0.setSwitchOnCheckedChangeListener(listener);
+        item0.setOnCheckedChangeListener(listener);
 
         setupPagedListView(Collections.singletonList(item0));
 
@@ -228,7 +228,7 @@
         CompoundButton.OnCheckedChangeListener listener =
                 mock(CompoundButton.OnCheckedChangeListener.class);
         SwitchListItem item0 = new SwitchListItem(mActivity);
-        item0.setSwitchOnCheckedChangeListener(listener);
+        item0.setOnCheckedChangeListener(listener);
 
         setupPagedListView(Collections.singletonList(item0));
 
@@ -241,7 +241,7 @@
         CompoundButton.OnCheckedChangeListener listener =
                 mock(CompoundButton.OnCheckedChangeListener.class);
         SwitchListItem item0 = new SwitchListItem(mActivity);
-        item0.setSwitchOnCheckedChangeListener(listener);
+        item0.setOnCheckedChangeListener(listener);
         item0.setChecked(true);
 
         setupPagedListView(Collections.singletonList(item0));
@@ -254,12 +254,12 @@
         CompoundButton.OnCheckedChangeListener listener =
                 mock(CompoundButton.OnCheckedChangeListener.class);
         SwitchListItem item0 = new SwitchListItem(mActivity);
-        item0.setSwitchOnCheckedChangeListener(listener);
+        item0.setOnCheckedChangeListener(listener);
 
         setupPagedListView(Collections.singletonList(item0));
 
         SwitchListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
-        toggleChecked(viewHolder.getSwitch());
+        toggleChecked(viewHolder.getCompoundButton());
 
         // Expect true because switch defaults to false.
         verify(listener).onCheckedChanged(any(CompoundButton.class), eq(true));
@@ -271,7 +271,7 @@
                 mock(CompoundButton.OnCheckedChangeListener.class);
         SwitchListItem item0 = new SwitchListItem(mActivity);
         item0.setChecked(true);
-        item0.setSwitchOnCheckedChangeListener(listener);
+        item0.setOnCheckedChangeListener(listener);
 
         setupPagedListView(Collections.singletonList(item0));
 
@@ -285,7 +285,7 @@
     public void testCheckingSwitch() {
         final boolean[] clicked = {false};
         SwitchListItem item0 = new SwitchListItem(mActivity);
-        item0.setSwitchOnCheckedChangeListener((button, isChecked) -> {
+        item0.setOnCheckedChangeListener((button, isChecked) -> {
             // Initial value is false.
             assertTrue(isChecked);
             clicked[0] = true;
@@ -302,21 +302,21 @@
     @Test
     public void testDividerVisibility() {
         SwitchListItem item0 = new SwitchListItem(mActivity);
-        item0.setShowSwitchDivider(true);
+        item0.setShowCompoundButtonDivider(true);
 
         SwitchListItem item1 = new SwitchListItem(mActivity);
-        item0.setShowSwitchDivider(false);
+        item0.setShowCompoundButtonDivider(false);
 
         List<SwitchListItem> items = Arrays.asList(item0, item1);
         setupPagedListView(items);
 
         SwitchListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
-        assertThat(viewHolder.getSwitch().getVisibility(), is(equalTo(View.VISIBLE)));
-        assertThat(viewHolder.getSwitch().getVisibility(), is(equalTo(View.VISIBLE)));
+        assertThat(viewHolder.getCompoundButton().getVisibility(), is(equalTo(View.VISIBLE)));
+        assertThat(viewHolder.getCompoundButton().getVisibility(), is(equalTo(View.VISIBLE)));
 
         viewHolder = getViewHolderAtPosition(1);
-        assertThat(viewHolder.getSwitch().getVisibility(), is(equalTo(View.VISIBLE)));
-        assertThat(viewHolder.getSwitchDivider().getVisibility(), is(equalTo(View.GONE)));
+        assertThat(viewHolder.getCompoundButton().getVisibility(), is(equalTo(View.VISIBLE)));
+        assertThat(viewHolder.getCompoundButtonDivider().getVisibility(), is(equalTo(View.GONE)));
     }
 
     @Test
@@ -752,7 +752,7 @@
         SwitchListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
         assertFalse(viewHolder.getTitle().isEnabled());
         assertFalse(viewHolder.getBody().isEnabled());
-        assertFalse(viewHolder.getSwitch().isEnabled());
+        assertFalse(viewHolder.getCompoundButton().isEnabled());
     }
 
     @Test
diff --git a/car/core/src/main/java/androidx/car/app/CarSingleChoiceDialog.java b/car/core/src/main/java/androidx/car/app/CarSingleChoiceDialog.java
index 73e7307..23bef3c 100644
--- a/car/core/src/main/java/androidx/car/app/CarSingleChoiceDialog.java
+++ b/car/core/src/main/java/androidx/car/app/CarSingleChoiceDialog.java
@@ -227,10 +227,10 @@
         RadioButtonListItem item = new RadioButtonListItem(getContext());
         item.setTitle(selectionItem.mTitle);
         item.setBody(selectionItem.mBody);
-        item.setShowRadioButtonDivider(false);
+        item.setShowCompoundButtonDivider(false);
         item.addViewBinder(vh -> {
-            vh.getRadioButton().setChecked(mSelectedItem == position);
-            vh.getRadioButton().setOnCheckedChangeListener(
+            vh.getCompoundButton().setChecked(mSelectedItem == position);
+            vh.getCompoundButton().setOnCheckedChangeListener(
                     (buttonView, isChecked) -> {
                         mSelectedItem = position;
                         // Refresh other radio button list items.
diff --git a/car/core/src/main/java/androidx/car/widget/CheckBoxListItem.java b/car/core/src/main/java/androidx/car/widget/CheckBoxListItem.java
new file mode 100644
index 0000000..be99348
--- /dev/null
+++ b/car/core/src/main/java/androidx/car/widget/CheckBoxListItem.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 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.car.widget;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.car.R;
+import androidx.car.util.CarUxRestrictionsUtils;
+import androidx.car.uxrestrictions.CarUxRestrictions;
+import androidx.car.widget.ListItemAdapter.ListItemType;
+import androidx.constraintlayout.widget.Guideline;
+
+/**
+ * Class to build a list item with {@link CheckBox}.
+ *
+ * <p>A checkbox list item is visually composed of 5 parts.
+ * <ul>
+ * <li>optional {@code Primary Action Icon}.
+ * <li>optional {@code Title}.
+ * <li>optional {@code Body}.
+ * <li>optional {@code Divider}.
+ * <li>A {@link CheckBox}.
+ * </ul>
+ */
+public final class CheckBoxListItem extends CompoundButtonListItem<CheckBoxListItem.ViewHolder> {
+
+    /**
+     * Creates a {@link ViewHolder}.
+     *
+     * @return a {@link ViewHolder} for this {@link CheckBoxListItem}.
+     */
+    @NonNull
+    public static ViewHolder createViewHolder(@NonNull View itemView) {
+        return new ViewHolder(itemView);
+    }
+
+    /**
+     * Creates a {@link CheckBoxListItem} that will be used to display a list item with a
+     * {@link CheckBox}.
+     *
+     * @param context The context to be used by this {@link CheckBoxListItem}.
+     */
+    public CheckBoxListItem(@NonNull Context context) {
+        super(context);
+    }
+
+    /**
+     * Used by {@link ListItemAdapter} to choose layout to inflate for view holder.
+     *
+     * @return Type of this {@link CompoundButtonListItem}.
+     */
+    @ListItemType
+    @Override
+    public int getViewType() {
+        return ListItemAdapter.LIST_ITEM_TYPE_CHECK_BOX;
+    }
+
+    /**
+     * Returns whether the compound button will be placed at the end of the list item layout. This
+     * value is used to determine start margins for the {@code Title} and {@code Body}.
+     *
+     * @return Whether compound button is placed at the end of the list item layout.
+     */
+    @Override
+    public boolean isCompoundButtonPositionEnd() {
+        return true;
+    }
+
+    /**
+     * ViewHolder that contains necessary widgets for {@link CheckBoxListItem}.
+     */
+    public static final class ViewHolder extends CompoundButtonListItem.ViewHolder {
+
+        private View[] mWidgetViews;
+
+        private ViewGroup mContainerLayout;
+
+        private ImageView mPrimaryIcon;
+
+        private TextView mTitle;
+        private TextView mBody;
+
+        private Guideline mSupplementalGuideline;
+
+        private CompoundButton mCompoundButton;
+        private View mCompoundButtonDivider;
+
+        /**
+         * Creates a {@link ViewHolder} for a {@link CheckBoxListItem}.
+         *
+         * @param itemView The view to be used to display a {@link CheckBoxListItem}.
+         */
+        public ViewHolder(@NonNull View itemView) {
+            super(itemView);
+
+            mContainerLayout = itemView.findViewById(R.id.container);
+
+            mPrimaryIcon = itemView.findViewById(R.id.primary_icon);
+
+            mTitle = itemView.findViewById(R.id.title);
+            mBody = itemView.findViewById(R.id.body);
+
+            mSupplementalGuideline = itemView.findViewById(R.id.supplemental_actions_guideline);
+
+            mCompoundButton = itemView.findViewById(R.id.checkbox_widget);
+            mCompoundButtonDivider = itemView.findViewById(R.id.checkbox_divider);
+
+            int minTouchSize = itemView.getContext().getResources()
+                    .getDimensionPixelSize(R.dimen.car_touch_target_size);
+            MinTouchTargetHelper.ensureThat(mCompoundButton).hasMinTouchSize(minTouchSize);
+
+            // Each line groups relevant child views in an effort to help keep this view array
+            // updated with actual child views in the ViewHolder.
+            mWidgetViews = new View[]{
+                    mPrimaryIcon,
+                    mTitle, mBody,
+                    mCompoundButton, mCompoundButtonDivider,
+            };
+        }
+
+        /**
+         * Updates child views with current car UX restrictions.
+         *
+         * <p>{@code Text} might be truncated to meet length limit required by regulation.
+         *
+         * @param restrictionsInfo current car UX restrictions.
+         */
+        @Override
+        public void onUxRestrictionsChanged(@NonNull CarUxRestrictions restrictionsInfo) {
+            CarUxRestrictionsUtils.apply(itemView.getContext(), restrictionsInfo, getBody());
+        }
+
+        /**
+         * Returns the primary icon view within this view holder's view.
+         *
+         * @return Icon view within this view holder's view.
+         */
+        @NonNull
+        public ImageView getPrimaryIcon() {
+            return mPrimaryIcon;
+        }
+
+        /**
+         * Returns the title view within this view holder's view.
+         *
+         * @return Title view within this view holder's view.
+         */
+        @NonNull
+        public TextView getTitle() {
+            return mTitle;
+        }
+
+        /**
+         * Returns the body view within this view holder's view.
+         *
+         * @return Body view within this view holder's view.
+         */
+        @NonNull
+        public TextView getBody() {
+            return mBody;
+        }
+
+        /**
+         * Returns the compound button divider view within this view holder's view.
+         *
+         * @return Compound button divider view within this view holder's view.
+         */
+        @NonNull
+        public View getCompoundButtonDivider() {
+            return mCompoundButtonDivider;
+        }
+
+        /**
+         * Returns the compound button within this view holder's view.
+         *
+         * @return Compound button within this view holder's view.
+         */
+        @NonNull
+        public CompoundButton getCompoundButton() {
+            return mCompoundButton;
+        }
+
+        @NonNull
+        Guideline getSupplementalGuideline() {
+            return mSupplementalGuideline;
+        }
+
+        @NonNull
+        View[] getWidgetViews() {
+            return mWidgetViews;
+        }
+
+        /**
+         * Returns the container layout of this view holder.
+         *
+         * @return Container layout of this view holder.
+         */
+        @NonNull
+        public ViewGroup getContainerLayout() {
+            return mContainerLayout;
+        }
+    }
+}
diff --git a/car/core/src/main/java/androidx/car/widget/CompoundButtonListItem.java b/car/core/src/main/java/androidx/car/widget/CompoundButtonListItem.java
new file mode 100644
index 0000000..42876f6
--- /dev/null
+++ b/car/core/src/main/java/androidx/car/widget/CompoundButtonListItem.java
@@ -0,0 +1,660 @@
+/*
+ * Copyright 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.car.widget;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.DimenRes;
+import androidx.annotation.Dimension;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.car.R;
+import androidx.car.widget.ListItemAdapter.ListItemType;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.Guideline;
+
+import java.lang.annotation.Retention;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * Class to build a list item with {@link CompoundButton}.
+ *
+ * <p>A compound button list item is visually composed of 5 parts.
+ * <ul>
+ * <li>optional {@code Primary Action Icon}.
+ * <li>optional {@code Title}.
+ * <li>optional {@code Body}.
+ * <li>optional {@code Divider}.
+ * <li>A {@link CompoundButton}.
+ * </ul>
+ *
+ * @param <VH> ViewHolder that extends {@link CompoundButtonListItem.ViewHolder}.
+ */
+public abstract class CompoundButtonListItem<VH extends CompoundButtonListItem.ViewHolder> extends
+        ListItem<VH> {
+
+    @Retention(SOURCE)
+    @IntDef({
+            PRIMARY_ACTION_ICON_SIZE_SMALL, PRIMARY_ACTION_ICON_SIZE_MEDIUM,
+            PRIMARY_ACTION_ICON_SIZE_LARGE})
+    private @interface PrimaryActionIconSize {
+    }
+
+    /**
+     * Small sized icon is the mostly commonly used size. It's the same as supplemental action icon.
+     */
+    public static final int PRIMARY_ACTION_ICON_SIZE_SMALL = 0;
+    /**
+     * Medium sized icon is slightly bigger than {@code SMALL} ones. It is intended for profile
+     * pictures (avatar), in which case caller is responsible for passing in a circular image.
+     */
+    public static final int PRIMARY_ACTION_ICON_SIZE_MEDIUM = 1;
+    /**
+     * Large sized icon is as tall as a list item with only {@code title} text. It is intended for
+     * album art.
+     */
+    public static final int PRIMARY_ACTION_ICON_SIZE_LARGE = 2;
+
+    @Retention(SOURCE)
+    @IntDef({
+            PRIMARY_ACTION_TYPE_NO_ICON, PRIMARY_ACTION_TYPE_EMPTY_ICON,
+            PRIMARY_ACTION_TYPE_ICON})
+    private @interface PrimaryActionType {
+    }
+
+    private static final int PRIMARY_ACTION_TYPE_NO_ICON = 0;
+    private static final int PRIMARY_ACTION_TYPE_EMPTY_ICON = 1;
+    private static final int PRIMARY_ACTION_TYPE_ICON = 2;
+
+    private final Context mContext;
+    private boolean mIsEnabled = true;
+    private boolean mIsClickable;
+
+    private final List<ViewBinder<ViewHolder>> mBinders = new ArrayList<>();
+
+    @PrimaryActionType
+    private int mPrimaryActionType = PRIMARY_ACTION_TYPE_NO_ICON;
+    private Drawable mPrimaryActionIconDrawable;
+    @PrimaryActionIconSize
+    private int mPrimaryActionIconSize = PRIMARY_ACTION_ICON_SIZE_SMALL;
+
+    private CharSequence mTitle;
+    private CharSequence mBody;
+
+    @Dimension
+    private final int mSupplementalGuidelineBegin;
+
+    private boolean mIsChecked;
+    /**
+     * {@code true} if the checked state of the item has changed programmatically and
+     * {@link #mOnCheckedChangeListener} needs to be notified.
+     */
+    private boolean mShouldNotifyChecked;
+    private boolean mShowCompoundButtonDivider;
+    private CompoundButton.OnCheckedChangeListener mOnCheckedChangeListener;
+
+    /**
+     * Creates a {@link CompoundButtonListItem} that will be used to display a list item with a
+     * {@link CompoundButton}.
+     *
+     * @param context The context to be used by this {@link CompoundButtonListItem}.
+     */
+    public CompoundButtonListItem(@NonNull Context context) {
+        mContext = context;
+        Resources res = mContext.getResources();
+        mSupplementalGuidelineBegin = res.getDimensionPixelSize(
+                R.dimen.car_list_item_supplemental_guideline_top);
+        markDirty();
+    }
+
+    /**
+     * Classes that extend {@link CompoundButtonListItem} should register its view type in
+     * {@link ListItemAdapter#registerListItemViewType(int, int, Function)}.
+     *
+     * @return Type of this {@link CompoundButtonListItem}.
+     */
+    @ListItemType
+    @Override
+    public abstract int getViewType();
+
+    /**
+     * Calculates the layout params for views in {@link ViewHolder}.
+     */
+    @Override
+    @CallSuper
+    protected void resolveDirtyState() {
+        mBinders.clear();
+
+        // Create binders that adjust layout params of each view.
+        setPrimaryAction();
+        setText();
+        setCompoundButton();
+        setItemClickable();
+    }
+
+    /**
+     * Hides all views in {@link ViewHolder} then applies ViewBinders to adjust view layout params.
+     */
+    @Override
+    public void onBind(@NonNull VH viewHolder) {
+        hideSubViews(viewHolder);
+        for (ViewBinder binder : mBinders) {
+            binder.bind(viewHolder);
+        }
+
+        for (View v : viewHolder.getWidgetViews()) {
+            v.setEnabled(mIsEnabled);
+        }
+        // SwitchListItem supports clicking on the item so we also update the entire itemView.
+        viewHolder.itemView.setEnabled(mIsEnabled);
+    }
+
+    @Override
+    public void setEnabled(boolean isEnabled) {
+        mIsEnabled = isEnabled;
+    }
+
+    /**
+     * Sets whether the item is clickable. If {@code true}, clicking item toggles the compound
+     * button.
+     */
+    public void setClickable(boolean isClickable) {
+        mIsClickable = isClickable;
+        markDirty();
+    }
+
+    /**
+     * Sets {@code Primary Action} to be represented by an icon.
+     *
+     * @param drawable the {@link Drawable} to set.
+     * @param size     small/medium/large. Available as {@link #PRIMARY_ACTION_ICON_SIZE_SMALL},
+     *                 {@link #PRIMARY_ACTION_ICON_SIZE_MEDIUM},
+     *                 {@link #PRIMARY_ACTION_ICON_SIZE_LARGE}.
+     */
+    public void setPrimaryActionIcon(@NonNull Drawable drawable, @PrimaryActionIconSize int size) {
+        mPrimaryActionType = PRIMARY_ACTION_TYPE_ICON;
+        mPrimaryActionIconDrawable = drawable;
+        mPrimaryActionIconSize = size;
+        markDirty();
+    }
+
+    /**
+     * Sets {@code Primary Action} to be represented by an icon.
+     *
+     * @param iconResId the resource identifier of the drawable.
+     * @param size      small/medium/large. Available as {@link #PRIMARY_ACTION_ICON_SIZE_SMALL},
+     *                  {@link #PRIMARY_ACTION_ICON_SIZE_MEDIUM},
+     *                  {@link #PRIMARY_ACTION_ICON_SIZE_LARGE}.
+     */
+    public void setPrimaryActionIcon(@DrawableRes int iconResId, @PrimaryActionIconSize int size) {
+        setPrimaryActionIcon(mContext.getDrawable(iconResId), size);
+    }
+
+    /**
+     * Sets {@code Primary Action} to be empty icon.
+     *
+     * <p>{@code Text} would have a start margin as if {@code Primary Action} were set to primary
+     * icon.
+     */
+    public void setPrimaryActionEmptyIcon() {
+        mPrimaryActionType = PRIMARY_ACTION_TYPE_EMPTY_ICON;
+        markDirty();
+    }
+
+    /**
+     * Sets {@code Primary Action} to have no icon. Text would align to the start of item.
+     */
+    public void setPrimaryActionNoIcon() {
+        mPrimaryActionType = PRIMARY_ACTION_TYPE_NO_ICON;
+        markDirty();
+    }
+
+    /**
+     * Sets the title of item.
+     *
+     * <p>{@code Title} text is limited to one line, and ellipses at the end.
+     *
+     * @param title text to display as title.
+     */
+    public void setTitle(@Nullable CharSequence title) {
+        mTitle = title;
+        markDirty();
+    }
+
+    /**
+     * Sets the body text of item.
+     *
+     * <p>Text beyond length required by regulation will be truncated.
+     *
+     * @param body text to be displayed.
+     */
+    public void setBody(@Nullable CharSequence body) {
+        mBody = body;
+        markDirty();
+    }
+
+    /**
+     * Sets the state of {@link CompoundButton}.
+     *
+     * @param isChecked sets the "checked/unchecked, namely on/off" state of compound button.
+     */
+    public void setChecked(boolean isChecked) {
+        if (mIsChecked == isChecked) {
+            return;
+        }
+        mIsChecked = isChecked;
+        mShouldNotifyChecked = true;
+        markDirty();
+    }
+
+    /**
+     * Registers a callback to be invoked when the checked state of compound button changes.
+     *
+     * @param listener callback to be invoked when the checked state shown in the UI changes.
+     */
+    public void setOnCheckedChangeListener(
+            @Nullable CompoundButton.OnCheckedChangeListener listener) {
+        mOnCheckedChangeListener = listener;
+        // This method invalidates previous listener. Reset so that we *only*
+        // notify when the checked state changes and not on the initial bind.
+        mShouldNotifyChecked = false;
+        markDirty();
+    }
+
+    /**
+     * Sets whether to display a vertical bar between compound button and text.
+     */
+    public void setShowCompoundButtonDivider(boolean showCompoundButtonDivider) {
+        mShowCompoundButtonDivider = showCompoundButtonDivider;
+        markDirty();
+    }
+
+    private void hideSubViews(ViewHolder vh) {
+        for (View v : vh.getWidgetViews()) {
+            v.setVisibility(View.GONE);
+        }
+    }
+
+    private void setPrimaryAction() {
+        setPrimaryIconContent();
+        setPrimaryIconLayout();
+    }
+
+    private void setText() {
+        setTextContent();
+        setTextVerticalMargin();
+        setTextStartMargin();
+        setTextEndMargin();
+    }
+
+    private void setPrimaryIconContent() {
+        switch (mPrimaryActionType) {
+            case PRIMARY_ACTION_TYPE_ICON:
+                mBinders.add(vh -> {
+                    vh.getPrimaryIcon().setVisibility(View.VISIBLE);
+                    vh.getPrimaryIcon().setImageDrawable(mPrimaryActionIconDrawable);
+                });
+                break;
+            case PRIMARY_ACTION_TYPE_EMPTY_ICON:
+                // Do nothing.
+                break;
+            case PRIMARY_ACTION_TYPE_NO_ICON:
+                mBinders.add(vh -> {
+                    vh.getPrimaryIcon().setVisibility(View.GONE);
+                });
+                break;
+            default:
+                throw new IllegalStateException("Unknown primary action type.");
+        }
+    }
+
+    /**
+     * Returns whether the compound button will be placed at the end of the list item layout. This
+     * value is used to determine start margins for the {@code Title} and {@code Body}.
+     *
+     * @return Whether compound button is placed at the end of the list item layout.
+     */
+    public abstract boolean isCompoundButtonPositionEnd();
+
+    /**
+     * Sets the size, start margin, and vertical position of primary icon.
+     *
+     * <p>Large icon will have no start margin, and always align center vertically.
+     *
+     * <p>Small/medium icon will have start margin, and uses a top margin such that it is "pinned"
+     * at the same position in list item regardless of item height.
+     */
+    private void setPrimaryIconLayout() {
+        if (mPrimaryActionType == PRIMARY_ACTION_TYPE_EMPTY_ICON
+                || mPrimaryActionType == PRIMARY_ACTION_TYPE_NO_ICON) {
+            return;
+        }
+
+        // Size of icon.
+        @DimenRes int sizeResId;
+        switch (mPrimaryActionIconSize) {
+            case PRIMARY_ACTION_ICON_SIZE_SMALL:
+                sizeResId = R.dimen.car_primary_icon_size;
+                break;
+            case PRIMARY_ACTION_ICON_SIZE_MEDIUM:
+                sizeResId = R.dimen.car_avatar_icon_size;
+                break;
+            case PRIMARY_ACTION_ICON_SIZE_LARGE:
+                sizeResId = R.dimen.car_single_line_list_item_height;
+                break;
+            default:
+                throw new IllegalStateException("Unknown primary action icon size.");
+        }
+
+        int iconSize = mContext.getResources().getDimensionPixelSize(sizeResId);
+
+        // Start margin of icon.
+        int startMargin;
+        switch (mPrimaryActionIconSize) {
+            case PRIMARY_ACTION_ICON_SIZE_SMALL:
+            case PRIMARY_ACTION_ICON_SIZE_MEDIUM:
+                startMargin = mContext.getResources().getDimensionPixelSize(R.dimen.car_keyline_1);
+                break;
+            case PRIMARY_ACTION_ICON_SIZE_LARGE:
+                startMargin = 0;
+                break;
+            default:
+                throw new IllegalStateException("Unknown primary action icon size.");
+        }
+
+        mBinders.add(vh -> {
+            ConstraintLayout.LayoutParams layoutParams =
+                    (ConstraintLayout.LayoutParams) vh.getPrimaryIcon().getLayoutParams();
+            layoutParams.height = layoutParams.width = iconSize;
+            layoutParams.setMarginStart(startMargin);
+
+            if (mPrimaryActionIconSize == PRIMARY_ACTION_ICON_SIZE_LARGE) {
+                // A large icon is always vertically centered.
+                layoutParams.verticalBias = 0.5f;
+                layoutParams.topMargin = 0;
+            } else {
+                // Align the icon to the top of the parent. This allows the topMargin to shift it
+                // down relative to the top.
+                layoutParams.verticalBias = 0f;
+
+                // For all other icon sizes, the icon should be centered within the height of
+                // car_double_line_list_item_height. Note: the actual height of the item can be
+                // larger than this.
+                int itemHeight = mContext.getResources().getDimensionPixelSize(
+                        R.dimen.car_double_line_list_item_height);
+                layoutParams.topMargin = (itemHeight - iconSize) / 2;
+            }
+
+            vh.getPrimaryIcon().requestLayout();
+        });
+    }
+
+    private void setTextContent() {
+        boolean hasTitle = !TextUtils.isEmpty(mTitle);
+        boolean hasBody = !TextUtils.isEmpty(mBody);
+
+        if (!hasTitle && !hasBody) {
+            return;
+        }
+
+        mBinders.add(vh -> {
+            if (hasTitle) {
+                vh.getTitle().setVisibility(View.VISIBLE);
+                vh.getTitle().setText(mTitle);
+            }
+
+            if (hasBody) {
+                vh.getBody().setVisibility(View.VISIBLE);
+                vh.getBody().setText(mBody);
+            }
+
+            if (hasTitle && !hasBody) {
+                // If only title, then center the supplemental actions.
+                vh.getSupplementalGuideline().setGuidelineBegin(
+                        ConstraintLayout.LayoutParams.UNSET);
+                vh.getSupplementalGuideline().setGuidelinePercent(0.5f);
+            } else {
+                // Otherwise, position it a fixed distance from the top.
+                vh.getSupplementalGuideline().setGuidelinePercent(
+                        ConstraintLayout.LayoutParams.UNSET);
+                vh.getSupplementalGuideline().setGuidelineBegin(
+                        mSupplementalGuidelineBegin);
+            }
+        });
+    }
+
+    /**
+     * Sets start margin of text view depending on icon type.
+     */
+    private void setTextStartMargin() {
+        @DimenRes int startMarginResId;
+        if (!isCompoundButtonPositionEnd()) {
+            startMarginResId = R.dimen.car_keyline_3;
+        } else {
+            switch (mPrimaryActionType) {
+                case PRIMARY_ACTION_TYPE_NO_ICON:
+                    startMarginResId = R.dimen.car_keyline_1;
+                    break;
+                case PRIMARY_ACTION_TYPE_EMPTY_ICON:
+                    startMarginResId = R.dimen.car_keyline_3;
+                    break;
+                case PRIMARY_ACTION_TYPE_ICON:
+                    startMarginResId = mPrimaryActionIconSize == PRIMARY_ACTION_ICON_SIZE_LARGE
+                            ? R.dimen.car_keyline_4
+                            : R.dimen.car_keyline_3;  // Small and medium sized icon.
+                    break;
+                default:
+                    throw new IllegalStateException("Unknown primary action type.");
+            }
+        }
+
+        int startMargin = mContext.getResources().getDimensionPixelSize(startMarginResId);
+        mBinders.add(vh -> {
+            MarginLayoutParams titleLayoutParams =
+                    (MarginLayoutParams) vh.getTitle().getLayoutParams();
+            titleLayoutParams.setMarginStart(startMargin);
+            vh.getTitle().requestLayout();
+
+            MarginLayoutParams bodyLayoutParams =
+                    (MarginLayoutParams) vh.getBody().getLayoutParams();
+            bodyLayoutParams.setMarginStart(startMargin);
+            vh.getBody().requestLayout();
+        });
+    }
+
+    private void setTextEndMargin() {
+        int endMargin = mContext.getResources().getDimensionPixelSize(R.dimen.car_padding_4);
+
+        mBinders.add(vh -> {
+            MarginLayoutParams titleLayoutParams =
+                    (MarginLayoutParams) vh.getTitle().getLayoutParams();
+            titleLayoutParams.setMarginEnd(endMargin);
+
+            MarginLayoutParams bodyLayoutParams =
+                    (MarginLayoutParams) vh.getBody().getLayoutParams();
+            bodyLayoutParams.setMarginEnd(endMargin);
+        });
+    }
+
+    /**
+     * Sets top/bottom margins of {@code Title} and {@code Body}.
+     */
+    private void setTextVerticalMargin() {
+        // Set all relevant fields in layout params to avoid carried over params when the item
+        // gets bound to a recycled view holder.
+        if (!TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mBody)) {
+            // Title only - view is aligned center vertically by itself.
+            mBinders.add(vh -> {
+                MarginLayoutParams layoutParams =
+                        (MarginLayoutParams) vh.getTitle().getLayoutParams();
+                layoutParams.topMargin = 0;
+                vh.getTitle().requestLayout();
+            });
+        } else if (TextUtils.isEmpty(mTitle) && !TextUtils.isEmpty(mBody)) {
+            mBinders.add(vh -> {
+                // Body uses top and bottom margin.
+                int margin = mContext.getResources().getDimensionPixelSize(
+                        R.dimen.car_padding_3);
+                MarginLayoutParams layoutParams =
+                        (MarginLayoutParams) vh.getBody().getLayoutParams();
+                layoutParams.topMargin = margin;
+                layoutParams.bottomMargin = margin;
+                vh.getBody().requestLayout();
+            });
+        } else {
+            mBinders.add(vh -> {
+                Resources resources = mContext.getResources();
+                int padding2 = resources.getDimensionPixelSize(R.dimen.car_padding_2);
+
+                // Title has a top margin
+                MarginLayoutParams titleLayoutParams =
+                        (MarginLayoutParams) vh.getTitle().getLayoutParams();
+                titleLayoutParams.topMargin = padding2;
+                vh.getTitle().requestLayout();
+
+                // Body is below title with no margin and has bottom margin.
+                MarginLayoutParams bodyLayoutParams =
+                        (MarginLayoutParams) vh.getBody().getLayoutParams();
+                bodyLayoutParams.topMargin = 0;
+                bodyLayoutParams.bottomMargin = padding2;
+                vh.getBody().requestLayout();
+            });
+        }
+    }
+
+    /**
+     * Sets up view(s) for supplemental action.
+     */
+    private void setCompoundButton() {
+        mBinders.add(vh -> {
+            vh.getCompoundButton().setVisibility(View.VISIBLE);
+            vh.getCompoundButton().setOnCheckedChangeListener(null);
+            vh.getCompoundButton().setChecked(mIsChecked);
+            vh.getCompoundButton().setOnCheckedChangeListener((buttonView, isChecked) -> {
+                if (mOnCheckedChangeListener != null) {
+                    // The checked state changed via user interaction with the compound button.
+                    mOnCheckedChangeListener.onCheckedChanged(buttonView, isChecked);
+                }
+                mIsChecked = isChecked;
+            });
+            if (mShouldNotifyChecked && mOnCheckedChangeListener != null) {
+                // The checked state was changed programmatically.
+                mOnCheckedChangeListener.onCheckedChanged(vh.getCompoundButton(),
+                        mIsChecked);
+                mShouldNotifyChecked = false;
+            }
+
+            if (mShowCompoundButtonDivider) {
+                vh.getCompoundButtonDivider().setVisibility(View.VISIBLE);
+            }
+        });
+    }
+
+    private void setItemClickable() {
+        mBinders.add(vh -> {
+            // If applicable (namely item is clickable), clicking item always toggles the
+            // compound button.
+            vh.itemView.setOnClickListener(v -> vh.getCompoundButton().toggle());
+            vh.itemView.setClickable(mIsClickable);
+        });
+    }
+
+    /**
+     * Holds views of CompoundButtonListItem.
+     */
+    public abstract static class ViewHolder extends ListItem.ViewHolder {
+
+        /**
+         * Creates a {@link ViewHolder} for a {@link CompoundButtonListItem}.
+         *
+         * @param itemView The view to be used to display a {@link CompoundButtonListItem}.
+         */
+        public ViewHolder(@NonNull View itemView) {
+            super(itemView);
+        }
+
+        /**
+         * Returns the primary icon view within this view holder's view.
+         *
+         * @return Icon view within this view holder's view.
+         */
+        @NonNull
+        public abstract ImageView getPrimaryIcon();
+
+        /**
+         * Returns the title view within this view holder's view.
+         *
+         * @return Title view within this view holder's view.
+         */
+        @NonNull
+        public abstract TextView getTitle();
+
+        /**
+         * Returns the body view within this view holder's view.
+         *
+         * @return Body view within this view holder's view.
+         */
+        @NonNull
+        public abstract TextView getBody();
+
+        /**
+         * Returns the compound button divider view within this view holder's view.
+         *
+         * @return Compound button divider view within this view holder's view.
+         */
+        @NonNull
+        public abstract View getCompoundButtonDivider();
+
+        /**
+         * Returns the compound button within this view holder's view.
+         *
+         * @return Compound button within this view holder's view.
+         */
+        @NonNull
+        public abstract CompoundButton getCompoundButton();
+
+        @NonNull
+        abstract Guideline getSupplementalGuideline();
+
+        @NonNull
+        abstract ViewGroup getContainerLayout();
+
+        /**
+         * Returns the container layout of this view holder.
+         *
+         * @return Container layout of this view holder.
+         */
+        @NonNull
+        abstract View[] getWidgetViews();
+    }
+}
diff --git a/car/core/src/main/java/androidx/car/widget/ListItemAdapter.java b/car/core/src/main/java/androidx/car/widget/ListItemAdapter.java
index 5bf1e6a..916bddb 100644
--- a/car/core/src/main/java/androidx/car/widget/ListItemAdapter.java
+++ b/car/core/src/main/java/androidx/car/widget/ListItemAdapter.java
@@ -95,12 +95,27 @@
      */
     public static final int BACKGROUND_STYLE_PANEL = 3;
 
+    /** @hide */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({
+            LIST_ITEM_TYPE_TEXT,
+            LIST_ITEM_TYPE_SEEKBAR,
+            LIST_ITEM_TYPE_SUBHEADER,
+            LIST_ITEM_TYPE_ACTION,
+            LIST_ITEM_TYPE_RADIO,
+            LIST_ITEM_TYPE_SWITCH,
+            LIST_ITEM_TYPE_CHECK_BOX})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ListItemType {
+    }
+
     static final int LIST_ITEM_TYPE_TEXT = 1;
     static final int LIST_ITEM_TYPE_SEEKBAR = 2;
     static final int LIST_ITEM_TYPE_SUBHEADER = 3;
     static final int LIST_ITEM_TYPE_ACTION = 4;
     static final int LIST_ITEM_TYPE_RADIO = 5;
     static final int LIST_ITEM_TYPE_SWITCH = 6;
+    static final int LIST_ITEM_TYPE_CHECK_BOX = 7;
 
     private final SparseIntArray mViewHolderLayoutResIds = new SparseIntArray();
 
@@ -146,6 +161,8 @@
                 R.layout.car_list_item_radio_content, RadioButtonListItem::createViewHolder);
         registerListItemViewTypeInternal(LIST_ITEM_TYPE_SWITCH,
                 R.layout.car_list_item_switch_content, SwitchListItem::createViewHolder);
+        registerListItemViewTypeInternal(LIST_ITEM_TYPE_CHECK_BOX,
+                R.layout.car_list_item_check_box_content, CheckBoxListItem::createViewHolder);
 
         mUxRestrictionsHelper =
                 new CarUxRestrictionsHelper(context, carUxRestrictions -> {
diff --git a/car/core/src/main/java/androidx/car/widget/RadioButtonListItem.java b/car/core/src/main/java/androidx/car/widget/RadioButtonListItem.java
index 36b05fc..84b20c8 100644
--- a/car/core/src/main/java/androidx/car/widget/RadioButtonListItem.java
+++ b/car/core/src/main/java/androidx/car/widget/RadioButtonListItem.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018 The Android Open Source Project
+ * Copyright 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.
@@ -16,11 +16,7 @@
 
 package androidx.car.widget;
 
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
 import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.CompoundButton;
@@ -28,483 +24,187 @@
 import android.widget.RadioButton;
 import android.widget.TextView;
 
-import androidx.annotation.DimenRes;
-import androidx.annotation.Dimension;
-import androidx.annotation.DrawableRes;
-import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.car.R;
 import androidx.car.util.CarUxRestrictionsUtils;
-
-import java.lang.annotation.Retention;
-import java.util.ArrayList;
-import java.util.List;
+import androidx.car.uxrestrictions.CarUxRestrictions;
+import androidx.car.widget.ListItemAdapter.ListItemType;
+import androidx.constraintlayout.widget.Guideline;
 
 /**
  * Class to build a list item with {@link RadioButton}.
  *
- * <p>A radio button list item visually composes of 4 parts.
+ * <p>A radio button list item is visually composed of 5 parts.
  * <ul>
+ * <li>A {@link RadioButton}.
+ * <li>optional {@code Divider}.
  * <li>optional {@code Primary Action Icon}.
  * <li>optional {@code Title}.
  * <li>optional {@code Body}.
- * <li>A {@link RadioButton}.
  * </ul>
- *
- * <p>Clicking the item always checks the radio button.
  */
-public class RadioButtonListItem extends ListItem<RadioButtonListItem.ViewHolder> {
-
-    @Retention(SOURCE)
-    @IntDef({
-            PRIMARY_ACTION_ICON_SIZE_SMALL, PRIMARY_ACTION_ICON_SIZE_MEDIUM,
-            PRIMARY_ACTION_ICON_SIZE_LARGE})
-    private @interface PrimaryActionIconSize {
-    }
+public final class RadioButtonListItem extends
+        CompoundButtonListItem<RadioButtonListItem.ViewHolder> {
 
     /**
-     * Small sized icon is the mostly commonly used size.
-     */
-    public static final int PRIMARY_ACTION_ICON_SIZE_SMALL = 0;
-    /**
-     * Medium sized icon is slightly bigger than {@code SMALL} ones. It is intended for profile
-     * pictures (avatar), in which case caller is responsible for passing in a circular image.
-     */
-    public static final int PRIMARY_ACTION_ICON_SIZE_MEDIUM = 1;
-    /**
-     * Large sized icon is as tall as a list item with only {@code title} text. It is intended for
-     * album art.
-     */
-    public static final int PRIMARY_ACTION_ICON_SIZE_LARGE = 2;
-
-    private final List<ViewBinder<ViewHolder>> mBinders = new ArrayList<>();
-    private final Context mContext;
-    private boolean mIsEnabled = true;
-
-    private Drawable mPrimaryActionIconDrawable;
-    @PrimaryActionIconSize private int mPrimaryActionIconSize = PRIMARY_ACTION_ICON_SIZE_SMALL;
-
-    @Dimension(unit = Dimension.PX)
-    private int mTextStartMargin;
-    private CharSequence mTitle;
-    private CharSequence mBody;
-
-    private boolean mIsChecked;
-    private boolean mShowRadioButtonDivider;
-    private CompoundButton.OnCheckedChangeListener mRadioButtonOnCheckedChangeListener;
-
-    /**
-     * Creates a {@link RadioButtonListItem.ViewHolder}.
+     * Creates a {@link ViewHolder}.
+     *
+     * @return a {@link ViewHolder} for this {@link RadioButtonListItem}.
      */
     @NonNull
     public static ViewHolder createViewHolder(@NonNull View itemView) {
         return new ViewHolder(itemView);
     }
 
+    /**
+     * Creates a {@link RadioButtonListItem} that will be used to display a list item with a
+     * {@link RadioButton}.
+     *
+     * @param context The context to be used by this {@link RadioButtonListItem}.
+     */
     public RadioButtonListItem(@NonNull Context context) {
-        mContext = context;
-        mTextStartMargin = mContext.getResources().getDimensionPixelSize(R.dimen.car_keyline_3);
-        markDirty();
+        super(context);
+    }
+
+    /**
+     * Returns whether the compound button will be placed at the end of the list item layout. This
+     * value is used to determine start margins for the {@code Title} and {@code Body}.
+     *
+     * @return Whether compound button is placed at the end of the list item layout.
+     */
+    @Override
+    public boolean isCompoundButtonPositionEnd() {
+        return false;
     }
 
     /**
      * Used by {@link ListItemAdapter} to choose layout to inflate for view holder.
+     *
+     * @return Type of this {@link CompoundButtonListItem}.
      */
+    @ListItemType
     @Override
     public int getViewType() {
         return ListItemAdapter.LIST_ITEM_TYPE_RADIO;
     }
 
-    @Override
-    public void setEnabled(boolean enabled) {
-        mIsEnabled = enabled;
-    }
-
-    @NonNull
-    protected Context getContext() {
-        return mContext;
-    }
-
     /**
-     * Sets the state of radio button.
-     *
-     * @param isChecked {@code true} to check the button; {@code false} to uncheck it.
+     * ViewHolder that contains necessary widgets for {@link RadioButtonListItem}.
      */
-    public void setChecked(boolean isChecked) {
-        if (mIsChecked == isChecked) {
-            return;
-        }
-        mIsChecked = isChecked;
-        markDirty();
-    }
+    public static final class ViewHolder extends CompoundButtonListItem.ViewHolder {
 
-    /**
-     * Get whether the radio button is checked.
-     *
-     * <p>The return value is in sync with UI state.
-     *
-     * @return {@code true} if the widget is checked; {@code false} otherwise.
-     */
-    public boolean isChecked() {
-        return mIsChecked;
-    }
-
-    /**
-     * Sets {@code Primary Action} to be represented by an icon. The size of icon automatically
-     * adjusts the start of {@code Text}.
-     *
-     * <p>Call {@link #setPrimaryActionNoIcon()} to clear the content and aligns text to the start
-     * of list item
-     *
-     * @param drawable the Drawable to set as primary action.
-     * @param size constant that represents the size of icon. See
-     *             {@link #PRIMARY_ACTION_ICON_SIZE_SMALL},
-     *             {@link #PRIMARY_ACTION_ICON_SIZE_MEDIUM}, and
-     *             {@link #PRIMARY_ACTION_ICON_SIZE_LARGE}.
-     */
-    public void setPrimaryActionIcon(@NonNull Drawable drawable, @PrimaryActionIconSize int size) {
-        mPrimaryActionIconDrawable = drawable;
-        mPrimaryActionIconSize = size;
-        markDirty();
-    }
-
-    /**
-     * Sets {@code Primary Action} to be represented by an icon. The size of icon automatically
-     * adjusts the start of {@code Text}.
-     *
-     * @param iconResId the resource identifier of the drawable.
-     * @param size constant that represents the size of icon. See
-     *             {@link #PRIMARY_ACTION_ICON_SIZE_SMALL},
-     *             {@link #PRIMARY_ACTION_ICON_SIZE_MEDIUM}, and
-     *             {@link #PRIMARY_ACTION_ICON_SIZE_LARGE}.
-     */
-    public void setPrimaryActionIcon(@DrawableRes int iconResId, @PrimaryActionIconSize int size) {
-        setPrimaryActionIcon(getContext().getDrawable(iconResId), size);
-    }
-
-    /**
-     * Sets {@code Primary Action} to have no icon. Text would align to the start of list item.
-     */
-    public void setPrimaryActionNoIcon() {
-        mPrimaryActionIconDrawable = null;
-        markDirty();
-    }
-
-    /**
-     * Sets title text to be displayed next to icon.
-     *
-     * @param text Text to be displayed, or {@code null} to clear the content.
-     */
-    public void setTitle(@Nullable CharSequence text) {
-        mTitle = text;
-        markDirty();
-    }
-
-    /**
-     * Sets body text to be displayed next to radio button.
-     *
-     * @param text Text to be displayed, or {@code null} to clear the content.
-     */
-    public void setBody(@Nullable CharSequence text) {
-        mBody = text;
-        markDirty();
-    }
-
-    /**
-     * Sets the start margin of text.
-     */
-    public void setTextStartMargin(@DimenRes int dimenRes) {
-        mTextStartMargin = mContext.getResources().getDimensionPixelSize(dimenRes);
-        markDirty();
-    }
-
-    /**
-     * Sets whether to display a vertical bar that separates {@code text} and radio button.
-     */
-    public void setShowRadioButtonDivider(boolean showDivider) {
-        mShowRadioButtonDivider = showDivider;
-        markDirty();
-    }
-
-    /**
-     * Sets {@link android.widget.CompoundButton.OnCheckedChangeListener} of radio button.
-     */
-    public void setOnCheckedChangeListener(
-            @NonNull CompoundButton.OnCheckedChangeListener listener) {
-        mRadioButtonOnCheckedChangeListener = listener;
-        markDirty();
-    }
-
-    /**
-     * Calculates the layout params for views in {@link ViewHolder}.
-     */
-    @Override
-    protected void resolveDirtyState() {
-        mBinders.clear();
-
-        // Create binders that adjust layout params of each view.
-        setPrimaryAction();
-        setTextInternal();
-        setRadioButton();
-        setOnClickListenerToCheckRadioButton();
-    }
-
-    private void setPrimaryAction() {
-        setPrimaryIconContent();
-        setPrimaryIconLayout();
-    }
-
-    private void setTextInternal() {
-        setTextContent();
-        setTextVerticalMargins();
-        setTextStartMargin();
-    }
-
-    private void setRadioButton() {
-        mBinders.add(vh -> {
-            // Clear listener before setting checked to avoid listener is notified every time
-            // we bind to view holder.
-            vh.getRadioButton().setOnCheckedChangeListener(null);
-            vh.getRadioButton().setChecked(mIsChecked);
-            // Keep internal checked state in sync with UI by wrapping listener.
-            vh.getRadioButton().setOnCheckedChangeListener((buttonView, isChecked) -> {
-                mIsChecked = isChecked;
-                if (mRadioButtonOnCheckedChangeListener != null) {
-                    mRadioButtonOnCheckedChangeListener.onCheckedChanged(buttonView, isChecked);
-                }
-            });
-
-            vh.getRadioButtonDivider().setVisibility(
-                    mShowRadioButtonDivider ? View.VISIBLE : View.GONE);
-        });
-    }
-
-    private void setPrimaryIconContent() {
-        mBinders.add(vh -> {
-            if (mPrimaryActionIconDrawable == null) {
-                vh.getPrimaryIcon().setVisibility(View.GONE);
-            } else {
-                vh.getPrimaryIcon().setVisibility(View.VISIBLE);
-                vh.getPrimaryIcon().setImageDrawable(mPrimaryActionIconDrawable);
-            }
-        });
-    }
-
-    /**
-     * Sets the size, start margin, and vertical position of primary icon.
-     *
-     * <p>Large icon will have no start margin, and always align center vertically.
-     *
-     * <p>Small/medium icon will have start margin, and uses a top margin such that it is "pinned"
-     * at the same position in list item regardless of item height.
-     */
-    private void setPrimaryIconLayout() {
-        if (mPrimaryActionIconDrawable == null) {
-            return;
-        }
-
-        // Size of icon.
-        @DimenRes int sizeResId;
-        switch (mPrimaryActionIconSize) {
-            case PRIMARY_ACTION_ICON_SIZE_SMALL:
-                sizeResId = R.dimen.car_primary_icon_size;
-                break;
-            case PRIMARY_ACTION_ICON_SIZE_MEDIUM:
-                sizeResId = R.dimen.car_avatar_icon_size;
-                break;
-            case PRIMARY_ACTION_ICON_SIZE_LARGE:
-                sizeResId = R.dimen.car_single_line_list_item_height;
-                break;
-            default:
-                throw new IllegalStateException("Unknown primary action icon size.");
-        }
-
-        int iconSize = mContext.getResources().getDimensionPixelSize(sizeResId);
-
-        // Start margin of icon.
-        int startMargin;
-        switch (mPrimaryActionIconSize) {
-            case PRIMARY_ACTION_ICON_SIZE_SMALL:
-            case PRIMARY_ACTION_ICON_SIZE_MEDIUM:
-                startMargin = mContext.getResources().getDimensionPixelSize(R.dimen.car_keyline_1);
-                break;
-            case PRIMARY_ACTION_ICON_SIZE_LARGE:
-                startMargin = 0;
-                break;
-            default:
-                throw new IllegalStateException("Unknown primary action icon size.");
-        }
-
-        mBinders.add(vh -> {
-            ViewGroup.MarginLayoutParams layoutParams =
-                    (ViewGroup.MarginLayoutParams) vh.getPrimaryIcon().getLayoutParams();
-            layoutParams.height = layoutParams.width = iconSize;
-            layoutParams.setMarginStart(startMargin);
-
-            vh.getPrimaryIcon().requestLayout();
-        });
-    }
-
-    private void setTextContent() {
-        if (!TextUtils.isEmpty(mTitle)) {
-            mBinders.add(vh -> {
-                vh.getTitle().setVisibility(View.VISIBLE);
-                vh.getTitle().setText(mTitle);
-            });
-        }
-
-        if (!TextUtils.isEmpty(mBody)) {
-            mBinders.add(vh -> {
-                vh.getBody().setVisibility(View.VISIBLE);
-                vh.getBody().setText(mBody);
-            });
-        } else {
-            mBinders.add(vh -> vh.getBody().setVisibility(View.GONE));
-        }
-    }
-
-    /**
-     * Sets top and bottom margins of text views depending on existence of other text view.
-     */
-    private void setTextVerticalMargins() {
-        if (TextUtils.isEmpty(mBody)) {
-            mBinders.add(vh -> {
-                ViewGroup.MarginLayoutParams textViewLayoutParams =
-                        (ViewGroup.MarginLayoutParams) vh.getTitle().getLayoutParams();
-                textViewLayoutParams.topMargin = 0;
-                vh.getTitle().requestLayout();
-            });
-        }
-
-        if (TextUtils.isEmpty(mTitle)) {
-            mBinders.add(vh -> {
-                ViewGroup.MarginLayoutParams textViewLayoutParams =
-                        (ViewGroup.MarginLayoutParams) vh.getBody().getLayoutParams();
-                textViewLayoutParams.bottomMargin = 0;
-                vh.getBody().requestLayout();
-            });
-        }
-    }
-
-    /**
-     * Sets start margin of text views.
-     */
-    private void setTextStartMargin() {
-        mBinders.add(vh -> {
-            ViewGroup.MarginLayoutParams textViewLayoutParams =
-                    (ViewGroup.MarginLayoutParams) vh.getTitle().getLayoutParams();
-            textViewLayoutParams.setMarginStart(mTextStartMargin);
-            vh.getTitle().requestLayout();
-
-            ViewGroup.MarginLayoutParams bodyTextViewLayoutParams =
-                    (ViewGroup.MarginLayoutParams) vh.getBody().getLayoutParams();
-            bodyTextViewLayoutParams.setMarginStart(mTextStartMargin);
-            vh.getBody().requestLayout();
-        });
-    }
-
-    // Clicking the item always checks radio button.
-    private void setOnClickListenerToCheckRadioButton() {
-        mBinders.add(vh -> {
-            vh.itemView.setClickable(true);
-            vh.itemView.setOnClickListener(v -> vh.getRadioButton().setChecked(true));
-        });
-    }
-
-    /**
-     * Hides all views in {@link ViewHolder} then applies ViewBinders to adjust view layout params.
-     */
-    @Override
-    protected void onBind(ViewHolder viewHolder) {
-        // Hide all subviews then apply view binders to adjust subviews.
-        hideSubViews(viewHolder);
-        for (ViewBinder binder : mBinders) {
-            binder.bind(viewHolder);
-        }
-
-        for (View v : viewHolder.getWidgetViews()) {
-            v.setEnabled(mIsEnabled);
-        }
-    }
-
-    private void hideSubViews(ViewHolder vh) {
-        for (View v : vh.getWidgetViews()) {
-            v.setVisibility(View.GONE);
-        }
-        // Radio button is always visible.
-        vh.getRadioButton().setVisibility(View.VISIBLE);
-    }
-
-    /**
-     * Holds views of RadioButtonListItem.
-     */
-    public static final class ViewHolder extends ListItem.ViewHolder {
-
-        private final View[] mWidgetViews;
+        private View[] mWidgetViews;
 
         private ViewGroup mContainerLayout;
 
         private ImageView mPrimaryIcon;
+
         private TextView mTitle;
         private TextView mBody;
 
-        private View mRadioButtonDivider;
-        private RadioButton mRadioButton;
+        private Guideline mSupplementalGuideline;
 
+        private CompoundButton mCompoundButton;
+        private View mCompoundButtonDivider;
+
+        /**
+         * Creates a {@link ViewHolder} for a {@link RadioButtonListItem}.
+         *
+         * @param itemView The view to be used to display a {@link RadioButtonListItem}.
+         */
         public ViewHolder(@NonNull View itemView) {
             super(itemView);
 
             mContainerLayout = itemView.findViewById(R.id.container);
 
             mPrimaryIcon = itemView.findViewById(R.id.primary_icon);
+
             mTitle = itemView.findViewById(R.id.title);
             mBody = itemView.findViewById(R.id.body);
 
-            mRadioButton = itemView.findViewById(R.id.radio_button);
-            mRadioButtonDivider = itemView.findViewById(R.id.radio_button_divider);
+            mSupplementalGuideline = itemView.findViewById(R.id.supplemental_actions_guideline);
+
+            mCompoundButton = itemView.findViewById(R.id.radiobutton_widget);
+            mCompoundButtonDivider = itemView.findViewById(R.id.radiobutton_divider);
 
             int minTouchSize = itemView.getContext().getResources()
                     .getDimensionPixelSize(R.dimen.car_touch_target_size);
-
-            MinTouchTargetHelper.ensureThat(mRadioButton)
-                    .hasMinTouchSize(minTouchSize);
+            MinTouchTargetHelper.ensureThat(mCompoundButton).hasMinTouchSize(minTouchSize);
 
             // Each line groups relevant child views in an effort to help keep this view array
             // updated with actual child views in the ViewHolder.
             mWidgetViews = new View[]{
-                    mPrimaryIcon, mTitle, mBody,
-                    mRadioButton, mRadioButtonDivider};
+                    mPrimaryIcon,
+                    mTitle, mBody,
+                    mCompoundButton, mCompoundButtonDivider,
+            };
         }
 
-        @NonNull
-        public ViewGroup getContainerLayout() {
-            return mContainerLayout;
+        /**
+         * Updates child views with current car UX restrictions.
+         *
+         * <p>{@code Text} might be truncated to meet length limit required by regulation.
+         *
+         * @param restrictionsInfo current car UX restrictions.
+         */
+        @Override
+        public void onUxRestrictionsChanged(@NonNull CarUxRestrictions restrictionsInfo) {
+            CarUxRestrictionsUtils.apply(itemView.getContext(), restrictionsInfo, getBody());
         }
 
+        /**
+         * Returns the primary icon view within this view holder's view.
+         *
+         * @return Icon view within this view holder's view.
+         */
         @NonNull
         public ImageView getPrimaryIcon() {
             return mPrimaryIcon;
         }
 
+        /**
+         * Returns the title view within this view holder's view.
+         *
+         * @return Title view within this view holder's view.
+         */
         @NonNull
         public TextView getTitle() {
             return mTitle;
         }
 
+        /**
+         * Returns the body view within this view holder's view.
+         *
+         * @return Body view within this view holder's view.
+         */
         @NonNull
         public TextView getBody() {
             return mBody;
         }
 
+        /**
+         * Returns the compound button divider view within this view holder's view.
+         *
+         * @return Compound button divider view within this view holder's view.
+         */
         @NonNull
-        public RadioButton getRadioButton() {
-            return mRadioButton;
+        public View getCompoundButtonDivider() {
+            return mCompoundButtonDivider;
+        }
+
+        /**
+         * Returns the compound button within this view holder's view.
+         *
+         * @return Compound button within this view holder's view.
+         */
+        @NonNull
+        public CompoundButton getCompoundButton() {
+            return mCompoundButton;
         }
 
         @NonNull
-        public View getRadioButtonDivider() {
-            return mRadioButtonDivider;
+        Guideline getSupplementalGuideline() {
+            return mSupplementalGuideline;
         }
 
         @NonNull
@@ -512,11 +212,14 @@
             return mWidgetViews;
         }
 
-        @Override
-        public void onUxRestrictionsChanged(
-                androidx.car.uxrestrictions.CarUxRestrictions restrictionInfo) {
-            CarUxRestrictionsUtils.apply(itemView.getContext(), restrictionInfo, getTitle());
-            CarUxRestrictionsUtils.apply(itemView.getContext(), restrictionInfo, getBody());
+        /**
+         * Returns the container layout of this view holder.
+         *
+         * @return Container layout of this view holder.
+         */
+        @NonNull
+        public ViewGroup getContainerLayout() {
+            return mContainerLayout;
         }
     }
 }
diff --git a/car/core/src/main/java/androidx/car/widget/SwitchListItem.java b/car/core/src/main/java/androidx/car/widget/SwitchListItem.java
index b461334..801486d 100644
--- a/car/core/src/main/java/androidx/car/widget/SwitchListItem.java
+++ b/car/core/src/main/java/androidx/car/widget/SwitchListItem.java
@@ -16,595 +16,85 @@
 
 package androidx.car.widget;
 
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
 import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
 import android.view.View;
-import android.view.ViewGroup.MarginLayoutParams;
+import android.view.ViewGroup;
 import android.widget.CompoundButton;
 import android.widget.ImageView;
 import android.widget.Switch;
 import android.widget.TextView;
 
-import androidx.annotation.CallSuper;
-import androidx.annotation.DimenRes;
-import androidx.annotation.Dimension;
-import androidx.annotation.DrawableRes;
-import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.car.R;
 import androidx.car.util.CarUxRestrictionsUtils;
 import androidx.car.uxrestrictions.CarUxRestrictions;
-import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.car.widget.ListItemAdapter.ListItemType;
 import androidx.constraintlayout.widget.Guideline;
 
-import java.lang.annotation.Retention;
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * Class to build a list item with {@link Switch}.
  *
- * <p>An item supports primary action and a switch as supplemental action.
- *
- * <p>An item visually composes of 3 parts; each part may contain multiple views.
+ * <p>A switch list item is visually composed of 5 parts.
  * <ul>
- * <li>{@code Primary Action}: represented by an icon of following types.
- * <ul>
- * <li>Primary Icon - icon size could be large or small.
- * <li>No Icon - no icon is shown.
- * <li>Empty Icon - {@code Text} offsets start space as if there was an icon.
+ * <li>A {@link Switch}.
+ * <li>optional {@code Divider}.
+ * <li>optional {@code Primary Action Icon}.
+ * <li>optional {@code Title}.
+ * <li>optional {@code Body}.
  * </ul>
- * <li>{@code Text}: supports any combination of the following text views.
- * <ul>
- * <li>Title
- * <li>Body
- * </ul>
- * <li>{@code Supplemental Action}: represented by {@link Switch}.
- * </ul>
- *
- * <p>{@code SwitchListItem} binds data to {@link ViewHolder} based on components selected.
- *
- * <p>When conflicting setter methods are called (e.g. setting primary action to both primary icon
- * and no icon), the last called method wins.
  */
-public class SwitchListItem extends ListItem<SwitchListItem.ViewHolder> {
-
-    @Retention(SOURCE)
-    @IntDef({
-            PRIMARY_ACTION_ICON_SIZE_SMALL, PRIMARY_ACTION_ICON_SIZE_MEDIUM,
-            PRIMARY_ACTION_ICON_SIZE_LARGE})
-    private @interface PrimaryActionIconSize {
-    }
-
-    /**
-     * Small sized icon is the mostly commonly used size. It's the same as supplemental action icon.
-     */
-    public static final int PRIMARY_ACTION_ICON_SIZE_SMALL = 0;
-    /**
-     * Medium sized icon is slightly bigger than {@code SMALL} ones. It is intended for profile
-     * pictures (avatar), in which case caller is responsible for passing in a circular image.
-     */
-    public static final int PRIMARY_ACTION_ICON_SIZE_MEDIUM = 1;
-    /**
-     * Large sized icon is as tall as a list item with only {@code title} text. It is intended for
-     * album art.
-     */
-    public static final int PRIMARY_ACTION_ICON_SIZE_LARGE = 2;
-
-    @Retention(SOURCE)
-    @IntDef({
-            PRIMARY_ACTION_TYPE_NO_ICON, PRIMARY_ACTION_TYPE_EMPTY_ICON,
-            PRIMARY_ACTION_TYPE_ICON})
-    private @interface PrimaryActionType {
-    }
-
-    private static final int PRIMARY_ACTION_TYPE_NO_ICON = 0;
-    private static final int PRIMARY_ACTION_TYPE_EMPTY_ICON = 1;
-    private static final int PRIMARY_ACTION_TYPE_ICON = 2;
-
-    private final Context mContext;
-    private boolean mIsEnabled = true;
-    private boolean mIsClickable;
-
-    private final List<ViewBinder<ViewHolder>> mBinders = new ArrayList<>();
-
-    @PrimaryActionType
-    private int mPrimaryActionType = PRIMARY_ACTION_TYPE_NO_ICON;
-    private Drawable mPrimaryActionIconDrawable;
-    @PrimaryActionIconSize
-    private int mPrimaryActionIconSize = PRIMARY_ACTION_ICON_SIZE_SMALL;
-
-    private CharSequence mTitle;
-    private CharSequence mBody;
-
-    @Dimension
-    private final int mSupplementalGuidelineBegin;
-
-    private boolean mSwitchChecked;
-    /**
-     * {@code true} if the checked state of the switch has changed programmatically and
-     * {@link #mSwitchOnCheckedChangeListener} needs to be notified.
-     */
-    private boolean mShouldNotifySwitchChecked;
-    private boolean mShowSwitchDivider;
-    private CompoundButton.OnCheckedChangeListener mSwitchOnCheckedChangeListener;
+public final class SwitchListItem extends CompoundButtonListItem<SwitchListItem.ViewHolder> {
 
     /**
      * Creates a {@link ViewHolder}.
+     *
+     * @return a {@link ViewHolder} for this {@link SwitchListItem}.
      */
     @NonNull
-    public static ViewHolder createViewHolder(View itemView) {
+    public static ViewHolder createViewHolder(@NonNull View itemView) {
         return new ViewHolder(itemView);
     }
 
-    public SwitchListItem(@NonNull Context context) {
-        mContext = context;
-        mSupplementalGuidelineBegin = mContext.getResources().getDimensionPixelSize(
-                R.dimen.car_list_item_supplemental_guideline_top);
-        markDirty();
-    }
-
     /**
      * Used by {@link ListItemAdapter} to choose layout to inflate for view holder.
+     *
+     * @return Type of this {@link CompoundButtonListItem}.
      */
+    @ListItemType
     @Override
     public int getViewType() {
         return ListItemAdapter.LIST_ITEM_TYPE_SWITCH;
     }
 
     /**
-     * Calculates the layout params for views in {@link ViewHolder}.
+     * Creates a {@link SwitchListItem} that will be used to display a list item with a
+     * {@link Switch}.
+     *
+     * @param context The context to be used by this {@link SwitchListItem}.
+     */
+    public SwitchListItem(@NonNull Context context) {
+        super(context);
+    }
+
+    /**
+     * Returns whether the compound button will be placed at the end of the list item layout. This
+     * value is used to determine start margins for the {@code Title} and {@code Body}.
+     *
+     * @return Whether compound button is placed at the end of the list item layout.
      */
     @Override
-    @CallSuper
-    protected void resolveDirtyState() {
-        mBinders.clear();
-
-        // Create binders that adjust layout params of each view.
-        setPrimaryAction();
-        setText();
-        setSwitch();
-        setItemClickable();
+    public boolean isCompoundButtonPositionEnd() {
+        return true;
     }
 
     /**
-     * Hides all views in {@link ViewHolder} then applies ViewBinders to adjust view layout params.
+     * ViewHolder that contains necessary widgets for {@link SwitchListItem}.
      */
-    @Override
-    public void onBind(ViewHolder viewHolder) {
-        hideSubViews(viewHolder);
-        for (ViewBinder binder : mBinders) {
-            binder.bind(viewHolder);
-        }
+    public static final class ViewHolder extends CompoundButtonListItem.ViewHolder {
 
-        for (View v : viewHolder.getWidgetViews()) {
-            v.setEnabled(mIsEnabled);
-        }
-        // SwitchListItem supports clicking on the item so we also update the entire itemView.
-        viewHolder.itemView.setEnabled(mIsEnabled);
-    }
+        private View[] mWidgetViews;
 
-    @Override
-    public void setEnabled(boolean enabled) {
-        mIsEnabled = enabled;
-    }
-
-    /**
-     * Sets whether the item is clickable. If {@code true}, clicking item toggles the switch.
-     */
-    public void setClickable(boolean isClickable) {
-        mIsClickable = isClickable;
-        markDirty();
-    }
-
-    /**
-     * Sets {@code Primary Action} to be represented by an icon.
-     *
-     * @param drawable the Drawable to set.
-     * @param size     small/medium/large. Available as {@link #PRIMARY_ACTION_ICON_SIZE_SMALL},
-     *                 {@link #PRIMARY_ACTION_ICON_SIZE_MEDIUM},
-     *                 {@link #PRIMARY_ACTION_ICON_SIZE_LARGE}.
-     */
-    public void setPrimaryActionIcon(@NonNull Drawable drawable, @PrimaryActionIconSize int size) {
-        mPrimaryActionType = PRIMARY_ACTION_TYPE_ICON;
-        mPrimaryActionIconDrawable = drawable;
-        mPrimaryActionIconSize = size;
-        markDirty();
-    }
-
-    /**
-     * Sets {@code Primary Action} to be represented by an icon.
-     *
-     * @param iconResId the resource identifier of the drawable.
-     * @param size      small/medium/large. Available as {@link #PRIMARY_ACTION_ICON_SIZE_SMALL},
-     *                  {@link #PRIMARY_ACTION_ICON_SIZE_MEDIUM},
-     *                  {@link #PRIMARY_ACTION_ICON_SIZE_LARGE}.
-     */
-    public void setPrimaryActionIcon(@DrawableRes int iconResId, @PrimaryActionIconSize int size) {
-        setPrimaryActionIcon(getContext().getDrawable(iconResId), size);
-    }
-
-    /**
-     * Sets {@code Primary Action} to be empty icon.
-     *
-     * <p>{@code Text} would have a start margin as if {@code Primary Action} were set to primary
-     * icon.
-     */
-    public void setPrimaryActionEmptyIcon() {
-        mPrimaryActionType = PRIMARY_ACTION_TYPE_EMPTY_ICON;
-        markDirty();
-    }
-
-    /**
-     * Sets {@code Primary Action} to have no icon. Text would align to the start of item.
-     */
-    public void setPrimaryActionNoIcon() {
-        mPrimaryActionType = PRIMARY_ACTION_TYPE_NO_ICON;
-        markDirty();
-    }
-
-    /**
-     * Sets the title of item.
-     *
-     * <p>Primary text is {@code Title} by default. It can be set by
-     * {@link #setBody(CharSequence)}
-     *
-     * <p>{@code Title} text is limited to one line, and ellipses at the end.
-     *
-     * @param title text to display as title.
-     */
-    public void setTitle(@Nullable CharSequence title) {
-        mTitle = title;
-        markDirty();
-    }
-
-    /**
-     * Sets the body text of item.
-     *
-     * <p>Text beyond length required by regulation will be truncated.
-     *
-     * @param body text to be displayed.
-     */
-    public void setBody(@Nullable CharSequence body) {
-        mBody = body;
-        markDirty();
-    }
-
-    /**
-     * Sets the state of {@code Switch}.
-     *
-     * @param isChecked sets the "checked/unchecked, namely on/off" state of switch.
-     * @deprecated Use {@link #setChecked(boolean)} to set checked state of {@code Switch}.
-     */
-    @Deprecated
-    public void setSwitchState(boolean isChecked) {
-        setChecked(isChecked);
-    }
-
-    /**
-     * Sets whether the switch is checked.
-     *
-     * @param isChecked {@code true} to check the switch or {@code false} to uncheck it.
-     */
-    public void setChecked(boolean isChecked) {
-        if (mSwitchChecked == isChecked) {
-            return;
-        }
-        mSwitchChecked = isChecked;
-        mShouldNotifySwitchChecked = true;
-        markDirty();
-    }
-
-    /**
-     * Registers a callback to be invoked when the checked state of switch changes.
-     *
-     * @param listener callback to be invoked when the checked state shown in the UI changes.
-     */
-    public void setSwitchOnCheckedChangeListener(
-            @Nullable CompoundButton.OnCheckedChangeListener listener) {
-        mSwitchOnCheckedChangeListener = listener;
-        // This method invalidates previous listener. Reset so that we *only*
-        // notify when the checked state changes and not on the initial bind.
-        mShouldNotifySwitchChecked = false;
-        markDirty();
-    }
-
-    /**
-     * Sets whether to display a vertical bar between switch and text.
-     */
-    public void setShowSwitchDivider(boolean showSwitchDivider) {
-        mShowSwitchDivider = showSwitchDivider;
-        markDirty();
-    }
-
-    @NonNull
-    protected final Context getContext() {
-        return mContext;
-    }
-
-    private void hideSubViews(ViewHolder vh) {
-        for (View v : vh.getWidgetViews()) {
-            v.setVisibility(View.GONE);
-        }
-    }
-
-    private void setPrimaryAction() {
-        setPrimaryIconContent();
-        setPrimaryIconLayout();
-    }
-
-    private void setText() {
-        setTextContent();
-        setTextVerticalMargin();
-        setTextStartMargin();
-        setTextEndMargin();
-    }
-
-    private void setPrimaryIconContent() {
-        switch (mPrimaryActionType) {
-            case PRIMARY_ACTION_TYPE_ICON:
-                mBinders.add(vh -> {
-                    vh.getPrimaryIcon().setVisibility(View.VISIBLE);
-                    vh.getPrimaryIcon().setImageDrawable(mPrimaryActionIconDrawable);
-                });
-                break;
-            case PRIMARY_ACTION_TYPE_EMPTY_ICON:
-            case PRIMARY_ACTION_TYPE_NO_ICON:
-                // Do nothing.
-                break;
-            default:
-                throw new IllegalStateException("Unknown primary action type.");
-        }
-    }
-
-    /**
-     * Sets the size, start margin, and vertical position of primary icon.
-     *
-     * <p>Large icon will have no start margin, and always align center vertically.
-     *
-     * <p>Small/medium icon will have start margin, and uses a top margin such that it is "pinned"
-     * at the same position in list item regardless of item height.
-     */
-    private void setPrimaryIconLayout() {
-        if (mPrimaryActionType == PRIMARY_ACTION_TYPE_EMPTY_ICON
-                || mPrimaryActionType == PRIMARY_ACTION_TYPE_NO_ICON) {
-            return;
-        }
-
-        // Size of icon.
-        @DimenRes int sizeResId;
-        switch (mPrimaryActionIconSize) {
-            case PRIMARY_ACTION_ICON_SIZE_SMALL:
-                sizeResId = R.dimen.car_primary_icon_size;
-                break;
-            case PRIMARY_ACTION_ICON_SIZE_MEDIUM:
-                sizeResId = R.dimen.car_avatar_icon_size;
-                break;
-            case PRIMARY_ACTION_ICON_SIZE_LARGE:
-                sizeResId = R.dimen.car_single_line_list_item_height;
-                break;
-            default:
-                throw new IllegalStateException("Unknown primary action icon size.");
-        }
-
-        int iconSize = mContext.getResources().getDimensionPixelSize(sizeResId);
-
-        // Start margin of icon.
-        int startMargin;
-        switch (mPrimaryActionIconSize) {
-            case PRIMARY_ACTION_ICON_SIZE_SMALL:
-            case PRIMARY_ACTION_ICON_SIZE_MEDIUM:
-                startMargin = mContext.getResources().getDimensionPixelSize(R.dimen.car_keyline_1);
-                break;
-            case PRIMARY_ACTION_ICON_SIZE_LARGE:
-                startMargin = 0;
-                break;
-            default:
-                throw new IllegalStateException("Unknown primary action icon size.");
-        }
-
-        mBinders.add(vh -> {
-            ConstraintLayout.LayoutParams layoutParams =
-                    (ConstraintLayout.LayoutParams) vh.getPrimaryIcon().getLayoutParams();
-            layoutParams.height = layoutParams.width = iconSize;
-            layoutParams.setMarginStart(startMargin);
-
-            if (mPrimaryActionIconSize == PRIMARY_ACTION_ICON_SIZE_LARGE) {
-                // A large icon is always vertically centered.
-                layoutParams.verticalBias = 0.5f;
-                layoutParams.topMargin = 0;
-            } else {
-                // Align the icon to the top of the parent. This allows the topMargin to shift it
-                // down relative to the top.
-                layoutParams.verticalBias = 0f;
-
-                // For all other icon sizes, the icon should be centered within the height of
-                // car_double_line_list_item_height. Note: the actual height of the item can be
-                // larger than this.
-                int itemHeight = mContext.getResources().getDimensionPixelSize(
-                        R.dimen.car_double_line_list_item_height);
-                layoutParams.topMargin = (itemHeight - iconSize) / 2;
-            }
-
-            vh.getPrimaryIcon().requestLayout();
-        });
-    }
-
-    private void setTextContent() {
-        boolean hasTitle = !TextUtils.isEmpty(mTitle);
-        boolean hasBody = !TextUtils.isEmpty(mBody);
-
-        if (!hasTitle && !hasBody) {
-            return;
-        }
-
-        mBinders.add(vh -> {
-            if (hasTitle) {
-                vh.getTitle().setVisibility(View.VISIBLE);
-                vh.getTitle().setText(mTitle);
-            }
-
-            if (hasBody) {
-                vh.getBody().setVisibility(View.VISIBLE);
-                vh.getBody().setText(mBody);
-            }
-
-            if (hasTitle && !hasBody) {
-                // If only title, then center the supplemental actions.
-                vh.getSupplementalGuideline().setGuidelineBegin(
-                        ConstraintLayout.LayoutParams.UNSET);
-                vh.getSupplementalGuideline().setGuidelinePercent(0.5f);
-            } else {
-                // Otherwise, position it a fixed distance from the top.
-                vh.getSupplementalGuideline().setGuidelinePercent(
-                        ConstraintLayout.LayoutParams.UNSET);
-                vh.getSupplementalGuideline().setGuidelineBegin(
-                        mSupplementalGuidelineBegin);
-            }
-        });
-    }
-
-    /**
-     * Sets start margin of text view depending on icon type.
-     */
-    private void setTextStartMargin() {
-        @DimenRes int startMarginResId;
-        switch (mPrimaryActionType) {
-            case PRIMARY_ACTION_TYPE_NO_ICON:
-                startMarginResId = R.dimen.car_keyline_1;
-                break;
-            case PRIMARY_ACTION_TYPE_EMPTY_ICON:
-                startMarginResId = R.dimen.car_keyline_3;
-                break;
-            case PRIMARY_ACTION_TYPE_ICON:
-                startMarginResId = mPrimaryActionIconSize == PRIMARY_ACTION_ICON_SIZE_LARGE
-                        ? R.dimen.car_keyline_4
-                        : R.dimen.car_keyline_3;  // Small and medium sized icon.
-                break;
-            default:
-                throw new IllegalStateException("Unknown primary action type.");
-        }
-        int startMargin = mContext.getResources().getDimensionPixelSize(startMarginResId);
-        mBinders.add(vh -> {
-            MarginLayoutParams titleLayoutParams =
-                    (MarginLayoutParams) vh.getTitle().getLayoutParams();
-            titleLayoutParams.setMarginStart(startMargin);
-            vh.getTitle().requestLayout();
-
-            MarginLayoutParams bodyLayoutParams =
-                    (MarginLayoutParams) vh.getBody().getLayoutParams();
-            bodyLayoutParams.setMarginStart(startMargin);
-            vh.getBody().requestLayout();
-        });
-    }
-
-    private void setTextEndMargin() {
-        int endMargin = mContext.getResources().getDimensionPixelSize(R.dimen.car_padding_4);
-
-        mBinders.add(vh -> {
-            MarginLayoutParams titleLayoutParams =
-                    (MarginLayoutParams) vh.getTitle().getLayoutParams();
-            titleLayoutParams.setMarginEnd(endMargin);
-
-            MarginLayoutParams bodyLayoutParams =
-                    (MarginLayoutParams) vh.getBody().getLayoutParams();
-            bodyLayoutParams.setMarginEnd(endMargin);
-        });
-    }
-
-    /**
-     * Sets top/bottom margins of {@code Title} and {@code Body}.
-     */
-    private void setTextVerticalMargin() {
-        // Set all relevant fields in layout params to avoid carried over params when the item
-        // gets bound to a recycled view holder.
-        if (!TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mBody)) {
-            // Title only - view is aligned center vertically by itself.
-            mBinders.add(vh -> {
-                MarginLayoutParams layoutParams =
-                        (MarginLayoutParams) vh.getTitle().getLayoutParams();
-                layoutParams.topMargin = 0;
-                vh.getTitle().requestLayout();
-            });
-        } else if (TextUtils.isEmpty(mTitle) && !TextUtils.isEmpty(mBody)) {
-            mBinders.add(vh -> {
-                // Body uses top and bottom margin.
-                int margin = mContext.getResources().getDimensionPixelSize(
-                        R.dimen.car_padding_3);
-                MarginLayoutParams layoutParams =
-                        (MarginLayoutParams) vh.getBody().getLayoutParams();
-                layoutParams.topMargin = margin;
-                layoutParams.bottomMargin = margin;
-                vh.getBody().requestLayout();
-            });
-        } else {
-            mBinders.add(vh -> {
-                Resources resources = mContext.getResources();
-                int padding2 = resources.getDimensionPixelSize(R.dimen.car_padding_2);
-
-                // Title has a top margin
-                MarginLayoutParams titleLayoutParams =
-                        (MarginLayoutParams) vh.getTitle().getLayoutParams();
-                titleLayoutParams.topMargin = padding2;
-                vh.getTitle().requestLayout();
-
-                // Body is below title with no margin and has bottom margin.
-                MarginLayoutParams bodyLayoutParams =
-                        (MarginLayoutParams) vh.getBody().getLayoutParams();
-                bodyLayoutParams.topMargin = 0;
-                bodyLayoutParams.bottomMargin = padding2;
-                vh.getBody().requestLayout();
-            });
-        }
-    }
-
-    /**
-     * Sets up view(s) for supplemental action.
-     */
-    private void setSwitch() {
-        mBinders.add(vh -> {
-            vh.getSwitch().setVisibility(View.VISIBLE);
-            vh.getSwitch().setOnCheckedChangeListener(null);
-            vh.getSwitch().setChecked(mSwitchChecked);
-            vh.getSwitch().setOnCheckedChangeListener((buttonView, isChecked) -> {
-                if (mSwitchOnCheckedChangeListener != null) {
-                    // The checked state changed via user interaction with the switch.
-                    mSwitchOnCheckedChangeListener.onCheckedChanged(buttonView, isChecked);
-                }
-                mSwitchChecked = isChecked;
-            });
-            if (mShouldNotifySwitchChecked && mSwitchOnCheckedChangeListener != null) {
-                // The checked state was changed programmatically.
-                mSwitchOnCheckedChangeListener.onCheckedChanged(vh.getSwitch(),
-                        mSwitchChecked);
-                mShouldNotifySwitchChecked = false;
-            }
-
-            if (mShowSwitchDivider) {
-                vh.getSwitchDivider().setVisibility(View.VISIBLE);
-            }
-        });
-    }
-
-    private void setItemClickable() {
-        mBinders.add(vh -> {
-            // If applicable (namely item is clickable), clicking item always toggles the switch.
-            vh.itemView.setOnClickListener(v -> vh.getSwitch().toggle());
-            vh.itemView.setClickable(mIsClickable);
-        });
-    }
-
-    /**
-     * Holds views of SwitchListItem.
-     */
-    public static final class ViewHolder extends ListItem.ViewHolder {
-
-        private final View[] mWidgetViews;
+        private ViewGroup mContainerLayout;
 
         private ImageView mPrimaryIcon;
 
@@ -613,15 +103,19 @@
 
         private Guideline mSupplementalGuideline;
 
-        private Switch mSwitch;
-        private View mSwitchDivider;
+        private CompoundButton mCompoundButton;
+        private View mCompoundButtonDivider;
 
         /**
-         * ViewHolder that contains necessary widgets for {@link SwitchListItem}.
+         * Creates a {@link ViewHolder} for a {@link SwitchListItem}.
+         *
+         * @param itemView The view to be used to display a {@link SwitchListItem}.
          */
         public ViewHolder(@NonNull View itemView) {
             super(itemView);
 
+            mContainerLayout = itemView.findViewById(R.id.container);
+
             mPrimaryIcon = itemView.findViewById(R.id.primary_icon);
 
             mTitle = itemView.findViewById(R.id.title);
@@ -629,19 +123,19 @@
 
             mSupplementalGuideline = itemView.findViewById(R.id.supplemental_actions_guideline);
 
-            mSwitch = itemView.findViewById(R.id.switch_widget);
-            mSwitchDivider = itemView.findViewById(R.id.switch_divider);
+            mCompoundButton = itemView.findViewById(R.id.switch_widget);
+            mCompoundButtonDivider = itemView.findViewById(R.id.switch_divider);
 
             int minTouchSize = itemView.getContext().getResources()
                     .getDimensionPixelSize(R.dimen.car_touch_target_size);
-            MinTouchTargetHelper.ensureThat(mSwitch).hasMinTouchSize(minTouchSize);
+            MinTouchTargetHelper.ensureThat(mCompoundButton).hasMinTouchSize(minTouchSize);
 
             // Each line groups relevant child views in an effort to help keep this view array
             // updated with actual child views in the ViewHolder.
             mWidgetViews = new View[]{
                     mPrimaryIcon,
                     mTitle, mBody,
-                    mSwitch, mSwitchDivider,
+                    mCompoundButton, mCompoundButtonDivider,
             };
         }
 
@@ -653,43 +147,86 @@
          * @param restrictionsInfo current car UX restrictions.
          */
         @Override
-        public void onUxRestrictionsChanged(CarUxRestrictions restrictionsInfo) {
+        public void onUxRestrictionsChanged(@NonNull CarUxRestrictions restrictionsInfo) {
             CarUxRestrictionsUtils.apply(itemView.getContext(), restrictionsInfo, getBody());
         }
 
+        /**
+         * Returns the primary icon view within this view holder's view.
+         *
+         * @return Icon view within this view holder's view.
+         */
         @NonNull
+        @Override
         public ImageView getPrimaryIcon() {
             return mPrimaryIcon;
         }
 
+        /**
+         * Returns the title view within this view holder's view.
+         *
+         * @return Title view within this view holder's view.
+         */
         @NonNull
+        @Override
         public TextView getTitle() {
             return mTitle;
         }
 
+        /**
+         * Returns the body view within this view holder's view.
+         *
+         * @return Body view within this view holder's view.
+         */
         @NonNull
+        @Override
         public TextView getBody() {
             return mBody;
         }
 
+        /**
+         * Returns the compound button divider view within this view holder's view.
+         *
+         * @return Compound button divider view within this view holder's view.
+         */
         @NonNull
-        public View getSwitchDivider() {
-            return mSwitchDivider;
+        @Override
+        public View getCompoundButtonDivider() {
+            return mCompoundButtonDivider;
+        }
+
+        /**
+         * Returns the compound button within this view holder's view.
+         *
+         * @return Compound button within this view holder's view.
+         */
+        @NonNull
+        @Override
+        public CompoundButton getCompoundButton() {
+            return mCompoundButton;
         }
 
         @NonNull
-        public Switch getSwitch() {
-            return mSwitch;
-        }
-
-        @NonNull
+        @Override
         Guideline getSupplementalGuideline() {
             return mSupplementalGuideline;
         }
 
         @NonNull
+        @Override
         View[] getWidgetViews() {
             return mWidgetViews;
         }
+
+        /**
+         * Returns the container layout of this view holder.
+         *
+         * @return Container layout of this view holder.
+         */
+        @NonNull
+        @Override
+        public ViewGroup getContainerLayout() {
+            return mContainerLayout;
+        }
     }
 }
diff --git a/jetifier/jetifier/migration.config b/jetifier/jetifier/migration.config
index e0b1250..6aa7518 100644
--- a/jetifier/jetifier/migration.config
+++ b/jetifier/jetifier/migration.config
@@ -292,22 +292,6 @@
       "to": "android/support/v4/media/{0}"
     },
     {
-      "from": "android/support/v4/widget/CursorAdapter(.*)",
-      "to": "androidx/cursoradapter/widget/CursorAdapter{0}"
-    },
-    {
-      "from": "android/support/v4/widget/CursorFilter(.*)",
-      "to": "androidx/cursoradapter/widget/CursorFilter{0}"
-    },
-    {
-      "from": "android/support/v4/widget/ResourceCursorAdapter(.*)",
-      "to": "androidx/cursoradapter/widget/ResourceCursorAdapter{0}"
-    },
-    {
-      "from": "android/support/v4/widget/SimpleCursorAdapter(.*)",
-      "to": "androidx/cursoradapter/widget/SimpleCursorAdapter{0}"
-    },
-    {
       "from": "android/support/v4/app/LoaderManager(.*)",
       "to": "androidx/loader/app/LoaderManager{0}"
     },
@@ -642,6 +626,22 @@
       "to": "ignore"
     },
     {
+      "from": "androidx/cursoradapter/widget/CursorAdapter(.*)",
+      "to": "ignore"
+    },
+    {
+      "from": "androidx/cursoradapter/widget/CursorFilter(.*)",
+      "to": "ignore"
+    },
+    {
+      "from": "androidx/cursoradapter/widget/ResourceCursorAdapter(.*)",
+      "to": "ignore"
+    },
+    {
+      "from": "androidx/cursoradapter/widget/SimpleCursorAdapter(.*)",
+      "to": "ignore"
+    },
+    {
       "from": "androidx/textclassifier/(.*)",
       "to": "ignore"
     },
@@ -1016,7 +1016,7 @@
       "to": "androidx/loader"
     },
     {
-      "from": "android/support/cursoradapter",
+      "from": "androidx/cursoradapter",
       "to": "androidx/cursoradapter"
     },
     {
@@ -1991,9 +1991,9 @@
     },
     {
       "from": {
-        "groupId": "com.android.support",
+        "groupId": "androidx.cursoradapter",
         "artifactId": "cursoradapter",
-        "version": "{oldSlVersion}"
+        "version": "{newSlVersion}"
       },
       "to": {
         "groupId": "androidx.cursoradapter",
@@ -4240,8 +4240,6 @@
       "android/support/v4/widget/AutoSizeableTextView": "androidx/core/widget/AutoSizeableTextView",
       "android/support/v4/widget/CompoundButtonCompat": "androidx/core/widget/CompoundButtonCompat",
       "android/support/v4/widget/ContentLoadingProgressBar": "androidx/core/widget/ContentLoadingProgressBar",
-      "android/support/v4/widget/CursorAdapter": "androidx/cursoradapter/widget/CursorAdapter",
-      "android/support/v4/widget/CursorFilter": "androidx/cursoradapter/widget/CursorFilter",
       "android/support/v4/widget/DirectedAcyclicGraph": "androidx/coordinatorlayout/widget/DirectedAcyclicGraph",
       "android/support/v4/widget/DrawerLayout": "androidx/drawerlayout/widget/DrawerLayout",
       "android/support/v4/widget/EdgeEffectCompat": "androidx/core/widget/EdgeEffectCompat",
@@ -4254,9 +4252,7 @@
       "android/support/v4/widget/NestedScrollView": "androidx/core/widget/NestedScrollView",
       "android/support/v4/widget/PopupMenuCompat": "androidx/core/widget/PopupMenuCompat",
       "android/support/v4/widget/PopupWindowCompat": "androidx/core/widget/PopupWindowCompat",
-      "android/support/v4/widget/ResourceCursorAdapter": "androidx/cursoradapter/widget/ResourceCursorAdapter",
       "android/support/v4/widget/ScrollerCompat": "androidx/core/widget/ScrollerCompat",
-      "android/support/v4/widget/SimpleCursorAdapter": "androidx/cursoradapter/widget/SimpleCursorAdapter",
       "android/support/v4/widget/TextViewCompat": "androidx/core/widget/TextViewCompat",
       "android/support/v4/widget/TintableCompoundButton": "androidx/core/widget/TintableCompoundButton",
       "android/support/v4/widget/TintableImageSourceView": "androidx/core/widget/TintableImageSourceView",
diff --git a/jetifier/jetifier/standalone/build.gradle b/jetifier/jetifier/standalone/build.gradle
index 83d059d..126f449 100644
--- a/jetifier/jetifier/standalone/build.gradle
+++ b/jetifier/jetifier/standalone/build.gradle
@@ -34,5 +34,4 @@
 
   destinationDir BuildServerConfigurationKt.getDistributionDirectory(rootProject)
 }
-rootProject.tasks["createArchive"].dependsOn(dist)
 
diff --git a/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java b/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java
index 89e2ebc..de69fc9 100644
--- a/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java
+++ b/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java
@@ -25,6 +25,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -1133,6 +1134,23 @@
     @Test
     @LargeTest
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT)
+    public void testSetPlaybackSpeedWithIllegalArguments() throws Throwable {
+        // Zero is not allowed.
+        ListenableFuture<PlayerResult> future = mPlayer.setPlaybackSpeed(0.0f);
+        PlayerResult result = future.get();
+        assertNotNull(result);
+        assertEquals(RESULT_ERROR_BAD_VALUE, result.getResultCode());
+
+        // Negative values are not allowed.
+        future = mPlayer.setPlaybackSpeed(-1.0f);
+        result = future.get();
+        assertNotNull(result);
+        assertEquals(RESULT_ERROR_BAD_VALUE, result.getResultCode());
+    }
+
+    @Test
+    @LargeTest
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT)
     public void testClose() throws Exception {
         assertTrue(loadResource(R.raw.testmp3_2));
         AudioAttributesCompat attributes = new AudioAttributesCompat.Builder()
diff --git a/media2/player/src/main/java/androidx/media2/player/MediaPlayer.java b/media2/player/src/main/java/androidx/media2/player/MediaPlayer.java
index de7cc0a..20ac028 100644
--- a/media2/player/src/main/java/androidx/media2/player/MediaPlayer.java
+++ b/media2/player/src/main/java/androidx/media2/player/MediaPlayer.java
@@ -823,6 +823,9 @@
         PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
             @Override
             List<ResolvableFuture<PlayerResult>> onExecute() {
+                if (playbackSpeed <= 0.0f) {
+                    return createFuturesForResultCode(RESULT_ERROR_BAD_VALUE);
+                }
                 ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
                 ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
                 synchronized (mPendingCommands) {
diff --git a/samples/SupportCarDemos/src/main/AndroidManifest.xml b/samples/SupportCarDemos/src/main/AndroidManifest.xml
index 7a205fc..34c0422 100644
--- a/samples/SupportCarDemos/src/main/AndroidManifest.xml
+++ b/samples/SupportCarDemos/src/main/AndroidManifest.xml
@@ -270,5 +270,13 @@
                 <category android:name="android.intent.category.SAMPLE_CODE"/>
             </intent-filter>
         </activity>
+        <activity android:name=".CheckBoxListItemActivity"
+            android:label="CheckBoxListItem Demo"
+            android:parentActivityName=".SupportCarDemoActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.SAMPLE_CODE"/>
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/CheckBoxListItemActivity.java b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/CheckBoxListItemActivity.java
new file mode 100644
index 0000000..2a1bed5
--- /dev/null
+++ b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/CheckBoxListItemActivity.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 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 com.example.androidx.car;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.widget.CompoundButton;
+import android.widget.Toast;
+
+import androidx.car.widget.CarToolbar;
+import androidx.car.widget.CheckBoxListItem;
+import androidx.car.widget.ListItem;
+import androidx.car.widget.ListItemAdapter;
+import androidx.car.widget.ListItemProvider;
+import androidx.car.widget.PagedListView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Demo activity for {@link androidx.car.widget.CheckBoxListItem}.
+ */
+public class CheckBoxListItemActivity extends Activity {
+
+    private PagedListView mPagedListView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_paged_list_view);
+
+        CarToolbar toolbar = findViewById(R.id.car_toolbar);
+        toolbar.setTitle(R.string.checkbox_list_item_title);
+        toolbar.setNavigationIconOnClickListener(v -> finish());
+
+        mPagedListView = findViewById(R.id.paged_list_view);
+
+        SampleProvider provider = new SampleProvider(this);
+        ListItemAdapter adapter = new ListItemAdapter(this, provider);
+
+        mPagedListView.setAdapter(adapter);
+        mPagedListView.setMaxPages(PagedListView.UNLIMITED_PAGES);
+
+        CheckBoxListItem item = new CheckBoxListItem(this);
+        item.setTitle("Clicking me to set checked state of item above");
+        item.setOnCheckedChangeListener((buttonView, isChecked) -> {
+            int size = adapter.getItemCount();
+            // -2 to get second to last item (the one above).
+            ((CheckBoxListItem) provider.mItems.get(size - 2)).setChecked(isChecked);
+            adapter.notifyDataSetChanged();
+        });
+        provider.mItems.add(item);
+    }
+
+    private static class SampleProvider extends ListItemProvider {
+        private Context mContext;
+
+        private final List<ListItem> mItems = new ArrayList<>();
+        private final ListItemProvider.ListProvider mListProvider =
+                new ListItemProvider.ListProvider(mItems);
+        private final CompoundButton.OnCheckedChangeListener mListener = (button, isChecked) ->
+                Toast.makeText(mContext,
+                        "Checkbox is " + (isChecked ? "checked" : "unchecked"),
+                        Toast.LENGTH_SHORT).show();
+
+        SampleProvider(Context context) {
+            mContext = context;
+
+            String longText = mContext.getString(R.string.long_text);
+
+            CheckBoxListItem item;
+
+            item = new CheckBoxListItem(mContext);
+            item.setTitle("Title - show divider");
+            item.setShowCompoundButtonDivider(true);
+            item.setOnCheckedChangeListener(mListener);
+            mItems.add(item);
+
+            item = new CheckBoxListItem(mContext);
+            item.setBody("Body text");
+            item.setOnCheckedChangeListener(mListener);
+            mItems.add(item);
+
+            item = new CheckBoxListItem(mContext);
+            item.setTitle("Long body text");
+            item.setBody(longText);
+            item.setOnCheckedChangeListener(mListener);
+            mItems.add(item);
+
+            item = new CheckBoxListItem(mContext);
+            item.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
+                    CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
+            item.setTitle("CheckBox with Icon");
+            item.setBody(longText);
+            item.setOnCheckedChangeListener(mListener);
+            mItems.add(item);
+
+            item = new CheckBoxListItem(mContext);
+            item.setTitle("CheckBox with Drawable");
+            item.setPrimaryActionIcon(
+                    mContext.getDrawable(android.R.drawable.sym_def_app_icon),
+                    CheckBoxListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
+            item.setBody(longText);
+            item.setOnCheckedChangeListener(mListener);
+            mItems.add(item);
+
+            item = new CheckBoxListItem(mContext);
+            item.setTitle("Clicking item toggles checkbox");
+            item.setClickable(true);
+            item.setOnCheckedChangeListener(mListener);
+            mItems.add(item);
+
+            item = new CheckBoxListItem(mContext);
+            item.setTitle("Disabled item");
+            item.setEnabled(false);
+            item.setOnCheckedChangeListener(mListener);
+            mItems.add(item);
+        }
+
+        @Override
+        public ListItem get(int position) {
+            return mListProvider.get(position);
+        }
+
+        @Override
+        public int size() {
+            return mListProvider.size();
+        }
+    }
+}
+
diff --git a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/RadioButtonListItemActivity.java b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/RadioButtonListItemActivity.java
index 2b9f396..69e5f89 100644
--- a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/RadioButtonListItemActivity.java
+++ b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/RadioButtonListItemActivity.java
@@ -21,6 +21,7 @@
 import android.os.Bundle;
 
 import androidx.car.app.CarListDialog;
+import androidx.car.widget.CarToolbar;
 import androidx.car.widget.ListItem;
 import androidx.car.widget.ListItemAdapter;
 import androidx.car.widget.ListItemProvider;
@@ -44,6 +45,9 @@
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_paged_list_view);
 
+        CarToolbar toolbar = findViewById(R.id.car_toolbar);
+        toolbar.setTitle(R.string.radio_button_list_item_title);
+        toolbar.setNavigationIconOnClickListener(v -> finish());
 
         mPagedListView = findViewById(R.id.paged_list_view);
         RadioButtonSelectionAdapter adapter = new RadioButtonSelectionAdapter(
@@ -67,7 +71,6 @@
 
         item = new RadioButtonListItem(this);
         item.setPrimaryActionNoIcon();
-        item.setTextStartMargin(R.dimen.car_keyline_3);
         item.setTitle("No icon");
         items.add(item);
 
@@ -75,7 +78,7 @@
         item.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
                 RadioButtonListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
         item.setTitle("Small icon - with action divider");
-        item.setShowRadioButtonDivider(true);
+        item.setShowCompoundButtonDivider(true);
         items.add(item);
 
         item = new RadioButtonListItem(this);
@@ -83,7 +86,7 @@
                 RadioButtonListItem.PRIMARY_ACTION_ICON_SIZE_MEDIUM);
         item.setTitle("Medium icon - with body text");
         item.setBody("Sample body text");
-        item.setShowRadioButtonDivider(true);
+        item.setShowCompoundButtonDivider(true);
         items.add(item);
 
         item = new RadioButtonListItem(this);
@@ -162,12 +165,12 @@
 
             RadioButtonListItem.ViewHolder viewHolder = (RadioButtonListItem.ViewHolder) vh;
 
-            viewHolder.getRadioButton().setChecked(mSelectionController.isChecked(position));
-            viewHolder.getRadioButton().setOnCheckedChangeListener((buttonView, isChecked) -> {
+            viewHolder.getCompoundButton().setChecked(mSelectionController.isChecked(position));
+            viewHolder.getCompoundButton().setOnCheckedChangeListener((buttonView, isChecked) -> {
                 mSelectionController.setChecked(position);
                 // Refresh other radio button list items.
                 notifyDataSetChanged();
             });
         }
     }
-}
+}
\ No newline at end of file
diff --git a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SwitchListItemActivity.java b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SwitchListItemActivity.java
index 63e9eb5..3dacd22 100644
--- a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SwitchListItemActivity.java
+++ b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SwitchListItemActivity.java
@@ -58,7 +58,7 @@
 
         SwitchListItem item = new SwitchListItem(this);
         item.setTitle("Clicking me to set checked state of item above");
-        item.setSwitchOnCheckedChangeListener((buttonView, isChecked) -> {
+        item.setOnCheckedChangeListener((buttonView, isChecked) -> {
             int size = adapter.getItemCount();
             // -2 to get second to last item (the one above).
             ((SwitchListItem) provider.mItems.get(size - 2)).setChecked(isChecked);
@@ -87,19 +87,19 @@
 
             item = new SwitchListItem(mContext);
             item.setTitle("Title - show divider");
-            item.setShowSwitchDivider(true);
-            item.setSwitchOnCheckedChangeListener(mListener);
+            item.setShowCompoundButtonDivider(true);
+            item.setOnCheckedChangeListener(mListener);
             mItems.add(item);
 
             item = new SwitchListItem(mContext);
             item.setBody("Body text");
-            item.setSwitchOnCheckedChangeListener(mListener);
+            item.setOnCheckedChangeListener(mListener);
             mItems.add(item);
 
             item = new SwitchListItem(mContext);
             item.setTitle("Long body text");
             item.setBody(longText);
-            item.setSwitchOnCheckedChangeListener(mListener);
+            item.setOnCheckedChangeListener(mListener);
             mItems.add(item);
 
             item = new SwitchListItem(mContext);
@@ -107,7 +107,7 @@
                     SwitchListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
             item.setTitle("Switch with Icon");
             item.setBody(longText);
-            item.setSwitchOnCheckedChangeListener(mListener);
+            item.setOnCheckedChangeListener(mListener);
             mItems.add(item);
 
             item = new SwitchListItem(mContext);
@@ -116,19 +116,19 @@
                     mContext.getDrawable(android.R.drawable.sym_def_app_icon),
                     SwitchListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
             item.setBody(longText);
-            item.setSwitchOnCheckedChangeListener(mListener);
+            item.setOnCheckedChangeListener(mListener);
             mItems.add(item);
 
             item = new SwitchListItem(mContext);
             item.setTitle("Clicking item toggles switch");
             item.setClickable(true);
-            item.setSwitchOnCheckedChangeListener(mListener);
+            item.setOnCheckedChangeListener(mListener);
             mItems.add(item);
 
             item = new SwitchListItem(mContext);
             item.setTitle("Disabled item");
             item.setEnabled(false);
-            item.setSwitchOnCheckedChangeListener(mListener);
+            item.setOnCheckedChangeListener(mListener);
             mItems.add(item);
         }
 
diff --git a/samples/SupportCarDemos/src/main/res/values/strings.xml b/samples/SupportCarDemos/src/main/res/values/strings.xml
index d75f7c5..fd26a43 100644
--- a/samples/SupportCarDemos/src/main/res/values/strings.xml
+++ b/samples/SupportCarDemos/src/main/res/values/strings.xml
@@ -22,7 +22,9 @@
     <string name="paged_list_view_shrink_title">PagedListViewShrinkDemo</string>
     <string name="text_list_item_title">TextListItem</string>
     <string name="seekbar_list_item_title">SeekbarListItem</string>
+    <string name="radio_button_list_item_title">RadioButtonListItem</string>
     <string name="switch_list_item_title">SwitchListItem</string>
+    <string name="checkbox_list_item_title">CheckBoxListItem</string>
     <string name="sub_header_list_item_title">SubheaderListItem</string>
     <string name="alpha_jump_title">Alpha Jump Demo</string>
     <string name="single_selection_dialog_title">CarSingleChoiceDialog Demo</string>
diff --git a/security/crypto/api/1.0.0-alpha01.txt b/security/crypto/api/1.0.0-alpha01.txt
index 261883a..44f0de6 100644
--- a/security/crypto/api/1.0.0-alpha01.txt
+++ b/security/crypto/api/1.0.0-alpha01.txt
@@ -1,295 +1,49 @@
 // Signature format: 3.0
-package androidx.security {
-
-  public class SecureConfig {
-    method public String getAndroidCAStore();
-    method public String getAndroidKeyStore();
-    method public String getAsymmetricBlockModes();
-    method public String getAsymmetricCipherTransformation();
-    method public String getAsymmetricKeyPairAlgorithm();
-    method public int getAsymmetricKeyPurposes();
-    method public int getAsymmetricKeySize();
-    method public String getAsymmetricPaddings();
-    method public boolean getAsymmetricRequireUserAuthEnabled();
-    method public int getAsymmetricRequireUserValiditySeconds();
-    method public boolean getAsymmetricSensitiveDataProtectionEnabled();
-    method public androidx.security.biometric.BiometricKeyAuthCallback getBiometricKeyAuthCallback();
-    method public String getCertPath();
-    method public String getCertPathValidator();
-    method public String[] getClientCertAlgorithms();
-    method public static androidx.security.SecureConfig getDefault();
-    method public String getKeystoreType();
-    method public static androidx.security.SecureConfig getNiapConfig();
-    method public static androidx.security.SecureConfig getNiapConfig(androidx.security.biometric.BiometricKeyAuthCallback);
-    method public String getSignatureAlgorithm();
-    method public String[] getStrongSSLCiphers();
-    method public String getSymmetricBlockModes();
-    method public String getSymmetricCipherTransformation();
-    method public int getSymmetricGcmTagLength();
-    method public String getSymmetricKeyAlgorithm();
-    method public int getSymmetricKeyPurposes();
-    method public int getSymmetricKeySize();
-    method public String getSymmetricPaddings();
-    method public boolean getSymmetricRequireUserAuthEnabled();
-    method public int getSymmetricRequireUserValiditySeconds();
-    method public boolean getSymmetricSensitiveDataProtectionEnabled();
-    method public androidx.security.config.TrustAnchorOptions getTrustAnchorOptions();
-    method public boolean getUseStrongSSLCiphers();
-    method public boolean getUseStrongSSLCiphersEnabled();
-    method public void setAndroidCAStore(String);
-    method public void setAndroidKeyStore(String);
-    method public void setAsymmetricBlockModes(String);
-    method public void setAsymmetricCipherTransformation(String);
-    method public void setAsymmetricKeyPairAlgorithm(String);
-    method public void setAsymmetricKeyPurposes(int);
-    method public void setAsymmetricKeySize(int);
-    method public void setAsymmetricPaddings(String);
-    method public void setAsymmetricRequireUserAuth(boolean);
-    method public void setAsymmetricRequireUserValiditySeconds(int);
-    method public void setAsymmetricSensitiveDataProtection(boolean);
-    method public void setBiometricKeyAuthCallback(androidx.security.biometric.BiometricKeyAuthCallback);
-    method public void setCertPath(String);
-    method public void setCertPathValidator(String);
-    method public void setClientCertAlgorithms(String[]);
-    method public void setKeystoreType(String);
-    method public void setSignatureAlgorithm(String);
-    method public void setStrongSSLCiphers(String[]);
-    method public void setSymmetricBlockModes(String);
-    method public void setSymmetricCipherTransformation(String);
-    method public void setSymmetricGcmTagLength(int);
-    method public void setSymmetricKeyAlgorithm(String);
-    method public void setSymmetricKeyPurposes(int);
-    method public void setSymmetricKeySize(int);
-    method public void setSymmetricPaddings(String);
-    method public void setSymmetricRequireUserAuth(boolean);
-    method public void setSymmetricRequireUserValiditySeconds(int);
-    method public void setSymmetricSensitiveDataProtection(boolean);
-    method public void setTrustAnchorOptions(androidx.security.config.TrustAnchorOptions);
-    method public void setUseStrongSSLCiphers(boolean);
-    field public static final int AES_IV_SIZE_BYTES = 16; // 0x10
-    field public static final String ANDROID_CA_STORE = "AndroidCAStore";
-    field public static final String ANDROID_KEYSTORE = "AndroidKeyStore";
-    field public static final String SSL_TLS = "TLS";
-  }
-
-  public static class SecureConfig.Builder {
-    ctor public SecureConfig.Builder();
-    method public androidx.security.SecureConfig build();
-    method public androidx.security.SecureConfig.Builder forKeyStoreType(String);
-    method public androidx.security.SecureConfig.Builder setAsymmetricBlockModes(String);
-    method public androidx.security.SecureConfig.Builder setAsymmetricCipherTransformation(String);
-    method public androidx.security.SecureConfig.Builder setAsymmetricKeyPairAlgorithm(String);
-    method public androidx.security.SecureConfig.Builder setAsymmetricKeyPurposes(int);
-    method public androidx.security.SecureConfig.Builder setAsymmetricKeySize(int);
-    method public androidx.security.SecureConfig.Builder setAsymmetricPaddings(String);
-    method public androidx.security.SecureConfig.Builder setAsymmetricRequireUserAuth(boolean);
-    method public androidx.security.SecureConfig.Builder setAsymmetricRequireUserValiditySeconds(int);
-    method public androidx.security.SecureConfig.Builder setAsymmetricSensitiveDataProtection(boolean);
-    method public androidx.security.SecureConfig.Builder setBiometricKeyAuthCallback(androidx.security.biometric.BiometricKeyAuthCallback);
-    method public androidx.security.SecureConfig.Builder setCertPath(String);
-    method public androidx.security.SecureConfig.Builder setCertPathValidator(String);
-    method public androidx.security.SecureConfig.Builder setClientCertAlgorithms(String[]);
-    method public androidx.security.SecureConfig.Builder setSignatureAlgorithm(String);
-    method public androidx.security.SecureConfig.Builder setStrongSSLCiphers(String[]);
-    method public androidx.security.SecureConfig.Builder setSymmetricBlockModes(String);
-    method public androidx.security.SecureConfig.Builder setSymmetricCipherTransformation(String);
-    method public androidx.security.SecureConfig.Builder setSymmetricGcmTagLength(int);
-    method public androidx.security.SecureConfig.Builder setSymmetricKeyAlgorithm(String);
-    method public androidx.security.SecureConfig.Builder setSymmetricKeyPurposes(int);
-    method public androidx.security.SecureConfig.Builder setSymmetricKeySize(int);
-    method public androidx.security.SecureConfig.Builder setSymmetricPaddings(String);
-    method public androidx.security.SecureConfig.Builder setSymmetricRequireUserAuth(boolean);
-    method public androidx.security.SecureConfig.Builder setSymmetricRequireUserValiditySeconds(int);
-    method public androidx.security.SecureConfig.Builder setSymmetricSensitiveDataProtection(boolean);
-    method public androidx.security.SecureConfig.Builder setTrustAnchorOptions(androidx.security.config.TrustAnchorOptions);
-    method public androidx.security.SecureConfig.Builder setUseStrongSSLCiphers(boolean);
-  }
-
-}
-
-package androidx.security.biometric {
-
-  public class BiometricKeyAuth extends androidx.biometric.BiometricPrompt.AuthenticationCallback {
-    ctor public BiometricKeyAuth(androidx.fragment.app.FragmentActivity, androidx.security.biometric.BiometricKeyAuthCallback);
-    method public void authenticateKey(javax.crypto.Cipher, androidx.biometric.BiometricPrompt.PromptInfo, androidx.security.crypto.SecureCipher.SecureAuthListener);
-    method public void authenticateKey(java.security.Signature, androidx.biometric.BiometricPrompt.PromptInfo, androidx.security.crypto.SecureCipher.SecureAuthListener);
-    method public void authenticateKey(javax.crypto.Cipher, androidx.security.crypto.SecureCipher.SecureAuthListener);
-    method public void authenticateKey(java.security.Signature, androidx.security.crypto.SecureCipher.SecureAuthListener);
-  }
-
-  public abstract class BiometricKeyAuthCallback {
-    ctor public BiometricKeyAuthCallback();
-    method public abstract void authenticateKey(javax.crypto.Cipher, androidx.security.crypto.SecureCipher.SecureAuthListener);
-    method public abstract void authenticateKey(java.security.Signature, androidx.security.crypto.SecureCipher.SecureAuthListener);
-    method public abstract void onAuthenticationError(int, CharSequence);
-    method public abstract void onAuthenticationFailed();
-    method public abstract void onAuthenticationSucceeded();
-    method public abstract void onMessage(String);
-  }
-
-  public enum BiometricKeyAuthCallback.BiometricStatus {
-    method public static androidx.security.biometric.BiometricKeyAuthCallback.BiometricStatus fromId(int);
-    method public int getType();
-    enum_constant public static final androidx.security.biometric.BiometricKeyAuthCallback.BiometricStatus ERROR;
-    enum_constant public static final androidx.security.biometric.BiometricKeyAuthCallback.BiometricStatus FAILED;
-    enum_constant public static final androidx.security.biometric.BiometricKeyAuthCallback.BiometricStatus SUCCESS;
-  }
-
-}
-
-package androidx.security.config {
-
-  public final class TldConstants {
-    field public static final java.util.List<java.lang.String> VALID_TLDS;
-  }
-
-  public enum TrustAnchorOptions {
-    method public static androidx.security.config.TrustAnchorOptions fromId(int);
-    method public int getType();
-    enum_constant public static final androidx.security.config.TrustAnchorOptions LIMITED_SYSTEM;
-    enum_constant public static final androidx.security.config.TrustAnchorOptions SYSTEM_ONLY;
-    enum_constant public static final androidx.security.config.TrustAnchorOptions USER_ONLY;
-    enum_constant public static final androidx.security.config.TrustAnchorOptions USER_SYSTEM;
-  }
-
-}
-
-package androidx.security.context {
-
-  public class SecureContextCompat {
-    ctor public SecureContextCompat(android.content.Context);
-    ctor public SecureContextCompat(android.content.Context, androidx.security.SecureConfig);
-    method public boolean deviceLocked();
-    method public void openEncryptedFileInput(String, java.util.concurrent.Executor, androidx.security.context.SecureContextCompat.EncryptedFileInputStreamListener) throws java.io.IOException;
-    method public java.io.FileOutputStream openEncryptedFileOutput(String, int) throws java.io.IOException;
-    method public java.io.FileOutputStream openEncryptedFileOutput(String, int, String) throws java.io.IOException;
-  }
-
-  public static interface SecureContextCompat.EncryptedFileInputStreamListener {
-    method public void onEncryptedFileInput(java.io.FileInputStream);
-  }
-
-}
-
 package androidx.security.crypto {
 
-  public class EphemeralSecretKey implements java.security.spec.KeySpec javax.crypto.SecretKey {
-    ctor public EphemeralSecretKey(byte[]);
-    ctor public EphemeralSecretKey(byte[], String);
-    ctor public EphemeralSecretKey(byte[], androidx.security.SecureConfig);
-    ctor public EphemeralSecretKey(byte[], String, androidx.security.SecureConfig);
-    ctor public EphemeralSecretKey(byte[], int, int, String);
-    method public void destroy();
-    method public void destroyCipherKey(javax.crypto.Cipher, int);
-    method public String getAlgorithm();
-    method public byte[] getEncoded();
-    method public String getFormat();
+  public final class EncryptedFile {
+    method public java.io.FileInputStream openFileInput() throws java.security.GeneralSecurityException, java.io.IOException;
+    method public java.io.FileOutputStream openFileOutput() throws java.security.GeneralSecurityException, java.io.IOException;
   }
 
-  public class SecureCipher {
-    ctor public SecureCipher(androidx.security.SecureConfig);
-    method public void decrypt(String, byte[], byte[], androidx.security.crypto.SecureCipher.SecureDecryptionListener);
-    method public void decryptAsymmetric(String, byte[], androidx.security.crypto.SecureCipher.SecureDecryptionListener);
-    method public void decryptEncodedData(byte[], androidx.security.crypto.SecureCipher.SecureDecryptionListener);
-    method public byte[] decryptEphemeralData(androidx.security.crypto.EphemeralSecretKey, byte[], byte[]);
-    method public byte[] encodeAsymmetricData(byte[], byte[]);
-    method public byte[] encodeEphemeralData(byte[], byte[], byte[], byte[]);
-    method public byte[] encodeSymmetricData(byte[], byte[], byte[]);
-    method public void encrypt(String, byte[], androidx.security.crypto.SecureCipher.SecureSymmetricEncryptionListener);
-    method public void encryptAsymmetric(String, byte[], androidx.security.crypto.SecureCipher.SecureAsymmetricEncryptionListener);
-    method public android.util.Pair<byte[],byte[]> encryptEphemeralData(androidx.security.crypto.EphemeralSecretKey, byte[]);
-    method public static androidx.security.crypto.SecureCipher getDefault();
-    method public static androidx.security.crypto.SecureCipher getDefault(androidx.security.biometric.BiometricKeyAuthCallback);
-    method public static androidx.security.crypto.SecureCipher getInstance(androidx.security.SecureConfig);
-    method public void sign(String, byte[], androidx.security.crypto.SecureCipher.SecureSignListener);
-    method public boolean verify(String, byte[], byte[]);
+  public static final class EncryptedFile.Builder {
+    ctor public EncryptedFile.Builder(java.io.File, android.content.Context, String, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
+    method public androidx.security.crypto.EncryptedFile build() throws java.security.GeneralSecurityException, java.io.IOException;
+    method public androidx.security.crypto.EncryptedFile.Builder setKeysetAlias(String);
+    method public androidx.security.crypto.EncryptedFile.Builder setKeysetPrefName(String);
   }
 
-  public static interface SecureCipher.SecureAsymmetricEncryptionListener {
-    method public void encryptionComplete(byte[]);
+  public enum EncryptedFile.FileEncryptionScheme {
+    enum_constant public static final androidx.security.crypto.EncryptedFile.FileEncryptionScheme AES256_GCM_HKDF_4KB;
   }
 
-  public static interface SecureCipher.SecureAuthListener {
-    method public void authComplete(androidx.security.biometric.BiometricKeyAuthCallback.BiometricStatus);
+  public final class EncryptedSharedPreferences implements android.content.SharedPreferences {
+    method public boolean contains(String?);
+    method public static android.content.SharedPreferences create(String, String, android.content.Context, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
+    method public android.content.SharedPreferences.Editor edit();
+    method public java.util.Map<java.lang.String,?> getAll();
+    method public boolean getBoolean(String?, boolean);
+    method public float getFloat(String?, float);
+    method public int getInt(String?, int);
+    method public long getLong(String?, long);
+    method public String? getString(String?, String?);
+    method public java.util.Set<java.lang.String>? getStringSet(String?, java.util.Set<java.lang.String>?);
+    method public void registerOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener);
+    method public void unregisterOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener);
   }
 
-  public static interface SecureCipher.SecureDecryptionListener {
-    method public void decryptionComplete(byte[]);
+  public enum EncryptedSharedPreferences.PrefKeyEncryptionScheme {
+    enum_constant public static final androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme AES256_SIV;
   }
 
-  public enum SecureCipher.SecureFileEncodingType {
-    method public static androidx.security.crypto.SecureCipher.SecureFileEncodingType fromId(int);
-    method public int getType();
-    enum_constant public static final androidx.security.crypto.SecureCipher.SecureFileEncodingType ASYMMETRIC;
-    enum_constant public static final androidx.security.crypto.SecureCipher.SecureFileEncodingType EPHEMERAL;
-    enum_constant public static final androidx.security.crypto.SecureCipher.SecureFileEncodingType NOT_ENCRYPTED;
-    enum_constant public static final androidx.security.crypto.SecureCipher.SecureFileEncodingType SYMMETRIC;
+  public enum EncryptedSharedPreferences.PrefValueEncryptionScheme {
+    enum_constant public static final androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme AES256_GCM;
   }
 
-  public static interface SecureCipher.SecureSignListener {
-    method public void signComplete(byte[]);
-  }
-
-  public static interface SecureCipher.SecureSymmetricEncryptionListener {
-    method public void encryptionComplete(byte[], byte[]);
-  }
-
-  public class SecureKeyGenerator {
-    method public boolean generateAsymmetricKeyPair(String);
-    method public androidx.security.crypto.EphemeralSecretKey generateEphemeralDataKey();
-    method public boolean generateKey(String);
-    method public static androidx.security.crypto.SecureKeyGenerator getDefault();
-    method public static androidx.security.crypto.SecureKeyGenerator getInstance(androidx.security.SecureConfig);
-  }
-
-  public class SecureKeyStore {
-    method public boolean checkKeyInsideSecureHardware(String);
-    method public boolean checkKeyInsideSecureHardwareAsymmetric(String);
-    method public void deleteKey(String);
-    method public static androidx.security.crypto.SecureKeyStore getDefault();
-    method public static androidx.security.crypto.SecureKeyStore getInstance(androidx.security.SecureConfig);
-    method public boolean keyExists(String);
-  }
-
-}
-
-package androidx.security.net {
-
-  public class SecureKeyManager implements android.security.KeyChainAliasCallback javax.net.ssl.X509KeyManager {
-    ctor public SecureKeyManager(String, androidx.security.SecureConfig);
-    method public void alias(String);
-    method public String chooseClientAlias(String[], java.security.Principal[], java.net.Socket);
-    method public final String chooseServerAlias(String, java.security.Principal[], java.net.Socket);
-    method public java.security.cert.X509Certificate[] getCertificateChain(String);
-    method public final String[] getClientAliases(String, java.security.Principal[]);
-    method public static androidx.security.net.SecureKeyManager getDefault(String);
-    method public static androidx.security.net.SecureKeyManager getDefault(String, androidx.security.SecureConfig);
-    method public java.security.PrivateKey getPrivateKey(String);
-    method public final String[] getServerAliases(String, java.security.Principal[]);
-    method public static androidx.security.net.SecureKeyManager installCertManually(androidx.security.net.SecureKeyManager.CertType, byte[], String, androidx.security.SecureConfig);
-    method public void setCertChain(java.security.cert.X509Certificate[]);
-    method public static void setContext(android.app.Activity);
-    method public void setPrivateKey(java.security.PrivateKey);
-  }
-
-  public enum SecureKeyManager.CertType {
-    method public static androidx.security.net.SecureKeyManager.CertType fromId(int);
-    method public int getType();
-    enum_constant public static final androidx.security.net.SecureKeyManager.CertType NOT_SUPPORTED;
-    enum_constant public static final androidx.security.net.SecureKeyManager.CertType PKCS12;
-    enum_constant public static final androidx.security.net.SecureKeyManager.CertType X509;
-  }
-
-  public class SecureURL {
-    ctor public SecureURL(String) throws java.net.MalformedURLException;
-    ctor public SecureURL(String, String) throws java.net.MalformedURLException;
-    ctor public SecureURL(String, String, androidx.security.SecureConfig) throws java.net.MalformedURLException;
-    method public String getClientCertAlias();
-    method public String getHostname();
-    method public int getPort();
-    method public boolean isValid(javax.net.ssl.HttpsURLConnection);
-    method public java.net.URLConnection openConnection() throws java.io.IOException;
-    method public java.net.URLConnection openUserTrustedCertConnection(java.util.Map<java.lang.String,java.io.InputStream>) throws java.io.IOException;
+  public final class MasterKeys {
+    ctor public MasterKeys();
+    method public static String getOrCreate(android.security.keystore.KeyGenParameterSpec) throws java.security.GeneralSecurityException, java.io.IOException;
+    field public static final android.security.keystore.KeyGenParameterSpec AES256_GCM_SPEC;
   }
 
 }
diff --git a/security/crypto/api/current.txt b/security/crypto/api/current.txt
index 261883a..44f0de6 100644
--- a/security/crypto/api/current.txt
+++ b/security/crypto/api/current.txt
@@ -1,295 +1,49 @@
 // Signature format: 3.0
-package androidx.security {
-
-  public class SecureConfig {
-    method public String getAndroidCAStore();
-    method public String getAndroidKeyStore();
-    method public String getAsymmetricBlockModes();
-    method public String getAsymmetricCipherTransformation();
-    method public String getAsymmetricKeyPairAlgorithm();
-    method public int getAsymmetricKeyPurposes();
-    method public int getAsymmetricKeySize();
-    method public String getAsymmetricPaddings();
-    method public boolean getAsymmetricRequireUserAuthEnabled();
-    method public int getAsymmetricRequireUserValiditySeconds();
-    method public boolean getAsymmetricSensitiveDataProtectionEnabled();
-    method public androidx.security.biometric.BiometricKeyAuthCallback getBiometricKeyAuthCallback();
-    method public String getCertPath();
-    method public String getCertPathValidator();
-    method public String[] getClientCertAlgorithms();
-    method public static androidx.security.SecureConfig getDefault();
-    method public String getKeystoreType();
-    method public static androidx.security.SecureConfig getNiapConfig();
-    method public static androidx.security.SecureConfig getNiapConfig(androidx.security.biometric.BiometricKeyAuthCallback);
-    method public String getSignatureAlgorithm();
-    method public String[] getStrongSSLCiphers();
-    method public String getSymmetricBlockModes();
-    method public String getSymmetricCipherTransformation();
-    method public int getSymmetricGcmTagLength();
-    method public String getSymmetricKeyAlgorithm();
-    method public int getSymmetricKeyPurposes();
-    method public int getSymmetricKeySize();
-    method public String getSymmetricPaddings();
-    method public boolean getSymmetricRequireUserAuthEnabled();
-    method public int getSymmetricRequireUserValiditySeconds();
-    method public boolean getSymmetricSensitiveDataProtectionEnabled();
-    method public androidx.security.config.TrustAnchorOptions getTrustAnchorOptions();
-    method public boolean getUseStrongSSLCiphers();
-    method public boolean getUseStrongSSLCiphersEnabled();
-    method public void setAndroidCAStore(String);
-    method public void setAndroidKeyStore(String);
-    method public void setAsymmetricBlockModes(String);
-    method public void setAsymmetricCipherTransformation(String);
-    method public void setAsymmetricKeyPairAlgorithm(String);
-    method public void setAsymmetricKeyPurposes(int);
-    method public void setAsymmetricKeySize(int);
-    method public void setAsymmetricPaddings(String);
-    method public void setAsymmetricRequireUserAuth(boolean);
-    method public void setAsymmetricRequireUserValiditySeconds(int);
-    method public void setAsymmetricSensitiveDataProtection(boolean);
-    method public void setBiometricKeyAuthCallback(androidx.security.biometric.BiometricKeyAuthCallback);
-    method public void setCertPath(String);
-    method public void setCertPathValidator(String);
-    method public void setClientCertAlgorithms(String[]);
-    method public void setKeystoreType(String);
-    method public void setSignatureAlgorithm(String);
-    method public void setStrongSSLCiphers(String[]);
-    method public void setSymmetricBlockModes(String);
-    method public void setSymmetricCipherTransformation(String);
-    method public void setSymmetricGcmTagLength(int);
-    method public void setSymmetricKeyAlgorithm(String);
-    method public void setSymmetricKeyPurposes(int);
-    method public void setSymmetricKeySize(int);
-    method public void setSymmetricPaddings(String);
-    method public void setSymmetricRequireUserAuth(boolean);
-    method public void setSymmetricRequireUserValiditySeconds(int);
-    method public void setSymmetricSensitiveDataProtection(boolean);
-    method public void setTrustAnchorOptions(androidx.security.config.TrustAnchorOptions);
-    method public void setUseStrongSSLCiphers(boolean);
-    field public static final int AES_IV_SIZE_BYTES = 16; // 0x10
-    field public static final String ANDROID_CA_STORE = "AndroidCAStore";
-    field public static final String ANDROID_KEYSTORE = "AndroidKeyStore";
-    field public static final String SSL_TLS = "TLS";
-  }
-
-  public static class SecureConfig.Builder {
-    ctor public SecureConfig.Builder();
-    method public androidx.security.SecureConfig build();
-    method public androidx.security.SecureConfig.Builder forKeyStoreType(String);
-    method public androidx.security.SecureConfig.Builder setAsymmetricBlockModes(String);
-    method public androidx.security.SecureConfig.Builder setAsymmetricCipherTransformation(String);
-    method public androidx.security.SecureConfig.Builder setAsymmetricKeyPairAlgorithm(String);
-    method public androidx.security.SecureConfig.Builder setAsymmetricKeyPurposes(int);
-    method public androidx.security.SecureConfig.Builder setAsymmetricKeySize(int);
-    method public androidx.security.SecureConfig.Builder setAsymmetricPaddings(String);
-    method public androidx.security.SecureConfig.Builder setAsymmetricRequireUserAuth(boolean);
-    method public androidx.security.SecureConfig.Builder setAsymmetricRequireUserValiditySeconds(int);
-    method public androidx.security.SecureConfig.Builder setAsymmetricSensitiveDataProtection(boolean);
-    method public androidx.security.SecureConfig.Builder setBiometricKeyAuthCallback(androidx.security.biometric.BiometricKeyAuthCallback);
-    method public androidx.security.SecureConfig.Builder setCertPath(String);
-    method public androidx.security.SecureConfig.Builder setCertPathValidator(String);
-    method public androidx.security.SecureConfig.Builder setClientCertAlgorithms(String[]);
-    method public androidx.security.SecureConfig.Builder setSignatureAlgorithm(String);
-    method public androidx.security.SecureConfig.Builder setStrongSSLCiphers(String[]);
-    method public androidx.security.SecureConfig.Builder setSymmetricBlockModes(String);
-    method public androidx.security.SecureConfig.Builder setSymmetricCipherTransformation(String);
-    method public androidx.security.SecureConfig.Builder setSymmetricGcmTagLength(int);
-    method public androidx.security.SecureConfig.Builder setSymmetricKeyAlgorithm(String);
-    method public androidx.security.SecureConfig.Builder setSymmetricKeyPurposes(int);
-    method public androidx.security.SecureConfig.Builder setSymmetricKeySize(int);
-    method public androidx.security.SecureConfig.Builder setSymmetricPaddings(String);
-    method public androidx.security.SecureConfig.Builder setSymmetricRequireUserAuth(boolean);
-    method public androidx.security.SecureConfig.Builder setSymmetricRequireUserValiditySeconds(int);
-    method public androidx.security.SecureConfig.Builder setSymmetricSensitiveDataProtection(boolean);
-    method public androidx.security.SecureConfig.Builder setTrustAnchorOptions(androidx.security.config.TrustAnchorOptions);
-    method public androidx.security.SecureConfig.Builder setUseStrongSSLCiphers(boolean);
-  }
-
-}
-
-package androidx.security.biometric {
-
-  public class BiometricKeyAuth extends androidx.biometric.BiometricPrompt.AuthenticationCallback {
-    ctor public BiometricKeyAuth(androidx.fragment.app.FragmentActivity, androidx.security.biometric.BiometricKeyAuthCallback);
-    method public void authenticateKey(javax.crypto.Cipher, androidx.biometric.BiometricPrompt.PromptInfo, androidx.security.crypto.SecureCipher.SecureAuthListener);
-    method public void authenticateKey(java.security.Signature, androidx.biometric.BiometricPrompt.PromptInfo, androidx.security.crypto.SecureCipher.SecureAuthListener);
-    method public void authenticateKey(javax.crypto.Cipher, androidx.security.crypto.SecureCipher.SecureAuthListener);
-    method public void authenticateKey(java.security.Signature, androidx.security.crypto.SecureCipher.SecureAuthListener);
-  }
-
-  public abstract class BiometricKeyAuthCallback {
-    ctor public BiometricKeyAuthCallback();
-    method public abstract void authenticateKey(javax.crypto.Cipher, androidx.security.crypto.SecureCipher.SecureAuthListener);
-    method public abstract void authenticateKey(java.security.Signature, androidx.security.crypto.SecureCipher.SecureAuthListener);
-    method public abstract void onAuthenticationError(int, CharSequence);
-    method public abstract void onAuthenticationFailed();
-    method public abstract void onAuthenticationSucceeded();
-    method public abstract void onMessage(String);
-  }
-
-  public enum BiometricKeyAuthCallback.BiometricStatus {
-    method public static androidx.security.biometric.BiometricKeyAuthCallback.BiometricStatus fromId(int);
-    method public int getType();
-    enum_constant public static final androidx.security.biometric.BiometricKeyAuthCallback.BiometricStatus ERROR;
-    enum_constant public static final androidx.security.biometric.BiometricKeyAuthCallback.BiometricStatus FAILED;
-    enum_constant public static final androidx.security.biometric.BiometricKeyAuthCallback.BiometricStatus SUCCESS;
-  }
-
-}
-
-package androidx.security.config {
-
-  public final class TldConstants {
-    field public static final java.util.List<java.lang.String> VALID_TLDS;
-  }
-
-  public enum TrustAnchorOptions {
-    method public static androidx.security.config.TrustAnchorOptions fromId(int);
-    method public int getType();
-    enum_constant public static final androidx.security.config.TrustAnchorOptions LIMITED_SYSTEM;
-    enum_constant public static final androidx.security.config.TrustAnchorOptions SYSTEM_ONLY;
-    enum_constant public static final androidx.security.config.TrustAnchorOptions USER_ONLY;
-    enum_constant public static final androidx.security.config.TrustAnchorOptions USER_SYSTEM;
-  }
-
-}
-
-package androidx.security.context {
-
-  public class SecureContextCompat {
-    ctor public SecureContextCompat(android.content.Context);
-    ctor public SecureContextCompat(android.content.Context, androidx.security.SecureConfig);
-    method public boolean deviceLocked();
-    method public void openEncryptedFileInput(String, java.util.concurrent.Executor, androidx.security.context.SecureContextCompat.EncryptedFileInputStreamListener) throws java.io.IOException;
-    method public java.io.FileOutputStream openEncryptedFileOutput(String, int) throws java.io.IOException;
-    method public java.io.FileOutputStream openEncryptedFileOutput(String, int, String) throws java.io.IOException;
-  }
-
-  public static interface SecureContextCompat.EncryptedFileInputStreamListener {
-    method public void onEncryptedFileInput(java.io.FileInputStream);
-  }
-
-}
-
 package androidx.security.crypto {
 
-  public class EphemeralSecretKey implements java.security.spec.KeySpec javax.crypto.SecretKey {
-    ctor public EphemeralSecretKey(byte[]);
-    ctor public EphemeralSecretKey(byte[], String);
-    ctor public EphemeralSecretKey(byte[], androidx.security.SecureConfig);
-    ctor public EphemeralSecretKey(byte[], String, androidx.security.SecureConfig);
-    ctor public EphemeralSecretKey(byte[], int, int, String);
-    method public void destroy();
-    method public void destroyCipherKey(javax.crypto.Cipher, int);
-    method public String getAlgorithm();
-    method public byte[] getEncoded();
-    method public String getFormat();
+  public final class EncryptedFile {
+    method public java.io.FileInputStream openFileInput() throws java.security.GeneralSecurityException, java.io.IOException;
+    method public java.io.FileOutputStream openFileOutput() throws java.security.GeneralSecurityException, java.io.IOException;
   }
 
-  public class SecureCipher {
-    ctor public SecureCipher(androidx.security.SecureConfig);
-    method public void decrypt(String, byte[], byte[], androidx.security.crypto.SecureCipher.SecureDecryptionListener);
-    method public void decryptAsymmetric(String, byte[], androidx.security.crypto.SecureCipher.SecureDecryptionListener);
-    method public void decryptEncodedData(byte[], androidx.security.crypto.SecureCipher.SecureDecryptionListener);
-    method public byte[] decryptEphemeralData(androidx.security.crypto.EphemeralSecretKey, byte[], byte[]);
-    method public byte[] encodeAsymmetricData(byte[], byte[]);
-    method public byte[] encodeEphemeralData(byte[], byte[], byte[], byte[]);
-    method public byte[] encodeSymmetricData(byte[], byte[], byte[]);
-    method public void encrypt(String, byte[], androidx.security.crypto.SecureCipher.SecureSymmetricEncryptionListener);
-    method public void encryptAsymmetric(String, byte[], androidx.security.crypto.SecureCipher.SecureAsymmetricEncryptionListener);
-    method public android.util.Pair<byte[],byte[]> encryptEphemeralData(androidx.security.crypto.EphemeralSecretKey, byte[]);
-    method public static androidx.security.crypto.SecureCipher getDefault();
-    method public static androidx.security.crypto.SecureCipher getDefault(androidx.security.biometric.BiometricKeyAuthCallback);
-    method public static androidx.security.crypto.SecureCipher getInstance(androidx.security.SecureConfig);
-    method public void sign(String, byte[], androidx.security.crypto.SecureCipher.SecureSignListener);
-    method public boolean verify(String, byte[], byte[]);
+  public static final class EncryptedFile.Builder {
+    ctor public EncryptedFile.Builder(java.io.File, android.content.Context, String, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
+    method public androidx.security.crypto.EncryptedFile build() throws java.security.GeneralSecurityException, java.io.IOException;
+    method public androidx.security.crypto.EncryptedFile.Builder setKeysetAlias(String);
+    method public androidx.security.crypto.EncryptedFile.Builder setKeysetPrefName(String);
   }
 
-  public static interface SecureCipher.SecureAsymmetricEncryptionListener {
-    method public void encryptionComplete(byte[]);
+  public enum EncryptedFile.FileEncryptionScheme {
+    enum_constant public static final androidx.security.crypto.EncryptedFile.FileEncryptionScheme AES256_GCM_HKDF_4KB;
   }
 
-  public static interface SecureCipher.SecureAuthListener {
-    method public void authComplete(androidx.security.biometric.BiometricKeyAuthCallback.BiometricStatus);
+  public final class EncryptedSharedPreferences implements android.content.SharedPreferences {
+    method public boolean contains(String?);
+    method public static android.content.SharedPreferences create(String, String, android.content.Context, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
+    method public android.content.SharedPreferences.Editor edit();
+    method public java.util.Map<java.lang.String,?> getAll();
+    method public boolean getBoolean(String?, boolean);
+    method public float getFloat(String?, float);
+    method public int getInt(String?, int);
+    method public long getLong(String?, long);
+    method public String? getString(String?, String?);
+    method public java.util.Set<java.lang.String>? getStringSet(String?, java.util.Set<java.lang.String>?);
+    method public void registerOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener);
+    method public void unregisterOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener);
   }
 
-  public static interface SecureCipher.SecureDecryptionListener {
-    method public void decryptionComplete(byte[]);
+  public enum EncryptedSharedPreferences.PrefKeyEncryptionScheme {
+    enum_constant public static final androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme AES256_SIV;
   }
 
-  public enum SecureCipher.SecureFileEncodingType {
-    method public static androidx.security.crypto.SecureCipher.SecureFileEncodingType fromId(int);
-    method public int getType();
-    enum_constant public static final androidx.security.crypto.SecureCipher.SecureFileEncodingType ASYMMETRIC;
-    enum_constant public static final androidx.security.crypto.SecureCipher.SecureFileEncodingType EPHEMERAL;
-    enum_constant public static final androidx.security.crypto.SecureCipher.SecureFileEncodingType NOT_ENCRYPTED;
-    enum_constant public static final androidx.security.crypto.SecureCipher.SecureFileEncodingType SYMMETRIC;
+  public enum EncryptedSharedPreferences.PrefValueEncryptionScheme {
+    enum_constant public static final androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme AES256_GCM;
   }
 
-  public static interface SecureCipher.SecureSignListener {
-    method public void signComplete(byte[]);
-  }
-
-  public static interface SecureCipher.SecureSymmetricEncryptionListener {
-    method public void encryptionComplete(byte[], byte[]);
-  }
-
-  public class SecureKeyGenerator {
-    method public boolean generateAsymmetricKeyPair(String);
-    method public androidx.security.crypto.EphemeralSecretKey generateEphemeralDataKey();
-    method public boolean generateKey(String);
-    method public static androidx.security.crypto.SecureKeyGenerator getDefault();
-    method public static androidx.security.crypto.SecureKeyGenerator getInstance(androidx.security.SecureConfig);
-  }
-
-  public class SecureKeyStore {
-    method public boolean checkKeyInsideSecureHardware(String);
-    method public boolean checkKeyInsideSecureHardwareAsymmetric(String);
-    method public void deleteKey(String);
-    method public static androidx.security.crypto.SecureKeyStore getDefault();
-    method public static androidx.security.crypto.SecureKeyStore getInstance(androidx.security.SecureConfig);
-    method public boolean keyExists(String);
-  }
-
-}
-
-package androidx.security.net {
-
-  public class SecureKeyManager implements android.security.KeyChainAliasCallback javax.net.ssl.X509KeyManager {
-    ctor public SecureKeyManager(String, androidx.security.SecureConfig);
-    method public void alias(String);
-    method public String chooseClientAlias(String[], java.security.Principal[], java.net.Socket);
-    method public final String chooseServerAlias(String, java.security.Principal[], java.net.Socket);
-    method public java.security.cert.X509Certificate[] getCertificateChain(String);
-    method public final String[] getClientAliases(String, java.security.Principal[]);
-    method public static androidx.security.net.SecureKeyManager getDefault(String);
-    method public static androidx.security.net.SecureKeyManager getDefault(String, androidx.security.SecureConfig);
-    method public java.security.PrivateKey getPrivateKey(String);
-    method public final String[] getServerAliases(String, java.security.Principal[]);
-    method public static androidx.security.net.SecureKeyManager installCertManually(androidx.security.net.SecureKeyManager.CertType, byte[], String, androidx.security.SecureConfig);
-    method public void setCertChain(java.security.cert.X509Certificate[]);
-    method public static void setContext(android.app.Activity);
-    method public void setPrivateKey(java.security.PrivateKey);
-  }
-
-  public enum SecureKeyManager.CertType {
-    method public static androidx.security.net.SecureKeyManager.CertType fromId(int);
-    method public int getType();
-    enum_constant public static final androidx.security.net.SecureKeyManager.CertType NOT_SUPPORTED;
-    enum_constant public static final androidx.security.net.SecureKeyManager.CertType PKCS12;
-    enum_constant public static final androidx.security.net.SecureKeyManager.CertType X509;
-  }
-
-  public class SecureURL {
-    ctor public SecureURL(String) throws java.net.MalformedURLException;
-    ctor public SecureURL(String, String) throws java.net.MalformedURLException;
-    ctor public SecureURL(String, String, androidx.security.SecureConfig) throws java.net.MalformedURLException;
-    method public String getClientCertAlias();
-    method public String getHostname();
-    method public int getPort();
-    method public boolean isValid(javax.net.ssl.HttpsURLConnection);
-    method public java.net.URLConnection openConnection() throws java.io.IOException;
-    method public java.net.URLConnection openUserTrustedCertConnection(java.util.Map<java.lang.String,java.io.InputStream>) throws java.io.IOException;
+  public final class MasterKeys {
+    ctor public MasterKeys();
+    method public static String getOrCreate(android.security.keystore.KeyGenParameterSpec) throws java.security.GeneralSecurityException, java.io.IOException;
+    field public static final android.security.keystore.KeyGenParameterSpec AES256_GCM_SPEC;
   }
 
 }
diff --git a/security/crypto/api/restricted_1.0.0-alpha01.txt b/security/crypto/api/restricted_1.0.0-alpha01.txt
index 23579e8..da4f6cc 100644
--- a/security/crypto/api/restricted_1.0.0-alpha01.txt
+++ b/security/crypto/api/restricted_1.0.0-alpha01.txt
@@ -1,6 +1 @@
 // Signature format: 3.0
-package androidx.security.crypto {
-
-
-}
-
diff --git a/security/crypto/api/restricted_current.txt b/security/crypto/api/restricted_current.txt
index 23579e8..da4f6cc 100644
--- a/security/crypto/api/restricted_current.txt
+++ b/security/crypto/api/restricted_current.txt
@@ -1,6 +1 @@
 // Signature format: 3.0
-package androidx.security.crypto {
-
-
-}
-
diff --git a/security/crypto/build.gradle b/security/crypto/build.gradle
index edcda67..02c34ea 100644
--- a/security/crypto/build.gradle
+++ b/security/crypto/build.gradle
@@ -28,12 +28,16 @@
     api("androidx.annotation:annotation:1.0.0") { transitive = true}
     api("androidx.core:core:1.0.0") { transitive = true}
     api("androidx.fragment:fragment:1.0.0") { transitive = true}
-    api("androidx.biometric:biometric:1.0.0-alpha03")
 
+    api("com.google.crypto.tink:tink-android:1.2.2")
+
+    androidTestImplementation("androidx.test.ext:junit:1.1.0")
     androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
     androidTestImplementation(ANDROIDX_TEST_CORE)
     androidTestImplementation(ANDROIDX_TEST_RUNNER)
+    androidTestImplementation(ANDROIDX_TEST_RULES)
     androidTestImplementation(MOCKITO_CORE)
+
 }
 
 android {
diff --git a/security/crypto/src/androidTest/AndroidManifest.xml b/security/crypto/src/androidTest/AndroidManifest.xml
index 4c91032..39f2d9c 100644
--- a/security/crypto/src/androidTest/AndroidManifest.xml
+++ b/security/crypto/src/androidTest/AndroidManifest.xml
@@ -18,4 +18,7 @@
           package="androidx.security.tests">
     <uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
     <uses-permission android:name="android.permission.INTERNET" />
+    <application>
+    </application>
+
 </manifest>
diff --git a/security/crypto/src/androidTest/java/androidx/security/context/SecureContextCompatTest.java b/security/crypto/src/androidTest/java/androidx/security/context/SecureContextCompatTest.java
deleted file mode 100644
index c526f13..0000000
--- a/security/crypto/src/androidTest/java/androidx/security/context/SecureContextCompatTest.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.context;
-
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.security.SecureConfig;
-import androidx.security.crypto.SecureKeyGenerator;
-import androidx.test.core.app.ApplicationProvider;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-@SuppressWarnings("unchecked")
-@RunWith(JUnit4.class)
-public class SecureContextCompatTest {
-
-
-    private Context mContext;
-    private static final String KEYPAIR = "file_key";
-
-
-    @Before
-    public void setup() {
-        mContext = ApplicationProvider.getApplicationContext();
-        SecureKeyGenerator keyGenerator = SecureKeyGenerator.getInstance(SecureConfig.getDefault());
-        keyGenerator.generateAsymmetricKeyPair(KEYPAIR);
-    }
-
-    @Test
-    public void testWriteEncryptedFile() {
-        String fileContent = "SOME TEST DATA!";
-        String fileName = "test_file";
-
-        SecureContextCompat secureContextCompat = new SecureContextCompat(mContext);
-        try {
-            FileOutputStream outputStream = secureContextCompat.openEncryptedFileOutput(fileName,
-                    Context.MODE_PRIVATE, KEYPAIR);
-            outputStream.write(fileContent.getBytes("UTF-8"));
-            outputStream.flush();
-            outputStream.close();
-
-            FileInputStream fileInputStream = mContext.openFileInput(fileName);
-            byte[] rawBytes = new byte[fileInputStream.available()];
-            fileInputStream.read(rawBytes);
-            Assert.assertNotEquals("Contents should differ, data was not encrypted.",
-                    fileContent, new String(rawBytes, "UTF-8"));
-        } catch (IOException ex) {
-            ex.printStackTrace();
-        }
-    }
-
-    @Test
-    public void testReadEncryptedFile() {
-        final String fileContent = "SOME TEST DATA!";
-        final String fileName = "test_file";
-        SecureContextCompat secureContextCompat = new SecureContextCompat(mContext);
-        try {
-            secureContextCompat.openEncryptedFileInput(fileName,
-                    null,
-                    new SecureContextCompat.EncryptedFileInputStreamListener() {
-                        @Override
-                        public void onEncryptedFileInput(@NonNull FileInputStream inputStream) {
-                            try {
-                                byte[] rawBytes = new byte[inputStream.available()];
-                                inputStream.read(rawBytes);
-                                Assert.assertNotEquals(
-                                        "Contents should be equal, data was encrypted.",
-                                        fileContent, new String(rawBytes, "UTF-8"));
-                            } catch (Exception ex) {
-                                ex.printStackTrace();
-                            }
-                        }
-                    });
-
-        } catch (IOException ex) {
-            ex.printStackTrace();
-        }
-    }
-
-}
-
diff --git a/security/crypto/src/androidTest/java/androidx/security/crypto/EncryptedFileTest.java b/security/crypto/src/androidTest/java/androidx/security/crypto/EncryptedFileTest.java
new file mode 100644
index 0000000..3f1b6666
--- /dev/null
+++ b/security/crypto/src/androidTest/java/androidx/security/crypto/EncryptedFileTest.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 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.security.crypto;
+
+import static androidx.security.crypto.MasterKeys.KEYSTORE_PATH_URI;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.StreamingAead;
+import com.google.crypto.tink.config.TinkConfig;
+import com.google.crypto.tink.integration.android.AndroidKeysetManager;
+import com.google.crypto.tink.streamingaead.StreamingAeadFactory;
+import com.google.crypto.tink.streamingaead.StreamingAeadKeyTemplates;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.KeyStore;
+
+@SuppressWarnings("unchecked")
+@RunWith(JUnit4.class)
+public class EncryptedFileTest {
+
+
+    private Context mContext;
+    private String mMasterKeyAlias;
+
+    @Before
+    public void setup() throws Exception {
+        mContext = ApplicationProvider.getApplicationContext();
+
+        SharedPreferences sharedPreferences = mContext.getSharedPreferences(
+                "__androidx_security_crypto_encrypted_file_pref__", Context.MODE_PRIVATE);
+        sharedPreferences.edit().clear().commit();
+
+
+        // Delete old keys for testing
+        String filePath = mContext.getFilesDir().getParent() + "/shared_prefs/"
+                + "__androidx_security_crypto_encrypted_file_pref__";
+        File deletePrefFile = new File(filePath);
+        deletePrefFile.delete();
+
+        filePath = mContext.getFilesDir().getParent() + "nothing_to_see_here";
+        deletePrefFile = new File(filePath);
+        deletePrefFile.delete();
+
+        File dataFile = new File(mContext.getFilesDir(), "nothing_to_see_here");
+        dataFile.delete();
+
+        dataFile = new File(mContext.getFilesDir(), "nothing_to_see_here_custom");
+        dataFile.delete();
+
+        dataFile = new File(mContext.getFilesDir(), "tink_test_file");
+        dataFile.delete();
+
+        // Delete MasterKeys
+        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+        keyStore.load(null);
+        keyStore.deleteEntry(MasterKeys.MASTER_KEY_ALIAS);
+
+        mMasterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);
+    }
+
+    @Test
+    public void testWriteReadEncryptedFile() throws Exception {
+        final String fileContent = "Don't tell anyone...";
+        final String fileName = "nothing_to_see_here";
+
+        // Write
+
+        EncryptedFile encryptedFile = new EncryptedFile.Builder(new File(mContext.getFilesDir(),
+                fileName), mContext, mMasterKeyAlias,
+                EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB)
+                .build();
+
+        OutputStream outputStream = encryptedFile.openFileOutput();
+        outputStream.write(fileContent.getBytes("UTF-8"));
+        outputStream.flush();
+        outputStream.close();
+
+        FileInputStream rawStream = mContext.openFileInput(fileName);
+        ByteArrayOutputStream rawByteArrayOutputStream = new ByteArrayOutputStream();
+        int rawNextByte = rawStream.read();
+        while (rawNextByte != -1) {
+            rawByteArrayOutputStream.write(rawNextByte);
+            rawNextByte = rawStream.read();
+        }
+        byte[] rawCipherText = rawByteArrayOutputStream.toByteArray();
+        System.out.println("Raw CipherText = " + new String(rawCipherText,
+                UTF_8));
+        rawStream.close();
+
+        InputStream inputStream = encryptedFile.openFileInput();
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        int nextByte = inputStream.read();
+        while (nextByte != -1) {
+            byteArrayOutputStream.write(nextByte);
+            nextByte = inputStream.read();
+        }
+
+        byte[] plainText = byteArrayOutputStream.toByteArray();
+
+        System.out.println("Decrypted Data: " + new String(plainText,
+                UTF_8));
+
+        Assert.assertEquals(
+                "Contents should be equal, data was encrypted.",
+                fileContent, new String(plainText, "UTF-8"));
+        inputStream.close();
+
+
+        EncryptedFile existingFileInputCheck = new EncryptedFile.Builder(
+                new File(mContext.getFilesDir(), "FAKE_FILE"), mContext, mMasterKeyAlias,
+                EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB)
+                .build();
+        boolean inputFailed = false;
+        try {
+            existingFileInputCheck.openFileInput();
+        } catch (IOException ex) {
+            inputFailed = true;
+        }
+        Assert.assertTrue("File should have failed opening.", inputFailed);
+
+        EncryptedFile existingFileOutputCheck = new EncryptedFile.Builder(
+                new File(mContext.getFilesDir(), fileName), mContext, mMasterKeyAlias,
+                EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB)
+                .build();
+        boolean outputFailed = false;
+        try {
+            existingFileOutputCheck.openFileOutput();
+        } catch (IOException ex) {
+            outputFailed = true;
+        }
+        Assert.assertTrue("File should have failed writing.", outputFailed);
+
+    }
+
+    @Test
+    public void testWriteReadEncryptedFileCustomPrefs() throws Exception {
+        final String fileContent = "Don't tell anyone...!!!!!";
+        final String fileName = "nothing_to_see_here_custom";
+
+        // Write
+        EncryptedFile encryptedFile = new EncryptedFile.Builder(new File(mContext.getFilesDir(),
+                fileName), mContext, mMasterKeyAlias,
+                EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB)
+                .setKeysetAlias("CustomKEYALIAS")
+                .setKeysetPrefName("CUSTOMPREFNAME")
+                .build();
+
+        OutputStream outputStream = encryptedFile.openFileOutput();
+        outputStream.write(fileContent.getBytes("UTF-8"));
+        outputStream.flush();
+        outputStream.close();
+
+        FileInputStream rawStream = mContext.openFileInput(fileName);
+        ByteArrayOutputStream rawByteArrayOutputStream = new ByteArrayOutputStream();
+        int rawNextByte = rawStream.read();
+        while (rawNextByte != -1) {
+            rawByteArrayOutputStream.write(rawNextByte);
+            rawNextByte = rawStream.read();
+        }
+        byte[] rawCipherText = rawByteArrayOutputStream.toByteArray();
+        System.out.println("Raw CipherText = " + new String(rawCipherText,
+                UTF_8));
+        rawStream.close();
+
+        InputStream inputStream = encryptedFile.openFileInput();
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        int nextByte = inputStream.read();
+        while (nextByte != -1) {
+            byteArrayOutputStream.write(nextByte);
+            nextByte = inputStream.read();
+        }
+
+        byte[] plainText = byteArrayOutputStream.toByteArray();
+
+        System.out.println("Decrypted Data: " + new String(plainText,
+                UTF_8));
+
+        Assert.assertEquals(
+                "Contents should be equal, data was encrypted.",
+                fileContent, new String(plainText, "UTF-8"));
+        inputStream.close();
+
+        SharedPreferences sharedPreferences = mContext.getSharedPreferences("CUSTOMPREFNAME",
+                Context.MODE_PRIVATE);
+        boolean containsKeyset = sharedPreferences.contains("CustomKEYALIAS");
+        Assert.assertTrue("Keyset should have existed.", containsKeyset);
+    }
+
+    @Test
+    public void tinkTest() throws Exception {
+        final String fileContent = "Don't tell anyone...";
+        final String fileName = "tink_test_file";
+        File file = new File(mContext.getFilesDir(), fileName);
+
+        // Write
+        EncryptedFile encryptedFile = new EncryptedFile.Builder(file, mContext, mMasterKeyAlias,
+                EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB)
+                .build();
+
+        OutputStream outputStream = encryptedFile.openFileOutput();
+        outputStream.write(fileContent.getBytes(UTF_8));
+        outputStream.flush();
+        outputStream.close();
+
+        TinkConfig.register();
+        KeysetHandle streadmingAeadKeysetHandle = new AndroidKeysetManager.Builder()
+                .withKeyTemplate(StreamingAeadKeyTemplates.AES256_GCM_HKDF_4KB)
+                .withSharedPref(mContext,
+                        "__androidx_security_crypto_encrypted_file_keyset__",
+                        "__androidx_security_crypto_encrypted_file_pref__")
+                .withMasterKeyUri(KEYSTORE_PATH_URI + mMasterKeyAlias)
+                .build().getKeysetHandle();
+
+        StreamingAead streamingAead = StreamingAeadFactory.getPrimitive(
+                streadmingAeadKeysetHandle);
+
+        FileInputStream fileInputStream = new FileInputStream(file);
+        InputStream inputStream = streamingAead.newDecryptingStream(fileInputStream,
+                file.getName().getBytes(UTF_8));
+
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        int nextByte = inputStream.read();
+        while (nextByte != -1) {
+            byteArrayOutputStream.write(nextByte);
+            nextByte = inputStream.read();
+        }
+
+        byte[] plainText = byteArrayOutputStream.toByteArray();
+
+        System.out.println("Decrypted Data: " + new String(plainText,
+                UTF_8));
+
+        Assert.assertEquals(
+                "Contents should be equal, data was encrypted.",
+                fileContent, new String(plainText, "UTF-8"));
+        inputStream.close();
+    }
+
+}
+
diff --git a/security/crypto/src/androidTest/java/androidx/security/crypto/EncryptedSharedPreferencesTest.java b/security/crypto/src/androidTest/java/androidx/security/crypto/EncryptedSharedPreferencesTest.java
new file mode 100644
index 0000000..cf405fc
--- /dev/null
+++ b/security/crypto/src/androidTest/java/androidx/security/crypto/EncryptedSharedPreferencesTest.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright 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.security.crypto;
+
+import static android.content.Context.MODE_PRIVATE;
+
+import static androidx.security.crypto.MasterKeys.KEYSTORE_PATH_URI;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.ArraySet;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.google.crypto.tink.Aead;
+import com.google.crypto.tink.DeterministicAead;
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.aead.AeadFactory;
+import com.google.crypto.tink.aead.AeadKeyTemplates;
+import com.google.crypto.tink.config.TinkConfig;
+import com.google.crypto.tink.daead.DeterministicAeadFactory;
+import com.google.crypto.tink.daead.DeterministicAeadKeyTemplates;
+import com.google.crypto.tink.integration.android.AndroidKeysetManager;
+import com.google.crypto.tink.subtle.Base64;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.security.KeyStore;
+import java.util.Set;
+
+@SuppressWarnings("unchecked")
+@RunWith(JUnit4.class)
+public class EncryptedSharedPreferencesTest {
+
+    private Context mContext;
+    private String mKeyAlias;
+
+    private static final String PREFS_FILE = "test_shared_prefs";
+
+    @Before
+    public void setup() throws Exception {
+
+        mContext = ApplicationProvider.getApplicationContext();
+
+        // Delete all previous keys and shared preferences.
+
+        String filePath = mContext.getFilesDir().getParent() + "/shared_prefs/"
+                + "__androidx_security__crypto_encrypted_prefs__";
+        File deletePrefFile = new File(filePath);
+        deletePrefFile.delete();
+
+        SharedPreferences notEncryptedSharedPrefs = mContext.getSharedPreferences(PREFS_FILE,
+                MODE_PRIVATE);
+        notEncryptedSharedPrefs.edit().clear().commit();
+
+        filePath = mContext.getFilesDir().getParent() + "/shared_prefs/"
+                + PREFS_FILE;
+        deletePrefFile = new File(filePath);
+        deletePrefFile.delete();
+
+        SharedPreferences encryptedSharedPrefs = mContext.getSharedPreferences("TinkTestPrefs",
+                MODE_PRIVATE);
+        encryptedSharedPrefs.edit().clear().commit();
+
+        filePath = mContext.getFilesDir().getParent() + "/shared_prefs/"
+                + "TinkTestPrefs";
+        deletePrefFile = new File(filePath);
+        deletePrefFile.delete();
+
+        // Delete MasterKeys
+        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+        keyStore.load(null);
+        keyStore.deleteEntry("_androidx_security_master_key_");
+
+        mKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);
+    }
+
+    @Test
+    public void testWriteSharedPrefs() throws Exception {
+
+        SharedPreferences sharedPreferences = EncryptedSharedPreferences
+                .create(PREFS_FILE,
+                        mKeyAlias, mContext,
+                        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
+                        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM);
+
+        SharedPreferences.Editor editor = sharedPreferences.edit();
+
+        // String Test
+        final String stringTestKey = "StringTest";
+        final String stringTestValue = "THIS IS A TEST STRING";
+        editor.putString(stringTestKey, stringTestValue);
+
+        SharedPreferences.OnSharedPreferenceChangeListener listener =
+                new SharedPreferences.OnSharedPreferenceChangeListener() {
+                    @Override
+                    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+                            String key) {
+                        Assert.assertEquals(stringTestValue,
+                                sharedPreferences.getString(stringTestKey, null));
+                    }
+                };
+
+        sharedPreferences.registerOnSharedPreferenceChangeListener(listener);
+
+
+        // String Set Test
+        String stringSetTestKey = "StringSetTest";
+        Set<String> stringSetValue = new ArraySet<>();
+        stringSetValue.add("Test1");
+        stringSetValue.add("Test2");
+        editor.putStringSet(stringSetTestKey, stringSetValue);
+
+
+        // Int Test
+        String intTestKey = "IntTest";
+        int intTestValue = 1000;
+        editor.putInt(intTestKey, intTestValue);
+
+        // Long Test
+        String longTestKey = "LongTest";
+        long longTestValue = 500L;
+        editor.putLong(longTestKey, longTestValue);
+
+        // Boolean Test
+        String booleanTestKey = "BooleanTest";
+        boolean booleanTestValue = true;
+        editor.putBoolean(booleanTestKey, booleanTestValue);
+
+        // Float Test
+        String floatTestKey = "FloatTest";
+        float floatTestValue = 250.5f;
+        editor.putFloat(floatTestKey, floatTestValue);
+
+        // Null Key Test
+        String nullKey = null;
+        String nullStringValue = "NULL_KEY";
+        editor.putString(nullKey, nullStringValue);
+
+        editor.commit();
+
+        // String Test Assertion
+        Assert.assertEquals(stringTestKey + " has the wrong value",
+                stringTestValue,
+                sharedPreferences.getString(stringTestKey, null));
+
+        // StringSet Test Assertion
+        Set<String> stringSetPrefsValue = sharedPreferences.getStringSet(stringSetTestKey, null);
+        String stringSetTestValue = null;
+        if (!stringSetPrefsValue.isEmpty()) {
+            stringSetTestValue = stringSetPrefsValue.iterator().next();
+        }
+        Assert.assertEquals(stringSetTestKey + " has the wrong value",
+                ((ArraySet<String>) stringSetValue).valueAt(0),
+                stringSetTestValue);
+
+        // Int Test Assertion
+        Assert.assertEquals(intTestKey + " has the wrong value",
+                intTestValue,
+                sharedPreferences.getInt(intTestKey, 0));
+
+        // Long Test Assertion
+        Assert.assertEquals(longTestKey + " has the wrong value",
+                longTestValue,
+                sharedPreferences.getLong(longTestKey, 0L));
+
+        // Boolean Test Assertion
+        Assert.assertEquals(booleanTestKey + " has the wrong value",
+                booleanTestValue,
+                sharedPreferences.getBoolean(booleanTestKey, false));
+
+        // Float Test Assertion
+        Assert.assertEquals(floatTestValue,
+                sharedPreferences.getFloat(floatTestKey, 0.0f),
+                0.0f);
+
+        // Null Key Test Assertion
+        Assert.assertEquals(nullKey + " has the wrong value",
+                nullStringValue,
+                sharedPreferences.getString(nullKey, null));
+
+        Assert.assertTrue(nullKey + " should exist", sharedPreferences.contains(nullKey));
+
+        // Test Remove
+        editor.remove(nullKey);
+        editor.commit();
+
+        Assert.assertEquals(nullKey + " should have been removed.",
+                null,
+                sharedPreferences.getString(nullKey, null));
+
+        Assert.assertFalse(nullKey + " should not exist",
+                sharedPreferences.contains(nullKey));
+
+        // Null String Key and value Test Assertion
+        editor.putString(null, null);
+        editor.putStringSet(null, null);
+        editor.commit();
+        Assert.assertEquals(null + " should not have a value",
+                null,
+                sharedPreferences.getString(null, null));
+
+        // Null StringSet Key and value Test Assertion
+
+        Assert.assertEquals(null + " should not have a value",
+                null,
+                sharedPreferences.getStringSet(null, null));
+
+        // Test overwriting keys
+        String twiceKey = "KeyTwice";
+        String twiceVal1 = "FirstVal";
+        String twiceVal2 = "SecondVal";
+        editor.putString(twiceKey, twiceVal1);
+        editor.commit();
+
+        Assert.assertEquals(twiceVal1 + " should be the value",
+                twiceVal1,
+                sharedPreferences.getString(twiceKey, null));
+
+        editor.putString(twiceKey, twiceVal2);
+        editor.commit();
+
+        Assert.assertEquals(twiceVal2 + " should be the value",
+                twiceVal2,
+                sharedPreferences.getString(twiceKey, null));
+    }
+
+    @Test
+    public void testWriteSharedPrefsTink() throws Exception {
+        String tinkTestPrefs = "TinkTestPrefs";
+        String testKey = "TestKey";
+        String testValue = "TestValue";
+
+        SharedPreferences encryptedSharedPreferences = EncryptedSharedPreferences
+                .create(tinkTestPrefs,
+                        mKeyAlias, mContext,
+                        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
+                        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM);
+
+        SharedPreferences.Editor encryptedEditor = encryptedSharedPreferences.edit();
+        encryptedEditor.putString(testKey, testValue);
+        encryptedEditor.commit();
+
+        // Set up Tink
+        TinkConfig.register();
+
+        KeysetHandle daeadKeysetHandle = new AndroidKeysetManager.Builder()
+                .withKeyTemplate(DeterministicAeadKeyTemplates.AES256_SIV)
+                .withSharedPref(mContext,
+                        "__androidx_security_crypto_encrypted_prefs_key_keyset__", tinkTestPrefs)
+                .withMasterKeyUri(KEYSTORE_PATH_URI + "_androidx_security_master_key_")
+                .build().getKeysetHandle();
+
+        DeterministicAead deterministicAead = DeterministicAeadFactory.getPrimitive(
+                daeadKeysetHandle);
+        byte[] encryptedKey = deterministicAead.encryptDeterministically(testKey.getBytes(UTF_8),
+                tinkTestPrefs.getBytes());
+        String encodedKey = Base64.encode(encryptedKey);
+        SharedPreferences  sharedPreferences = mContext.getSharedPreferences(tinkTestPrefs,
+                MODE_PRIVATE);
+
+        boolean keyExists = sharedPreferences.contains(encodedKey);
+        Assert.assertTrue("Key should exist if Tink is compatible.", keyExists);
+
+        KeysetHandle aeadKeysetHandle = new AndroidKeysetManager.Builder()
+                .withKeyTemplate(AeadKeyTemplates.AES256_GCM)
+                .withSharedPref(mContext,
+                        "__androidx_security_crypto_encrypted_prefs_value_keyset__", tinkTestPrefs)
+                .withMasterKeyUri(KEYSTORE_PATH_URI + "_androidx_security_master_key_")
+                .build().getKeysetHandle();
+
+        Aead aead = AeadFactory.getPrimitive(aeadKeysetHandle);
+
+        String encryptedValue = sharedPreferences.getString(encodedKey, null);
+        byte[] cipherText = Base64.decode(encryptedValue);
+        ByteBuffer values = ByteBuffer.wrap(aead.decrypt(cipherText, encodedKey.getBytes(UTF_8)));
+        values.getInt(); // throw type away, we know its a String
+        int length = values.getInt();
+        ByteBuffer stringSlice = values.slice();
+        stringSlice.limit(length);
+        String actualValue = UTF_8.decode(stringSlice).toString();
+        Assert.assertEquals("String should have been equal to original",
+                actualValue,
+                testValue);
+    }
+
+}
diff --git a/security/crypto/src/androidTest/java/androidx/security/crypto/SecureCipherTest.java b/security/crypto/src/androidTest/java/androidx/security/crypto/SecureCipherTest.java
deleted file mode 100644
index 2529319..0000000
--- a/security/crypto/src/androidTest/java/androidx/security/crypto/SecureCipherTest.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright 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.security.crypto;
-
-import androidx.annotation.NonNull;
-import androidx.security.SecureConfig;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-
-@SuppressWarnings("unchecked")
-@RunWith(JUnit4.class)
-public class SecureCipherTest {
-
-    @Test
-    public void testEncryptDecryptSymmetricData() {
-        final String keyAlias = "test_signing_key";
-        final String original = "It's a secret...";
-        try {
-            SecureConfig config = SecureConfig.getDefault();
-            final SecureCipher cipher = new SecureCipher(config);
-            SecureKeyGenerator keyGenerator = SecureKeyGenerator.getInstance(config);
-            keyGenerator.generateKey(keyAlias);
-            cipher.encrypt(keyAlias, original.getBytes("UTF-8"),
-                    new SecureCipher.SecureSymmetricEncryptionListener() {
-                        @Override
-                        public void encryptionComplete(@NonNull byte[] cipherText,
-                                @NonNull byte[] iv) {
-                            cipher.decrypt(keyAlias, cipherText, iv,
-                                    new SecureCipher.SecureDecryptionListener() {
-                                        @Override
-                                        public void decryptionComplete(
-                                                @NonNull byte[] clearText) {
-                                            try {
-                                                Assert.assertEquals(
-                                                        "Original should match"
-                                                                + "encrypted/decrypted data",
-                                                        original,
-                                                        new String(clearText,
-                                                                "UTF-8"));
-                                            } catch (UnsupportedEncodingException ex) {
-                                                ex.printStackTrace();
-                                            }
-                                        }
-                                    });
-
-                        }
-                    });
-
-        } catch (IOException ex) {
-            ex.printStackTrace();
-        }
-
-    }
-
-    @Test
-    public void testSignVerifyData() {
-        final String keyAlias = "test_signing_key";
-        final String original = "It's a secret...";
-        try {
-            SecureConfig config = SecureConfig.getDefault();
-            final SecureCipher cipher = new SecureCipher(config);
-            SecureKeyGenerator keyGenerator = SecureKeyGenerator.getInstance(config);
-            keyGenerator.generateAsymmetricKeyPair(keyAlias);
-            cipher.sign(keyAlias, original.getBytes("UTF-8"),
-                    new SecureCipher.SecureSignListener() {
-                        @Override
-                        public void signComplete(@NonNull byte[] signature) {
-                            try {
-                                Assert.assertTrue(
-                                        "Signature should verify",
-                                        cipher.verify(keyAlias,
-                                                original.getBytes("UTF-8"),
-                                        signature));
-                            } catch (UnsupportedEncodingException ex) {
-                                ex.printStackTrace();
-                            }
-                        }
-                    });
-        } catch (IOException ex) {
-            ex.printStackTrace();
-        }
-
-    }
-
-}
diff --git a/security/crypto/src/androidTest/java/androidx/security/crypto/SecureKeyStoreTest.java b/security/crypto/src/androidTest/java/androidx/security/crypto/SecureKeyStoreTest.java
deleted file mode 100644
index 293f9a2..0000000
--- a/security/crypto/src/androidTest/java/androidx/security/crypto/SecureKeyStoreTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 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.security.crypto;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class SecureKeyStoreTest {
-
-    @Test
-    public void testKeyDoesNotExist() throws Throwable {
-        SecureKeyStore secureKeyStore = SecureKeyStore.getDefault();
-        boolean keyExists = secureKeyStore.keyExists("Not_a_real_key");
-        Assert.assertFalse("Key has not been created and should not exist!", keyExists);
-    }
-
-    @Test
-    public void testSymmetricKeyExists() throws Throwable {
-        SecureKeyGenerator keyGenerator = SecureKeyGenerator.getDefault();
-        keyGenerator.generateKey("symmetric_key");
-
-        SecureKeyStore keyStore = SecureKeyStore.getDefault();
-        boolean keyExists = keyStore.keyExists("symmetric_key");
-
-        Assert.assertTrue("Symmetric Key should exist!", keyExists);
-    }
-
-    @Test
-    public void testDeleteSymmetricKey() throws Throwable {
-        SecureKeyStore keyStore = SecureKeyStore.getDefault();
-        keyStore.deleteKey("symmetric_key");
-
-        boolean keyExists = keyStore.keyExists("symmetric_key");
-
-        Assert.assertFalse("Symmetric Key should have been deleted!", keyExists);
-    }
-
-    @Test
-    public void testAsymmetricKeyExists() throws Throwable {
-        SecureKeyGenerator keyGenerator = SecureKeyGenerator.getDefault();
-        keyGenerator.generateAsymmetricKeyPair("asymmetric_key_pair");
-
-        SecureKeyStore keyStore = SecureKeyStore.getDefault();
-        boolean keyExists = keyStore.keyExists("asymmetric_key_pair");
-
-        Assert.assertTrue("Asymmetric Key should exist!", keyExists);
-    }
-
-    @Test
-    public void testDeleteAsymmetricKey() throws Throwable {
-        SecureKeyStore keyStore = SecureKeyStore.getDefault();
-        keyStore.deleteKey("asymmetric_key_pair");
-
-        boolean keyExists = keyStore.keyExists("asymmetric_key_pair");
-
-        Assert.assertFalse("Asymmetric Key should have been deleted!", keyExists);
-    }
-
-}
diff --git a/security/crypto/src/androidTest/java/androidx/security/net/SecureURLTest.java b/security/crypto/src/androidTest/java/androidx/security/net/SecureURLTest.java
deleted file mode 100644
index 793b8aa..0000000
--- a/security/crypto/src/androidTest/java/androidx/security/net/SecureURLTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 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.security.net;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.IOException;
-
-import javax.net.ssl.HttpsURLConnection;
-
-@SuppressWarnings("unchecked")
-@RunWith(JUnit4.class)
-public class SecureURLTest {
-
-    @Test
-    public void testValidHttpsUrlConnection() {
-        String url = "https://www.google.com";
-        try {
-            SecureURL secureURL = new SecureURL(url);
-            HttpsURLConnection connection = (HttpsURLConnection) secureURL.openConnection();
-
-
-            boolean valid = secureURL.isValid(connection);
-
-            Assert.assertTrue("Connection to " + url + " should be valid.",
-                    valid);
-        } catch (IOException ex) {
-            ex.printStackTrace();
-        }
-
-
-    }
-
-    @Test
-    public void testInValidHttpsUrlConnection() {
-        String url = "https://revoked.badssl.com";
-        try {
-            SecureURL secureURL = new SecureURL(url);
-            HttpsURLConnection connection = (HttpsURLConnection) secureURL.openConnection();
-
-            boolean valid = secureURL.isValid(connection);
-
-            Assert.assertFalse("Connection to " + url
-                            + " should be  invalid, revoked cert.",
-                    valid);
-
-        } catch (IOException ex) {
-            ex.printStackTrace();
-        }
-
-
-    }
-}
diff --git a/security/crypto/src/main/AndroidManifest.xml b/security/crypto/src/main/AndroidManifest.xml
index 7e7b70b..25bdf7a 100644
--- a/security/crypto/src/main/AndroidManifest.xml
+++ b/security/crypto/src/main/AndroidManifest.xml
@@ -15,5 +15,4 @@
 -->
 <manifest package="androidx.security"
     xmlns:android="http://schemas.android.com/apk/res/android">
-    <uses-permission android:name="android.permission.USE_BIOMETRIC" />
 </manifest>
\ No newline at end of file
diff --git a/security/crypto/src/main/java/androidx/security/SecureConfig.java b/security/crypto/src/main/java/androidx/security/SecureConfig.java
deleted file mode 100644
index 8dc6ba9..0000000
--- a/security/crypto/src/main/java/androidx/security/SecureConfig.java
+++ /dev/null
@@ -1,1016 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package androidx.security;
-
-import android.security.keystore.KeyProperties;
-
-import androidx.annotation.NonNull;
-import androidx.security.biometric.BiometricKeyAuthCallback;
-import androidx.security.config.TrustAnchorOptions;
-
-/**
- * Class that defines constants used by the library. Includes predefined configurations for:
- *
- * Default:
- * SecureConfig.getDefault() provides a good basic security configuration for encrypting data
- * both in transit and at rest.
- *
- * NIAP:
- * For government use, SecureConfig.getNiapConfig(biometric auth) which returns compliant security
- * settings for NIAP use cases.
- *
- *
- */
-public class SecureConfig {
-
-    public static final String ANDROID_KEYSTORE = "AndroidKeyStore";
-    public static final String ANDROID_CA_STORE = "AndroidCAStore";
-    public static final int AES_IV_SIZE_BYTES = 16;
-    public static final String SSL_TLS = "TLS";
-
-    String mAndroidKeyStore;
-    String mAndroidCAStore;
-    String mKeystoreType;
-
-    // Asymmetric Encryption Constants
-    String mAsymmetricKeyPairAlgorithm;
-    int mAsymmetricKeySize;
-    String mAsymmetricCipherTransformation;
-    String mAsymmetricBlockModes;
-    String mAsymmetricPaddings;
-    int mAsymmetricKeyPurposes;
-    // Sets KeyGenBuilder#setUnlockedDeviceRequired to true, requires Android 9 Pie.
-    boolean mAsymmetricSensitiveDataProtection;
-    boolean mAsymmetricRequireUserAuth;
-    int mAsymmetricRequireUserValiditySeconds;
-    String mAsymmetricDigests;
-
-    // Symmetric Encryption Constants
-    String mSymmetricKeyAlgorithm;
-    String mSymmetricBlockModes;
-    String mSymmetricPaddings;
-    int mSymmetricKeySize;
-    int mSymmetricGcmTagLength;
-    int mSymmetricKeyPurposes;
-    String mSymmetricCipherTransformation;
-    // Sets KeyGenBuilder#setUnlockedDeviceRequired to true, requires Android 9 Pie.
-    boolean mSymmetricSensitiveDataProtection;
-    boolean mSymmetricRequireUserAuth;
-    int mSymmetricRequireUserValiditySeconds;
-    private String mSymmetricDigests;
-
-    // Certificate Constants
-    String mCertPath;
-    String mCertPathValidator;
-    boolean mUseStrongSSLCiphers;
-    String[] mStrongSSLCiphers;
-    String[] mClientCertAlgorithms;
-    TrustAnchorOptions mTrustAnchorOptions;
-
-    BiometricKeyAuthCallback mBiometricKeyAuthCallback;
-
-    String mSignatureAlgorithm;
-
-    SecureConfig() {
-    }
-
-    /**
-     * SecureConfig.Builder configures SecureConfig.
-     */
-    public static class Builder {
-
-        public Builder() {
-        }
-
-        // Keystore Constants
-        String mAndroidKeyStore;
-        String mAndroidCAStore;
-        String mKeystoreType;
-
-        // Asymmetric Encryption Constants
-        String mAsymmetricKeyPairAlgorithm;
-        int mAsymmetricKeySize;
-        String mAsymmetricCipherTransformation;
-        int mAsymmetricKeyPurposes;
-        String mAsymmetricBlockModes;
-        String mAsymmetricPaddings;
-        boolean mAsymmetricSensitiveDataProtection;
-        boolean mAsymmetricRequireUserAuth;
-        int mAsymmetricRequireUserValiditySeconds;
-
-        /**
-         * Sets the keystore type
-         *
-         * @param keystoreType the KeystoreType to set
-         * @return
-         */
-        @NonNull
-        public Builder forKeyStoreType(@NonNull String keystoreType) {
-            this.mKeystoreType = keystoreType;
-            return this;
-        }
-
-        /**
-         * Sets the key pair algorithm.
-         *
-         * @param keyPairAlgorithm the key pair algorithm
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setAsymmetricKeyPairAlgorithm(@NonNull String keyPairAlgorithm) {
-            this.mAsymmetricKeyPairAlgorithm = keyPairAlgorithm;
-            return this;
-        }
-
-        /**
-         * @param keySize
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setAsymmetricKeySize(int keySize) {
-            this.mAsymmetricKeySize = keySize;
-            return this;
-        }
-
-        /**
-         * @param cipherTransformation
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setAsymmetricCipherTransformation(@NonNull String cipherTransformation) {
-            this.mAsymmetricCipherTransformation = cipherTransformation;
-            return this;
-        }
-
-        /**
-         * @param purposes
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setAsymmetricKeyPurposes(int purposes) {
-            this.mAsymmetricKeyPurposes = purposes;
-            return this;
-        }
-
-        /**
-         * @param blockModes
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setAsymmetricBlockModes(@NonNull String blockModes) {
-            this.mAsymmetricBlockModes = blockModes;
-            return this;
-        }
-
-        /**
-         * @param paddings
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setAsymmetricPaddings(@NonNull String paddings) {
-            this.mAsymmetricPaddings = paddings;
-            return this;
-        }
-
-        /**
-         * @param dataProtection
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setAsymmetricSensitiveDataProtection(boolean dataProtection) {
-            this.mAsymmetricSensitiveDataProtection = dataProtection;
-            return this;
-        }
-
-        /**
-         * @param userAuth
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setAsymmetricRequireUserAuth(boolean userAuth) {
-            this.mAsymmetricRequireUserAuth = userAuth;
-            return this;
-        }
-
-        /**
-         * @param authValiditySeconds
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setAsymmetricRequireUserValiditySeconds(int authValiditySeconds) {
-            this.mAsymmetricRequireUserValiditySeconds = authValiditySeconds;
-            return this;
-        }
-
-        // Symmetric Encryption Constants
-        String mSymmetricKeyAlgorithm;
-        String mSymmetricBlockModes;
-        String mSymmetricPaddings;
-        int mSymmetricKeySize;
-        int mSymmetricGcmTagLength;
-        int mSymmetricKeyPurposes;
-        String mSymmetricCipherTransformation;
-        boolean mSymmetricSensitiveDataProtection;
-        boolean mSymmetricRequireUserAuth;
-        int mSymmetricRequireUserValiditySeconds;
-
-        /**
-         * @param keyAlgorithm
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setSymmetricKeyAlgorithm(@NonNull String keyAlgorithm) {
-            this.mSymmetricKeyAlgorithm = keyAlgorithm;
-            return this;
-        }
-
-        /**
-         * @param keySize
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setSymmetricKeySize(int keySize) {
-            this.mSymmetricKeySize = keySize;
-            return this;
-        }
-
-        /**
-         * @param cipherTransformation
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setSymmetricCipherTransformation(@NonNull String cipherTransformation) {
-            this.mSymmetricCipherTransformation = cipherTransformation;
-            return this;
-        }
-
-        /**
-         * @param purposes
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setSymmetricKeyPurposes(int purposes) {
-            this.mSymmetricKeyPurposes = purposes;
-            return this;
-        }
-
-        /**
-         * @param gcmTagLength
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setSymmetricGcmTagLength(int gcmTagLength) {
-            this.mSymmetricGcmTagLength = gcmTagLength;
-            return this;
-        }
-
-        /**
-         * @param blockModes
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setSymmetricBlockModes(@NonNull String blockModes) {
-            this.mSymmetricBlockModes = blockModes;
-            return this;
-        }
-
-        /**
-         * @param paddings
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setSymmetricPaddings(@NonNull String paddings) {
-            this.mSymmetricPaddings = paddings;
-            return this;
-        }
-
-        /**
-         * @param dataProtection
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setSymmetricSensitiveDataProtection(boolean dataProtection) {
-            this.mSymmetricSensitiveDataProtection = dataProtection;
-            return this;
-        }
-
-        /**
-         * @param userAuth
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setSymmetricRequireUserAuth(boolean userAuth) {
-            this.mSymmetricRequireUserAuth = userAuth;
-            return this;
-        }
-
-        /**
-         * @param authValiditySeconds
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setSymmetricRequireUserValiditySeconds(int authValiditySeconds) {
-            this.mSymmetricRequireUserValiditySeconds = authValiditySeconds;
-            return this;
-        }
-
-        // Certificate Constants
-        String mCertPath;
-        String mCertPathValidator;
-        boolean mUseStrongSSLCiphers;
-        String[] mStrongSSLCiphers;
-        String[] mClientCertAlgorithms;
-        TrustAnchorOptions mAnchorOptions;
-        BiometricKeyAuthCallback mBiometricKeyAuthCallback;
-        String mSignatureAlgorithm;
-
-        /**
-         * @param certPath
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setCertPath(@NonNull String certPath) {
-            this.mCertPath = certPath;
-            return this;
-        }
-
-        /**
-         * @param certPathValidator
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setCertPathValidator(@NonNull String certPathValidator) {
-            this.mCertPathValidator = certPathValidator;
-            return this;
-        }
-
-        /**
-         * @param strongSSLCiphers
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setUseStrongSSLCiphers(boolean strongSSLCiphers) {
-            this.mUseStrongSSLCiphers = strongSSLCiphers;
-            return this;
-        }
-
-        /**
-         * @param strongSSLCiphers
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setStrongSSLCiphers(@NonNull String[] strongSSLCiphers) {
-            this.mStrongSSLCiphers = strongSSLCiphers;
-            return this;
-        }
-
-        /**
-         * @param clientCertAlgorithms
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setClientCertAlgorithms(@NonNull String[] clientCertAlgorithms) {
-            this.mClientCertAlgorithms = clientCertAlgorithms;
-            return this;
-        }
-
-        /**
-         * @param trustAnchorOptions
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setTrustAnchorOptions(@NonNull TrustAnchorOptions trustAnchorOptions) {
-            this.mAnchorOptions = trustAnchorOptions;
-            return this;
-        }
-
-        /**
-         * @param biometricKeyAuthCallback
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setBiometricKeyAuthCallback(
-                @NonNull BiometricKeyAuthCallback biometricKeyAuthCallback) {
-            this.mBiometricKeyAuthCallback = biometricKeyAuthCallback;
-            return this;
-        }
-
-        /**
-         * @param signatureAlgorithm
-         * @return The configured builder
-         */
-        @NonNull
-        public Builder setSignatureAlgorithm(
-                @NonNull String signatureAlgorithm) {
-            this.mSignatureAlgorithm = signatureAlgorithm;
-            return this;
-        }
-
-
-
-        /**
-         * @return The configured builder
-         */
-        @NonNull
-        public SecureConfig build() {
-            SecureConfig secureConfig = new SecureConfig();
-            secureConfig.mAndroidKeyStore = this.mAndroidKeyStore;
-            secureConfig.mAndroidCAStore = this.mAndroidCAStore;
-            secureConfig.mKeystoreType = this.mKeystoreType;
-
-            secureConfig.mAsymmetricKeyPairAlgorithm = this.mAsymmetricKeyPairAlgorithm;
-            secureConfig.mAsymmetricKeySize = this.mAsymmetricKeySize;
-            secureConfig.mAsymmetricCipherTransformation = this.mAsymmetricCipherTransformation;
-            secureConfig.mAsymmetricKeyPurposes = this.mAsymmetricKeyPurposes;
-            secureConfig.mAsymmetricBlockModes = this.mAsymmetricBlockModes;
-            secureConfig.mAsymmetricPaddings = this.mAsymmetricPaddings;
-            secureConfig.mAsymmetricSensitiveDataProtection =
-                    this.mAsymmetricSensitiveDataProtection;
-            secureConfig.mAsymmetricRequireUserAuth = this.mAsymmetricRequireUserAuth;
-            secureConfig.mAsymmetricRequireUserValiditySeconds =
-                    this.mAsymmetricRequireUserValiditySeconds;
-
-            secureConfig.mSymmetricKeyAlgorithm = this.mSymmetricKeyAlgorithm;
-            secureConfig.mSymmetricBlockModes = this.mSymmetricBlockModes;
-            secureConfig.mSymmetricPaddings = this.mSymmetricPaddings;
-            secureConfig.mSymmetricKeySize = this.mSymmetricKeySize;
-            secureConfig.mSymmetricGcmTagLength = this.mSymmetricGcmTagLength;
-            secureConfig.mSymmetricKeyPurposes = this.mSymmetricKeyPurposes;
-            secureConfig.mSymmetricCipherTransformation = this.mSymmetricCipherTransformation;
-            secureConfig.mSymmetricSensitiveDataProtection =
-                    this.mSymmetricSensitiveDataProtection;
-            secureConfig.mSymmetricRequireUserAuth = this.mSymmetricRequireUserAuth;
-            secureConfig.mSymmetricRequireUserValiditySeconds =
-                    this.mSymmetricRequireUserValiditySeconds;
-
-            secureConfig.mCertPath = this.mCertPath;
-            secureConfig.mCertPathValidator = this.mCertPathValidator;
-            secureConfig.mUseStrongSSLCiphers = this.mUseStrongSSLCiphers;
-            secureConfig.mStrongSSLCiphers = this.mStrongSSLCiphers;
-            secureConfig.mClientCertAlgorithms = this.mClientCertAlgorithms;
-            secureConfig.mTrustAnchorOptions = this.mAnchorOptions;
-            secureConfig.mBiometricKeyAuthCallback = this.mBiometricKeyAuthCallback;
-            secureConfig.mSignatureAlgorithm = this.mSignatureAlgorithm;
-            return secureConfig;
-        }
-    }
-
-    /**
-     * @return A NIAP compliant configuration.
-     */
-    @NonNull
-    public static SecureConfig getNiapConfig() {
-        return getNiapConfig(null);
-    }
-
-    /**
-     * @return A default configuration with for consumer applications.
-     */
-    @NonNull
-    public static SecureConfig getDefault() {
-        SecureConfig.Builder builder = new SecureConfig.Builder();
-        builder.mAndroidKeyStore = SecureConfig.ANDROID_KEYSTORE;
-        builder.mAndroidCAStore = SecureConfig.ANDROID_CA_STORE;
-        builder.mKeystoreType = "PKCS12";
-
-        builder.mAsymmetricKeyPairAlgorithm = KeyProperties.KEY_ALGORITHM_RSA;
-        builder.mAsymmetricKeySize = 2048;
-        builder.mAsymmetricCipherTransformation = "RSA/ECB/PKCS1Padding";
-        builder.mAsymmetricBlockModes = KeyProperties.BLOCK_MODE_ECB;
-        builder.mAsymmetricPaddings = KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1;
-        builder.mAsymmetricKeyPurposes = KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_SIGN;
-        builder.mAsymmetricSensitiveDataProtection = false;
-        builder.mAsymmetricRequireUserAuth = false;
-        builder.mAsymmetricRequireUserValiditySeconds = -1;
-
-        builder.mSymmetricKeyAlgorithm = KeyProperties.KEY_ALGORITHM_AES;
-        builder.mSymmetricBlockModes = KeyProperties.BLOCK_MODE_GCM;
-        builder.mSymmetricPaddings = KeyProperties.ENCRYPTION_PADDING_NONE;
-        builder.mSymmetricKeySize = 256;
-        builder.mSymmetricGcmTagLength = 128;
-        builder.mSymmetricKeyPurposes =
-                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT;
-        builder.mSymmetricCipherTransformation = "AES/GCM/NoPadding";
-        builder.mSymmetricSensitiveDataProtection = false;
-        builder.mSymmetricRequireUserAuth = false;
-        builder.mSymmetricRequireUserValiditySeconds = -1;
-
-        builder.mCertPath = "X.509";
-        builder.mCertPathValidator = "PKIX";
-        builder.mUseStrongSSLCiphers = false;
-        builder.mStrongSSLCiphers = null;
-        builder.mClientCertAlgorithms = new String[]{"RSA"};
-        builder.mAnchorOptions = TrustAnchorOptions.USER_SYSTEM;
-        builder.mBiometricKeyAuthCallback = null;
-        builder.mSignatureAlgorithm = "SHA256withECDSA";
-
-        return builder.build();
-    }
-
-    /**
-     * Create a Niap compliant configuration
-     *
-     * -Insert Link to Spec
-     *
-     * @param biometricKeyAuthCallback
-     * @return The NIAP compliant configuration
-     */
-    @NonNull
-    public static SecureConfig getNiapConfig(
-            @NonNull BiometricKeyAuthCallback biometricKeyAuthCallback) {
-        SecureConfig.Builder builder = new SecureConfig.Builder();
-        builder.mAndroidKeyStore = SecureConfig.ANDROID_KEYSTORE;
-        builder.mAndroidCAStore = SecureConfig.ANDROID_CA_STORE;
-        builder.mKeystoreType = "PKCS12";
-
-        builder.mAsymmetricKeyPairAlgorithm = KeyProperties.KEY_ALGORITHM_RSA;
-        builder.mAsymmetricKeySize = 4096;
-        builder.mAsymmetricCipherTransformation = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
-        builder.mAsymmetricBlockModes = KeyProperties.BLOCK_MODE_ECB;
-        builder.mAsymmetricPaddings = KeyProperties.ENCRYPTION_PADDING_RSA_OAEP;
-        builder.mAsymmetricKeyPurposes = KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_SIGN;
-        builder.mAsymmetricSensitiveDataProtection = true;
-        builder.mAsymmetricRequireUserAuth = true;
-        builder.mAsymmetricRequireUserValiditySeconds = -1;
-
-        builder.mSymmetricKeyAlgorithm = KeyProperties.KEY_ALGORITHM_AES;
-        builder.mSymmetricBlockModes = KeyProperties.BLOCK_MODE_GCM;
-        builder.mSymmetricPaddings = KeyProperties.ENCRYPTION_PADDING_NONE;
-        builder.mSymmetricKeySize = 256;
-        builder.mSymmetricGcmTagLength = 128;
-        builder.mSymmetricKeyPurposes =
-                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT;
-        builder.mSymmetricCipherTransformation = "AES/GCM/NoPadding";
-        builder.mSymmetricSensitiveDataProtection = true;
-        builder.mSymmetricRequireUserAuth = true;
-        builder.mSymmetricRequireUserValiditySeconds = -1;
-
-        builder.mCertPath = "X.509";
-        builder.mCertPathValidator = "PKIX";
-        builder.mUseStrongSSLCiphers = false;
-        builder.mStrongSSLCiphers = new String[]{
-                "TLS_RSA_WITH_AES_128_CBC_SHA256",
-                "TLS_RSA_WITH_AES_256_CBC_SHA256",
-                "TLS_RSA_WITH_AES_128_GCM_SHA256",
-                "TLS_RSA_WITH_AES_256_GCM_SHA384",
-                "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
-                "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
-                "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
-                "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
-                "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
-                "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
-                "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
-        };
-        builder.mClientCertAlgorithms = new String[]{"RSA"};
-        builder.mAnchorOptions = TrustAnchorOptions.USER_SYSTEM;
-        builder.mBiometricKeyAuthCallback = biometricKeyAuthCallback;
-        builder.mSignatureAlgorithm = "SHA512withRSA/PSS";
-
-        return builder.build();
-    }
-
-    /**
-     * @return
-     */
-    @NonNull
-    public String getAndroidKeyStore() {
-        return mAndroidKeyStore;
-    }
-
-    public void setAndroidKeyStore(@NonNull String androidKeyStore) {
-        this.mAndroidKeyStore = androidKeyStore;
-    }
-
-    /**
-     * @return
-     */
-    @NonNull
-    public String getAndroidCAStore() {
-        return mAndroidCAStore;
-    }
-
-    public void setAndroidCAStore(@NonNull String androidCAStore) {
-        this.mAndroidCAStore = androidCAStore;
-    }
-
-    /**
-     * @return
-     */
-    @NonNull
-    public String getKeystoreType() {
-        return mKeystoreType;
-    }
-
-    /**
-     * @param keystoreType
-     */
-    @NonNull
-    public void setKeystoreType(@NonNull String keystoreType) {
-        this.mKeystoreType = keystoreType;
-    }
-
-    /**
-     * @return
-     */
-    @NonNull
-    public String getAsymmetricKeyPairAlgorithm() {
-        return mAsymmetricKeyPairAlgorithm;
-    }
-
-    /**
-     * @param asymmetricKeyPairAlgorithm
-     */
-    public void setAsymmetricKeyPairAlgorithm(@NonNull String asymmetricKeyPairAlgorithm) {
-        this.mAsymmetricKeyPairAlgorithm = asymmetricKeyPairAlgorithm;
-    }
-
-    /**
-     * @return
-     */
-    public int getAsymmetricKeySize() {
-        return mAsymmetricKeySize;
-    }
-
-    /**
-     * @param asymmetricKeySize
-     */
-    public void setAsymmetricKeySize(int asymmetricKeySize) {
-        this.mAsymmetricKeySize = asymmetricKeySize;
-    }
-
-    /**
-     * @return
-     */
-    @NonNull
-    public String getAsymmetricCipherTransformation() {
-        return mAsymmetricCipherTransformation;
-    }
-
-    /**
-     * @param asymmetricCipherTransformation
-     */
-    public void setAsymmetricCipherTransformation(@NonNull String asymmetricCipherTransformation) {
-        this.mAsymmetricCipherTransformation = asymmetricCipherTransformation;
-    }
-
-    /**
-     * @return
-     */
-    @NonNull
-    public String getAsymmetricBlockModes() {
-        return mAsymmetricBlockModes;
-    }
-
-    /**
-     * @param asymmetricBlockModes
-     */
-    public void setAsymmetricBlockModes(@NonNull String asymmetricBlockModes) {
-        this.mAsymmetricBlockModes = asymmetricBlockModes;
-    }
-
-    /**
-     * @return
-     */
-    @NonNull
-    public String getAsymmetricPaddings() {
-        return mAsymmetricPaddings;
-    }
-
-    /**
-     * @param asymmetricPaddings
-     */
-    public void setAsymmetricPaddings(@NonNull String asymmetricPaddings) {
-        this.mAsymmetricPaddings = asymmetricPaddings;
-    }
-
-    /**
-     * @return
-     */
-    public int getAsymmetricKeyPurposes() {
-        return mAsymmetricKeyPurposes;
-    }
-
-    /**
-     * @param asymmetricKeyPurposes
-     */
-    public void setAsymmetricKeyPurposes(int asymmetricKeyPurposes) {
-        this.mAsymmetricKeyPurposes = asymmetricKeyPurposes;
-    }
-
-    /**
-     * @return
-     */
-    public boolean getAsymmetricSensitiveDataProtectionEnabled() {
-        return mAsymmetricSensitiveDataProtection;
-    }
-
-    /**
-     * @param asymmetricSensitiveDataProtection
-     */
-    public void setAsymmetricSensitiveDataProtection(boolean asymmetricSensitiveDataProtection) {
-        this.mAsymmetricSensitiveDataProtection = asymmetricSensitiveDataProtection;
-    }
-
-    /**
-     * @return
-     */
-    public boolean getAsymmetricRequireUserAuthEnabled() {
-        return mAsymmetricRequireUserAuth && mBiometricKeyAuthCallback != null;
-    }
-
-    /**
-     * @param requireUserAuth
-     */
-    public void setAsymmetricRequireUserAuth(boolean requireUserAuth) {
-        this.mAsymmetricRequireUserAuth = requireUserAuth;
-    }
-
-    /**
-     * @return
-     */
-    public int getAsymmetricRequireUserValiditySeconds() {
-        return this.mAsymmetricRequireUserValiditySeconds;
-    }
-
-    /**
-     * @param userValiditySeconds
-     */
-    public void setAsymmetricRequireUserValiditySeconds(int userValiditySeconds) {
-        this.mAsymmetricRequireUserValiditySeconds = userValiditySeconds;
-    }
-
-    /**
-     * @return
-     */
-    @NonNull
-    public String getSymmetricKeyAlgorithm() {
-        return mSymmetricKeyAlgorithm;
-    }
-
-    /**
-     * @param symmetricKeyAlgorithm
-     */
-    public void setSymmetricKeyAlgorithm(@NonNull String symmetricKeyAlgorithm) {
-        this.mSymmetricKeyAlgorithm = symmetricKeyAlgorithm;
-    }
-
-    /**
-     * @return
-     */
-    @NonNull
-    public String getSymmetricBlockModes() {
-        return mSymmetricBlockModes;
-    }
-
-    /**
-     * @param symmetricBlockModes
-     */
-    public void setSymmetricBlockModes(@NonNull String symmetricBlockModes) {
-        this.mSymmetricBlockModes = symmetricBlockModes;
-    }
-
-    /**
-     * @return
-     */
-    @NonNull
-    public String getSymmetricPaddings() {
-        return mSymmetricPaddings;
-    }
-
-    /**
-     * @param symmetricPaddings
-     */
-    public void setSymmetricPaddings(@NonNull String symmetricPaddings) {
-        this.mSymmetricPaddings = symmetricPaddings;
-    }
-
-    /**
-     * @return
-     */
-    public int getSymmetricKeySize() {
-        return mSymmetricKeySize;
-    }
-
-    /**
-     * @param symmetricKeySize
-     */
-    public void setSymmetricKeySize(int symmetricKeySize) {
-        this.mSymmetricKeySize = symmetricKeySize;
-    }
-
-    /**
-     * @return
-     */
-    public int getSymmetricGcmTagLength() {
-        return mSymmetricGcmTagLength;
-    }
-
-    /**
-     * @param symmetricGcmTagLength
-     */
-    public void setSymmetricGcmTagLength(int symmetricGcmTagLength) {
-        this.mSymmetricGcmTagLength = symmetricGcmTagLength;
-    }
-
-    /**
-     * @return
-     */
-    public int getSymmetricKeyPurposes() {
-        return mSymmetricKeyPurposes;
-    }
-
-    /**
-     * @param symmetricKeyPurposes
-     */
-    public void setSymmetricKeyPurposes(int symmetricKeyPurposes) {
-        this.mSymmetricKeyPurposes = symmetricKeyPurposes;
-    }
-
-    /**
-     * @return
-     */
-    @NonNull
-    public String getSymmetricCipherTransformation() {
-        return mSymmetricCipherTransformation;
-    }
-
-    /**
-     * @param symmetricCipherTransformation
-     */
-    public void setSymmetricCipherTransformation(@NonNull String symmetricCipherTransformation) {
-        this.mSymmetricCipherTransformation = symmetricCipherTransformation;
-    }
-
-    /**
-     * @return
-     */
-    public boolean getSymmetricSensitiveDataProtectionEnabled() {
-        return mSymmetricSensitiveDataProtection;
-    }
-
-    /**
-     * @param symmetricSensitiveDataProtection
-     */
-    public void setSymmetricSensitiveDataProtection(boolean symmetricSensitiveDataProtection) {
-        this.mSymmetricSensitiveDataProtection = symmetricSensitiveDataProtection;
-    }
-
-    public boolean getSymmetricRequireUserAuthEnabled() {
-        return mSymmetricRequireUserAuth && mBiometricKeyAuthCallback != null;
-    }
-
-    /**
-     * @param requireUserAuth
-     */
-    public void setSymmetricRequireUserAuth(boolean requireUserAuth) {
-        this.mSymmetricRequireUserAuth = requireUserAuth;
-    }
-
-    /**
-     * @return
-     */
-    public int getSymmetricRequireUserValiditySeconds() {
-        return this.mSymmetricRequireUserValiditySeconds;
-    }
-
-    /**
-     * @param userValiditySeconds
-     */
-    public void setSymmetricRequireUserValiditySeconds(int userValiditySeconds) {
-        this.mSymmetricRequireUserValiditySeconds = userValiditySeconds;
-    }
-
-    /**
-     * @return
-     */
-    @NonNull
-    public String getCertPath() {
-        return mCertPath;
-    }
-
-    public void setCertPath(@NonNull String certPath) {
-        this.mCertPath = certPath;
-    }
-
-    /**
-     * @return
-     */
-    @NonNull
-    public String getCertPathValidator() {
-        return mCertPathValidator;
-    }
-
-    /**
-     * @param certPathValidator
-     */
-    public void setCertPathValidator(@NonNull String certPathValidator) {
-        this.mCertPathValidator = certPathValidator;
-    }
-
-    /**
-     * @return
-     */
-    public boolean getUseStrongSSLCiphersEnabled() {
-        return mUseStrongSSLCiphers;
-    }
-
-    /**
-     * @param useStrongSSLCiphers
-     */
-    public void setUseStrongSSLCiphers(boolean useStrongSSLCiphers) {
-        this.mUseStrongSSLCiphers = useStrongSSLCiphers;
-    }
-
-    public boolean getUseStrongSSLCiphers() {
-        return mUseStrongSSLCiphers;
-    }
-
-    /**
-     * @return
-     */
-    @NonNull
-    public String[] getStrongSSLCiphers() {
-        return mStrongSSLCiphers;
-    }
-
-    /**
-     * @param strongSSLCiphers
-     */
-    public void setStrongSSLCiphers(@NonNull String[] strongSSLCiphers) {
-        this.mStrongSSLCiphers = strongSSLCiphers;
-    }
-
-    /**
-     * @return
-     */
-    @NonNull
-    public String[] getClientCertAlgorithms() {
-        return mClientCertAlgorithms;
-    }
-
-    public void setClientCertAlgorithms(@NonNull String[] clientCertAlgorithms) {
-        this.mClientCertAlgorithms = clientCertAlgorithms;
-    }
-
-    /**
-     * @return
-     */
-    @NonNull
-    public TrustAnchorOptions getTrustAnchorOptions() {
-        return mTrustAnchorOptions;
-    }
-
-    /**
-     * @param trustAnchorOptions
-     */
-    public void setTrustAnchorOptions(@NonNull TrustAnchorOptions trustAnchorOptions) {
-        this.mTrustAnchorOptions = trustAnchorOptions;
-    }
-
-    /**
-     * @return
-     */
-    @NonNull
-    public BiometricKeyAuthCallback getBiometricKeyAuthCallback() {
-        return mBiometricKeyAuthCallback;
-    }
-
-    /**
-     * @param biometricKeyAuthCallback
-     */
-    public void setBiometricKeyAuthCallback(
-            @NonNull BiometricKeyAuthCallback biometricKeyAuthCallback) {
-        this.mBiometricKeyAuthCallback = biometricKeyAuthCallback;
-    }
-
-    /**
-     * @return
-     */
-    @NonNull
-    public String getSignatureAlgorithm() {
-        return mSignatureAlgorithm;
-    }
-
-    /**
-     * @param signatureAlgorithm
-     */
-    public void setSignatureAlgorithm(
-            @NonNull String signatureAlgorithm) {
-        this.mSignatureAlgorithm = signatureAlgorithm;
-    }
-}
diff --git a/security/crypto/src/main/java/androidx/security/biometric/BiometricKeyAuth.java b/security/crypto/src/main/java/androidx/security/biometric/BiometricKeyAuth.java
deleted file mode 100644
index f15fbb3..0000000
--- a/security/crypto/src/main/java/androidx/security/biometric/BiometricKeyAuth.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.biometric;
-
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.biometric.BiometricPrompt;
-import androidx.fragment.app.FragmentActivity;
-import androidx.security.crypto.SecureCipher;
-
-import java.security.Signature;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executors;
-
-import javax.crypto.Cipher;
-
-/**
- * Class that handles authenticating Ciphers using Biometric Prompt.
- */
-public class BiometricKeyAuth extends BiometricPrompt.AuthenticationCallback {
-
-    private static final String TAG = "BiometricKeyAuth";
-
-    private FragmentActivity mActivity;
-    private SecureCipher.SecureAuthListener mSecureAuthListener;
-    private CountDownLatch mCountDownLatch = null;
-    private BiometricKeyAuthCallback mBiometricKeyAuthCallback;
-
-    /**
-     * @param activity The activity to use a parent
-     * @param callback Callback to reply with when complete.
-     */
-    public BiometricKeyAuth(@NonNull FragmentActivity activity,
-            @NonNull BiometricKeyAuthCallback callback) {
-        this.mActivity = activity;
-        this.mBiometricKeyAuthCallback = callback;
-    }
-
-    @Override
-    public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
-        super.onAuthenticationSucceeded(result);
-        mBiometricKeyAuthCallback.onAuthenticationSucceeded();
-        Log.i(TAG, "Fingerprint success!");
-        mSecureAuthListener.authComplete(BiometricKeyAuthCallback.BiometricStatus.SUCCESS);
-        mCountDownLatch.countDown();
-    }
-
-    @Override
-    public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
-        super.onAuthenticationError(errorCode, errString);
-        mBiometricKeyAuthCallback.onMessage(String.valueOf(errString));
-        mBiometricKeyAuthCallback.onAuthenticationError(errorCode, errString);
-        mSecureAuthListener.authComplete(BiometricKeyAuthCallback.BiometricStatus.FAILED);
-        mCountDownLatch.countDown();
-    }
-
-    @Override
-    public void onAuthenticationFailed() {
-        super.onAuthenticationFailed();
-        mBiometricKeyAuthCallback.onAuthenticationFailed();
-        mSecureAuthListener.authComplete(BiometricKeyAuthCallback.BiometricStatus.FAILED);
-        mCountDownLatch.countDown();
-    }
-
-    /**
-     * Authenticates a key, via the Cipher it's used with
-     *
-     * @param cipher The cipher to authenticate
-     * @param promptInfo The prompt info for the auth fragment
-     * @param listener the listener to call back when complete
-     */
-    public void authenticateKey(@NonNull Cipher cipher,
-            @NonNull BiometricPrompt.PromptInfo promptInfo,
-            @NonNull SecureCipher.SecureAuthListener listener) {
-        authenticateKeyObject(cipher, promptInfo, listener);
-    }
-
-    /**
-     * Authenticates a key, via the Cipher it's used with
-     *
-     * @param signature The signature to authenticate
-     * @param promptInfo The prompt info for the auth fragment
-     * @param listener the listener to call back when complete
-     */
-    public void authenticateKey(@NonNull Signature signature,
-            @NonNull BiometricPrompt.PromptInfo promptInfo,
-            @NonNull SecureCipher.SecureAuthListener listener) {
-        authenticateKeyObject(signature, promptInfo, listener);
-    }
-
-    private void authenticateKeyObject(Object crypto,
-            BiometricPrompt.PromptInfo promptInfo,
-            SecureCipher.SecureAuthListener listener) {
-        mCountDownLatch = new CountDownLatch(1);
-        mSecureAuthListener = listener;
-
-        BiometricPrompt prompt = new BiometricPrompt(mActivity,
-                Executors.newSingleThreadExecutor(),
-                this);
-        BiometricPrompt.CryptoObject cryptoObject;
-        if (crypto instanceof Cipher) {
-            cryptoObject = new BiometricPrompt.CryptoObject((Cipher) crypto);
-        } else { /*if(crypto instanceof Signature) {*/
-            cryptoObject = new BiometricPrompt.CryptoObject((Signature) crypto);
-        }
-        prompt.authenticate(promptInfo, cryptoObject);
-        try {
-            mCountDownLatch.await();
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-
-    /**
-     * Authenticates a key, via the Cipher it's used with. Provides a default implementation of
-     * PromptInfo.
-     *
-     * @param cipher The cipher to authenticate
-     * @param listener the listener to call back when complete
-     */
-    public void authenticateKey(@NonNull Cipher cipher,
-            @NonNull SecureCipher.SecureAuthListener listener) {
-        authenticateKeyObject(cipher, listener);
-    }
-
-    /**
-     * Authenticates a key, via the Signature it's used with. Provides a default implementation of
-     * PromptInfo.
-     *
-     * @param signature The signature to authenticate
-     * @param listener the listener to call back when complete
-     */
-    public void authenticateKey(@NonNull Signature signature,
-            @NonNull SecureCipher.SecureAuthListener listener) {
-        authenticateKeyObject(signature, listener);
-    }
-
-    private void authenticateKeyObject(Object crypto,
-            @NonNull SecureCipher.SecureAuthListener listener) {
-        authenticateKeyObject(crypto, new BiometricPrompt.PromptInfo.Builder()
-                .setTitle("Please Auth for key usage.")
-                .setSubtitle("Key used for encrypting files")
-                .setDescription("User authentication required to access key.")
-                .setNegativeButtonText("Cancel")
-                .build(), listener);
-    }
-
-}
diff --git a/security/crypto/src/main/java/androidx/security/biometric/BiometricKeyAuthCallback.java b/security/crypto/src/main/java/androidx/security/biometric/BiometricKeyAuthCallback.java
deleted file mode 100644
index ff60d31..0000000
--- a/security/crypto/src/main/java/androidx/security/biometric/BiometricKeyAuthCallback.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.security.biometric;
-
-import androidx.annotation.NonNull;
-import androidx.security.crypto.SecureCipher;
-
-import java.security.Signature;
-
-import javax.crypto.Cipher;
-
-
-
-/**
- * Callback for Biometric Key auth.
- *
- */
-public abstract class BiometricKeyAuthCallback {
-
-    /**
-     * Statuses of Biometric auth
-     */
-    public enum BiometricStatus {
-        SUCCESS(0),
-        FAILED(1),
-        ERROR(2);
-
-        private final int mType;
-
-        BiometricStatus(int type) {
-            this.mType = type;
-        }
-
-        /**
-         * @return the mType
-         */
-        public int getType() {
-            return this.mType;
-        }
-
-        /**
-         * @return the status that matches the id
-         */
-        @NonNull
-        public static BiometricStatus fromId(int id) {
-            switch (id) {
-                case 0:
-                    return SUCCESS;
-                case 1:
-                    return FAILED;
-                case 2:
-                    return ERROR;
-            }
-            return ERROR;
-        }
-    }
-
-    /**
-     *
-     */
-    public abstract void onAuthenticationSucceeded();
-
-    /**
-     *
-     */
-    public abstract void onAuthenticationError(int errorCode, @NonNull CharSequence errString);
-
-    /**
-     *
-     */
-    public abstract void onAuthenticationFailed();
-
-    /**
-     * @param message the message to send
-     */
-    public abstract void onMessage(@NonNull String message);
-
-    /**
-     * @param cipher The cipher to authenticate
-     * @param listener The listener to call back
-     */
-    public abstract void authenticateKey(@NonNull Cipher cipher,
-            @NonNull SecureCipher.SecureAuthListener listener);
-
-    /**
-     * @param signature The signature to authenticate
-     * @param listener The listener to call back
-     */
-    public abstract void authenticateKey(@NonNull Signature signature,
-            @NonNull SecureCipher.SecureAuthListener listener);
-
-}
diff --git a/security/crypto/src/main/java/androidx/security/config/TldConstants.java b/security/crypto/src/main/java/androidx/security/config/TldConstants.java
deleted file mode 100644
index d118cb5c..0000000
--- a/security/crypto/src/main/java/androidx/security/config/TldConstants.java
+++ /dev/null
@@ -1,1575 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package androidx.security.config;
-
-import androidx.annotation.NonNull;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Class that contains all known TLDs.
- */
-public final class TldConstants {
-
-    private TldConstants() {
-    }
-
-    @NonNull
-    public static final List<String> VALID_TLDS = Arrays.asList(
-            "*.AAA",
-            "*.AARP",
-            "*.ABARTH",
-            "*.ABB",
-            "*.ABBOTT",
-            "*.ABBVIE",
-            "*.ABC",
-            "*.ABLE",
-            "*.ABOGADO",
-            "*.ABUDHABI",
-            "*.AC",
-            "*.ACADEMY",
-            "*.ACCENTURE",
-            "*.ACCOUNTANT",
-            "*.ACCOUNTANTS",
-            "*.ACO",
-            "*.ACTIVE",
-            "*.ACTOR",
-            "*.AD",
-            "*.ADAC",
-            "*.ADS",
-            "*.ADULT",
-            "*.AE",
-            "*.AEG",
-            "*.AERO",
-            "*.AETNA",
-            "*.AF",
-            "*.AFAMILYCOMPANY",
-            "*.AFL",
-            "*.AFRICA",
-            "*.AG",
-            "*.AGAKHAN",
-            "*.AGENCY",
-            "*.AI",
-            "*.AIG",
-            "*.AIGO",
-            "*.AIRBUS",
-            "*.AIRFORCE",
-            "*.AIRTEL",
-            "*.AKDN",
-            "*.AL",
-            "*.ALFAROMEO",
-            "*.ALIBABA",
-            "*.ALIPAY",
-            "*.ALLFINANZ",
-            "*.ALLSTATE",
-            "*.ALLY",
-            "*.ALSACE",
-            "*.ALSTOM",
-            "*.AM",
-            "*.AMERICANEXPRESS",
-            "*.AMERICANFAMILY",
-            "*.AMEX",
-            "*.AMFAM",
-            "*.AMICA",
-            "*.AMSTERDAM",
-            "*.ANALYTICS",
-            "*.ANDROID",
-            "*.ANQUAN",
-            "*.ANZ",
-            "*.AO",
-            "*.AOL",
-            "*.APARTMENTS",
-            "*.APP",
-            "*.APPLE",
-            "*.AQ",
-            "*.AQUARELLE",
-            "*.AR",
-            "*.ARAB",
-            "*.ARAMCO",
-            "*.ARCHI",
-            "*.ARMY",
-            "*.ARPA",
-            "*.ART",
-            "*.ARTE",
-            "*.AS",
-            "*.ASDA",
-            "*.ASIA",
-            "*.ASSOCIATES",
-            "*.AT",
-            "*.ATHLETA",
-            "*.ATTORNEY",
-            "*.AU",
-            "*.AUCTION",
-            "*.AUDI",
-            "*.AUDIBLE",
-            "*.AUDIO",
-            "*.AUSPOST",
-            "*.AUTHOR",
-            "*.AUTO",
-            "*.AUTOS",
-            "*.AVIANCA",
-            "*.AW",
-            "*.AWS",
-            "*.AX",
-            "*.AXA",
-            "*.AZ",
-            "*.AZURE",
-            "*.BA",
-            "*.BABY",
-            "*.BAIDU",
-            "*.BANAMEX",
-            "*.BANANAREPUBLIC",
-            "*.BAND",
-            "*.BANK",
-            "*.BAR",
-            "*.BARCELONA",
-            "*.BARCLAYCARD",
-            "*.BARCLAYS",
-            "*.BAREFOOT",
-            "*.BARGAINS",
-            "*.BASEBALL",
-            "*.BASKETBALL",
-            "*.BAUHAUS",
-            "*.BAYERN",
-            "*.BB",
-            "*.BBC",
-            "*.BBT",
-            "*.BBVA",
-            "*.BCG",
-            "*.BCN",
-            "*.BD",
-            "*.BE",
-            "*.BEATS",
-            "*.BEAUTY",
-            "*.BEER",
-            "*.BENTLEY",
-            "*.BERLIN",
-            "*.BEST",
-            "*.BESTBUY",
-            "*.BET",
-            "*.BF",
-            "*.BG",
-            "*.BH",
-            "*.BHARTI",
-            "*.BI",
-            "*.BIBLE",
-            "*.BID",
-            "*.BIKE",
-            "*.BING",
-            "*.BINGO",
-            "*.BIO",
-            "*.BIZ",
-            "*.BJ",
-            "*.BLACK",
-            "*.BLACKFRIDAY",
-            "*.BLANCO",
-            "*.BLOCKBUSTER",
-            "*.BLOG",
-            "*.BLOOMBERG",
-            "*.BLUE",
-            "*.BM",
-            "*.BMS",
-            "*.BMW",
-            "*.BN",
-            "*.BNL",
-            "*.BNPPARIBAS",
-            "*.BO",
-            "*.BOATS",
-            "*.BOEHRINGER",
-            "*.BOFA",
-            "*.BOM",
-            "*.BOND",
-            "*.BOO",
-            "*.BOOK",
-            "*.BOOKING",
-            "*.BOSCH",
-            "*.BOSTIK",
-            "*.BOSTON",
-            "*.BOT",
-            "*.BOUTIQUE",
-            "*.BOX",
-            "*.BR",
-            "*.BRADESCO",
-            "*.BRIDGESTONE",
-            "*.BROADWAY",
-            "*.BROKER",
-            "*.BROTHER",
-            "*.BRUSSELS",
-            "*.BS",
-            "*.BT",
-            "*.BUDAPEST",
-            "*.BUGATTI",
-            "*.BUILD",
-            "*.BUILDERS",
-            "*.BUSINESS",
-            "*.BUY",
-            "*.BUZZ",
-            "*.BV",
-            "*.BW",
-            "*.BY",
-            "*.BZ",
-            "*.BZH",
-            "*.CA",
-            "*.CAB",
-            "*.CAFE",
-            "*.CAL",
-            "*.CALL",
-            "*.CALVINKLEIN",
-            "*.CAM",
-            "*.CAMERA",
-            "*.CAMP",
-            "*.CANCERRESEARCH",
-            "*.CANON",
-            "*.CAPETOWN",
-            "*.CAPITAL",
-            "*.CAPITALONE",
-            "*.CAR",
-            "*.CARAVAN",
-            "*.CARDS",
-            "*.CARE",
-            "*.CAREER",
-            "*.CAREERS",
-            "*.CARS",
-            "*.CARTIER",
-            "*.CASA",
-            "*.CASE",
-            "*.CASEIH",
-            "*.CASH",
-            "*.CASINO",
-            "*.CAT",
-            "*.CATERING",
-            "*.CATHOLIC",
-            "*.CBA",
-            "*.CBN",
-            "*.CBRE",
-            "*.CBS",
-            "*.CC",
-            "*.CD",
-            "*.CEB",
-            "*.CENTER",
-            "*.CEO",
-            "*.CERN",
-            "*.CF",
-            "*.CFA",
-            "*.CFD",
-            "*.CG",
-            "*.CH",
-            "*.CHANEL",
-            "*.CHANNEL",
-            "*.CHARITY",
-            "*.CHASE",
-            "*.CHAT",
-            "*.CHEAP",
-            "*.CHINTAI",
-            "*.CHRISTMAS",
-            "*.CHROME",
-            "*.CHRYSLER",
-            "*.CHURCH",
-            "*.CI",
-            "*.CIPRIANI",
-            "*.CIRCLE",
-            "*.CISCO",
-            "*.CITADEL",
-            "*.CITI",
-            "*.CITIC",
-            "*.CITY",
-            "*.CITYEATS",
-            "*.CK",
-            "*.CL",
-            "*.CLAIMS",
-            "*.CLEANING",
-            "*.CLICK",
-            "*.CLINIC",
-            "*.CLINIQUE",
-            "*.CLOTHING",
-            "*.CLOUD",
-            "*.CLUB",
-            "*.CLUBMED",
-            "*.CM",
-            "*.CN",
-            "*.CO",
-            "*.COACH",
-            "*.CODES",
-            "*.COFFEE",
-            "*.COLLEGE",
-            "*.COLOGNE",
-            "*.COM",
-            "*.COMCAST",
-            "*.COMMBANK",
-            "*.COMMUNITY",
-            "*.COMPANY",
-            "*.COMPARE",
-            "*.COMPUTER",
-            "*.COMSEC",
-            "*.CONDOS",
-            "*.CONSTRUCTION",
-            "*.CONSULTING",
-            "*.CONTACT",
-            "*.CONTRACTORS",
-            "*.COOKING",
-            "*.COOKINGCHANNEL",
-            "*.COOL",
-            "*.COOP",
-            "*.CORSICA",
-            "*.COUNTRY",
-            "*.COUPON",
-            "*.COUPONS",
-            "*.COURSES",
-            "*.CR",
-            "*.CREDIT",
-            "*.CREDITCARD",
-            "*.CREDITUNION",
-            "*.CRICKET",
-            "*.CROWN",
-            "*.CRS",
-            "*.CRUISE",
-            "*.CRUISES",
-            "*.CSC",
-            "*.CU",
-            "*.CUISINELLA",
-            "*.CV",
-            "*.CW",
-            "*.CX",
-            "*.CY",
-            "*.CYMRU",
-            "*.CYOU",
-            "*.CZ",
-            "*.DABUR",
-            "*.DAD",
-            "*.DANCE",
-            "*.DATA",
-            "*.DATE",
-            "*.DATING",
-            "*.DATSUN",
-            "*.DAY",
-            "*.DCLK",
-            "*.DDS",
-            "*.DE",
-            "*.DEAL",
-            "*.DEALER",
-            "*.DEALS",
-            "*.DEGREE",
-            "*.DELIVERY",
-            "*.DELL",
-            "*.DELOITTE",
-            "*.DELTA",
-            "*.DEMOCRAT",
-            "*.DENTAL",
-            "*.DENTIST",
-            "*.DESI",
-            "*.DESIGN",
-            "*.DEV",
-            "*.DHL",
-            "*.DIAMONDS",
-            "*.DIET",
-            "*.DIGITAL",
-            "*.DIRECT",
-            "*.DIRECTORY",
-            "*.DISCOUNT",
-            "*.DISCOVER",
-            "*.DISH",
-            "*.DIY",
-            "*.DJ",
-            "*.DK",
-            "*.DM",
-            "*.DNP",
-            "*.DO",
-            "*.DOCS",
-            "*.DOCTOR",
-            "*.DODGE",
-            "*.DOG",
-            "*.DOHA",
-            "*.DOMAINS",
-            "*.DOT",
-            "*.DOWNLOAD",
-            "*.DRIVE",
-            "*.DTV",
-            "*.DUBAI",
-            "*.DUCK",
-            "*.DUNLOP",
-            "*.DUNS",
-            "*.DUPONT",
-            "*.DURBAN",
-            "*.DVAG",
-            "*.DVR",
-            "*.DZ",
-            "*.EARTH",
-            "*.EAT",
-            "*.EC",
-            "*.ECO",
-            "*.EDEKA",
-            "*.EDU",
-            "*.EDUCATION",
-            "*.EE",
-            "*.EG",
-            "*.EMAIL",
-            "*.EMERCK",
-            "*.ENERGY",
-            "*.ENGINEER",
-            "*.ENGINEERING",
-            "*.ENTERPRISES",
-            "*.EPOST",
-            "*.EPSON",
-            "*.EQUIPMENT",
-            "*.ER",
-            "*.ERICSSON",
-            "*.ERNI",
-            "*.ES",
-            "*.ESQ",
-            "*.ESTATE",
-            "*.ESURANCE",
-            "*.ET",
-            "*.ETISALAT",
-            "*.EU",
-            "*.EUROVISION",
-            "*.EUS",
-            "*.EVENTS",
-            "*.EVERBANK",
-            "*.EXCHANGE",
-            "*.EXPERT",
-            "*.EXPOSED",
-            "*.EXPRESS",
-            "*.EXTRASPACE",
-            "*.FAGE",
-            "*.FAIL",
-            "*.FAIRWINDS",
-            "*.FAITH",
-            "*.FAMILY",
-            "*.FAN",
-            "*.FANS",
-            "*.FARM",
-            "*.FARMERS",
-            "*.FASHION",
-            "*.FAST",
-            "*.FEDEX",
-            "*.FEEDBACK",
-            "*.FERRARI",
-            "*.FERRERO",
-            "*.FI",
-            "*.FIAT",
-            "*.FIDELITY",
-            "*.FIDO",
-            "*.FILM",
-            "*.FINAL",
-            "*.FINANCE",
-            "*.FINANCIAL",
-            "*.FIRE",
-            "*.FIRESTONE",
-            "*.FIRMDALE",
-            "*.FISH",
-            "*.FISHING",
-            "*.FIT",
-            "*.FITNESS",
-            "*.FJ",
-            "*.FK",
-            "*.FLICKR",
-            "*.FLIGHTS",
-            "*.FLIR",
-            "*.FLORIST",
-            "*.FLOWERS",
-            "*.FLY",
-            "*.FM",
-            "*.FO",
-            "*.FOO",
-            "*.FOOD",
-            "*.FOODNETWORK",
-            "*.FOOTBALL",
-            "*.FORD",
-            "*.FOREX",
-            "*.FORSALE",
-            "*.FORUM",
-            "*.FOUNDATION",
-            "*.FOX",
-            "*.FR",
-            "*.FREE",
-            "*.FRESENIUS",
-            "*.FRL",
-            "*.FROGANS",
-            "*.FRONTDOOR",
-            "*.FRONTIER",
-            "*.FTR",
-            "*.FUJITSU",
-            "*.FUJIXEROX",
-            "*.FUN",
-            "*.FUND",
-            "*.FURNITURE",
-            "*.FUTBOL",
-            "*.FYI",
-            "*.GA",
-            "*.GAL",
-            "*.GALLERY",
-            "*.GALLO",
-            "*.GALLUP",
-            "*.GAME",
-            "*.GAMES",
-            "*.GAP",
-            "*.GARDEN",
-            "*.GB",
-            "*.GBIZ",
-            "*.GD",
-            "*.GDN",
-            "*.GE",
-            "*.GEA",
-            "*.GENT",
-            "*.GENTING",
-            "*.GEORGE",
-            "*.GF",
-            "*.GG",
-            "*.GGEE",
-            "*.GH",
-            "*.GI",
-            "*.GIFT",
-            "*.GIFTS",
-            "*.GIVES",
-            "*.GIVING",
-            "*.GL",
-            "*.GLADE",
-            "*.GLASS",
-            "*.GLE",
-            "*.GLOBAL",
-            "*.GLOBO",
-            "*.GM",
-            "*.GMAIL",
-            "*.GMBH",
-            "*.GMO",
-            "*.GMX",
-            "*.GN",
-            "*.GODADDY",
-            "*.GOLD",
-            "*.GOLDPOINT",
-            "*.GOLF",
-            "*.GOO",
-            "*.GOODHANDS",
-            "*.GOODYEAR",
-            "*.GOOG",
-            "*.GOOGLE",
-            "*.GOP",
-            "*.GOT",
-            "*.GOV",
-            "*.GP",
-            "*.GQ",
-            "*.GR",
-            "*.GRAINGER",
-            "*.GRAPHICS",
-            "*.GRATIS",
-            "*.GREEN",
-            "*.GRIPE",
-            "*.GROCERY",
-            "*.GROUP",
-            "*.GS",
-            "*.GT",
-            "*.GU",
-            "*.GUARDIAN",
-            "*.GUCCI",
-            "*.GUGE",
-            "*.GUIDE",
-            "*.GUITARS",
-            "*.GURU",
-            "*.GW",
-            "*.GY",
-            "*.HAIR",
-            "*.HAMBURG",
-            "*.HANGOUT",
-            "*.HAUS",
-            "*.HBO",
-            "*.HDFC",
-            "*.HDFCBANK",
-            "*.HEALTH",
-            "*.HEALTHCARE",
-            "*.HELP",
-            "*.HELSINKI",
-            "*.HERE",
-            "*.HERMES",
-            "*.HGTV",
-            "*.HIPHOP",
-            "*.HISAMITSU",
-            "*.HITACHI",
-            "*.HIV",
-            "*.HK",
-            "*.HKT",
-            "*.HM",
-            "*.HN",
-            "*.HOCKEY",
-            "*.HOLDINGS",
-            "*.HOLIDAY",
-            "*.HOMEDEPOT",
-            "*.HOMEGOODS",
-            "*.HOMES",
-            "*.HOMESENSE",
-            "*.HONDA",
-            "*.HONEYWELL",
-            "*.HORSE",
-            "*.HOSPITAL",
-            "*.HOST",
-            "*.HOSTING",
-            "*.HOT",
-            "*.HOTELES",
-            "*.HOTELS",
-            "*.HOTMAIL",
-            "*.HOUSE",
-            "*.HOW",
-            "*.HR",
-            "*.HSBC",
-            "*.HT",
-            "*.HU",
-            "*.HUGHES",
-            "*.HYATT",
-            "*.HYUNDAI",
-            "*.IBM",
-            "*.ICBC",
-            "*.ICE",
-            "*.ICU",
-            "*.ID",
-            "*.IE",
-            "*.IEEE",
-            "*.IFM",
-            "*.IKANO",
-            "*.IL",
-            "*.IM",
-            "*.IMAMAT",
-            "*.IMDB",
-            "*.IMMO",
-            "*.IMMOBILIEN",
-            "*.IN",
-            "*.INC",
-            "*.INDUSTRIES",
-            "*.INFINITI",
-            "*.INFO",
-            "*.ING",
-            "*.INK",
-            "*.INSTITUTE",
-            "*.INSURANCE",
-            "*.INSURE",
-            "*.INT",
-            "*.INTEL",
-            "*.INTERNATIONAL",
-            "*.INTUIT",
-            "*.INVESTMENTS",
-            "*.IO",
-            "*.IPIRANGA",
-            "*.IQ",
-            "*.IR",
-            "*.IRISH",
-            "*.IS",
-            "*.ISELECT",
-            "*.ISMAILI",
-            "*.IST",
-            "*.ISTANBUL",
-            "*.IT",
-            "*.ITAU",
-            "*.ITV",
-            "*.IVECO",
-            "*.JAGUAR",
-            "*.JAVA",
-            "*.JCB",
-            "*.JCP",
-            "*.JE",
-            "*.JEEP",
-            "*.JETZT",
-            "*.JEWELRY",
-            "*.JIO",
-            "*.JLC",
-            "*.JLL",
-            "*.JM",
-            "*.JMP",
-            "*.JNJ",
-            "*.JO",
-            "*.JOBS",
-            "*.JOBURG",
-            "*.JOT",
-            "*.JOY",
-            "*.JP",
-            "*.JPMORGAN",
-            "*.JPRS",
-            "*.JUEGOS",
-            "*.JUNIPER",
-            "*.KAUFEN",
-            "*.KDDI",
-            "*.KE",
-            "*.KERRYHOTELS",
-            "*.KERRYLOGISTICS",
-            "*.KERRYPROPERTIES",
-            "*.KFH",
-            "*.KG",
-            "*.KH",
-            "*.KI",
-            "*.KIA",
-            "*.KIM",
-            "*.KINDER",
-            "*.KINDLE",
-            "*.KITCHEN",
-            "*.KIWI",
-            "*.KM",
-            "*.KN",
-            "*.KOELN",
-            "*.KOMATSU",
-            "*.KOSHER",
-            "*.KP",
-            "*.KPMG",
-            "*.KPN",
-            "*.KR",
-            "*.KRD",
-            "*.KRED",
-            "*.KUOKGROUP",
-            "*.KW",
-            "*.KY",
-            "*.KYOTO",
-            "*.KZ",
-            "*.LA",
-            "*.LACAIXA",
-            "*.LADBROKES",
-            "*.LAMBORGHINI",
-            "*.LAMER",
-            "*.LANCASTER",
-            "*.LANCIA",
-            "*.LANCOME",
-            "*.LAND",
-            "*.LANDROVER",
-            "*.LANXESS",
-            "*.LASALLE",
-            "*.LAT",
-            "*.LATINO",
-            "*.LATROBE",
-            "*.LAW",
-            "*.LAWYER",
-            "*.LB",
-            "*.LC",
-            "*.LDS",
-            "*.LEASE",
-            "*.LECLERC",
-            "*.LEFRAK",
-            "*.LEGAL",
-            "*.LEGO",
-            "*.LEXUS",
-            "*.LGBT",
-            "*.LI",
-            "*.LIAISON",
-            "*.LIDL",
-            "*.LIFE",
-            "*.LIFEINSURANCE",
-            "*.LIFESTYLE",
-            "*.LIGHTING",
-            "*.LIKE",
-            "*.LILLY",
-            "*.LIMITED",
-            "*.LIMO",
-            "*.LINCOLN",
-            "*.LINDE",
-            "*.LINK",
-            "*.LIPSY",
-            "*.LIVE",
-            "*.LIVING",
-            "*.LIXIL",
-            "*.LK",
-            "*.LLC",
-            "*.LOAN",
-            "*.LOANS",
-            "*.LOCKER",
-            "*.LOCUS",
-            "*.LOFT",
-            "*.LOL",
-            "*.LONDON",
-            "*.LOTTE",
-            "*.LOTTO",
-            "*.LOVE",
-            "*.LPL",
-            "*.LPLFINANCIAL",
-            "*.LR",
-            "*.LS",
-            "*.LT",
-            "*.LTD",
-            "*.LTDA",
-            "*.LU",
-            "*.LUNDBECK",
-            "*.LUPIN",
-            "*.LUXE",
-            "*.LUXURY",
-            "*.LV",
-            "*.LY",
-            "*.MA",
-            "*.MACYS",
-            "*.MADRID",
-            "*.MAIF",
-            "*.MAISON",
-            "*.MAKEUP",
-            "*.MAN",
-            "*.MANAGEMENT",
-            "*.MANGO",
-            "*.MAP",
-            "*.MARKET",
-            "*.MARKETING",
-            "*.MARKETS",
-            "*.MARRIOTT",
-            "*.MARSHALLS",
-            "*.MASERATI",
-            "*.MATTEL",
-            "*.MBA",
-            "*.MC",
-            "*.MCKINSEY",
-            "*.MD",
-            "*.ME",
-            "*.MED",
-            "*.MEDIA",
-            "*.MEET",
-            "*.MELBOURNE",
-            "*.MEME",
-            "*.MEMORIAL",
-            "*.MEN",
-            "*.MENU",
-            "*.MERCKMSD",
-            "*.METLIFE",
-            "*.MG",
-            "*.MH",
-            "*.MIAMI",
-            "*.MICROSOFT",
-            "*.MIL",
-            "*.MINI",
-            "*.MINT",
-            "*.MIT",
-            "*.MITSUBISHI",
-            "*.MK",
-            "*.ML",
-            "*.MLB",
-            "*.MLS",
-            "*.MM",
-            "*.MMA",
-            "*.MN",
-            "*.MO",
-            "*.MOBI",
-            "*.MOBILE",
-            "*.MOBILY",
-            "*.MODA",
-            "*.MOE",
-            "*.MOI",
-            "*.MOM",
-            "*.MONASH",
-            "*.MONEY",
-            "*.MONSTER",
-            "*.MOPAR",
-            "*.MORMON",
-            "*.MORTGAGE",
-            "*.MOSCOW",
-            "*.MOTO",
-            "*.MOTORCYCLES",
-            "*.MOV",
-            "*.MOVIE",
-            "*.MOVISTAR",
-            "*.MP",
-            "*.MQ",
-            "*.MR",
-            "*.MS",
-            "*.MSD",
-            "*.MT",
-            "*.MTN",
-            "*.MTR",
-            "*.MU",
-            "*.MUSEUM",
-            "*.MUTUAL",
-            "*.MV",
-            "*.MW",
-            "*.MX",
-            "*.MY",
-            "*.MZ",
-            "*.NA",
-            "*.NAB",
-            "*.NADEX",
-            "*.NAGOYA",
-            "*.NAME",
-            "*.NATIONWIDE",
-            "*.NATURA",
-            "*.NAVY",
-            "*.NBA",
-            "*.NC",
-            "*.NE",
-            "*.NEC",
-            "*.NET",
-            "*.NETBANK",
-            "*.NETFLIX",
-            "*.NETWORK",
-            "*.NEUSTAR",
-            "*.NEW",
-            "*.NEWHOLLAND",
-            "*.NEWS",
-            "*.NEXT",
-            "*.NEXTDIRECT",
-            "*.NEXUS",
-            "*.NF",
-            "*.NFL",
-            "*.NG",
-            "*.NGO",
-            "*.NHK",
-            "*.NI",
-            "*.NICO",
-            "*.NIKE",
-            "*.NIKON",
-            "*.NINJA",
-            "*.NISSAN",
-            "*.NISSAY",
-            "*.NL",
-            "*.NO",
-            "*.NOKIA",
-            "*.NORTHWESTERNMUTUAL",
-            "*.NORTON",
-            "*.NOW",
-            "*.NOWRUZ",
-            "*.NOWTV",
-            "*.NP",
-            "*.NR",
-            "*.NRA",
-            "*.NRW",
-            "*.NTT",
-            "*.NU",
-            "*.NYC",
-            "*.NZ",
-            "*.OBI",
-            "*.OBSERVER",
-            "*.OFF",
-            "*.OFFICE",
-            "*.OKINAWA",
-            "*.OLAYAN",
-            "*.OLAYANGROUP",
-            "*.OLDNAVY",
-            "*.OLLO",
-            "*.OM",
-            "*.OMEGA",
-            "*.ONE",
-            "*.ONG",
-            "*.ONL",
-            "*.ONLINE",
-            "*.ONYOURSIDE",
-            "*.OOO",
-            "*.OPEN",
-            "*.ORACLE",
-            "*.ORANGE",
-            "*.ORG",
-            "*.ORGANIC",
-            "*.ORIGINS",
-            "*.OSAKA",
-            "*.OTSUKA",
-            "*.OTT",
-            "*.OVH",
-            "*.PA",
-            "*.PAGE",
-            "*.PANASONIC",
-            "*.PANERAI",
-            "*.PARIS",
-            "*.PARS",
-            "*.PARTNERS",
-            "*.PARTS",
-            "*.PARTY",
-            "*.PASSAGENS",
-            "*.PAY",
-            "*.PCCW",
-            "*.PE",
-            "*.PET",
-            "*.PF",
-            "*.PFIZER",
-            "*.PG",
-            "*.PH",
-            "*.PHARMACY",
-            "*.PHD",
-            "*.PHILIPS",
-            "*.PHONE",
-            "*.PHOTO",
-            "*.PHOTOGRAPHY",
-            "*.PHOTOS",
-            "*.PHYSIO",
-            "*.PIAGET",
-            "*.PICS",
-            "*.PICTET",
-            "*.PICTURES",
-            "*.PID",
-            "*.PIN",
-            "*.PING",
-            "*.PINK",
-            "*.PIONEER",
-            "*.PIZZA",
-            "*.PK",
-            "*.PL",
-            "*.PLACE",
-            "*.PLAY",
-            "*.PLAYSTATION",
-            "*.PLUMBING",
-            "*.PLUS",
-            "*.PM",
-            "*.PN",
-            "*.PNC",
-            "*.POHL",
-            "*.POKER",
-            "*.POLITIE",
-            "*.PORN",
-            "*.POST",
-            "*.PR",
-            "*.PRAMERICA",
-            "*.PRAXI",
-            "*.PRESS",
-            "*.PRIME",
-            "*.PRO",
-            "*.PROD",
-            "*.PRODUCTIONS",
-            "*.PROF",
-            "*.PROGRESSIVE",
-            "*.PROMO",
-            "*.PROPERTIES",
-            "*.PROPERTY",
-            "*.PROTECTION",
-            "*.PRU",
-            "*.PRUDENTIAL",
-            "*.PS",
-            "*.PT",
-            "*.PUB",
-            "*.PW",
-            "*.PWC",
-            "*.PY",
-            "*.QA",
-            "*.QPON",
-            "*.QUEBEC",
-            "*.QUEST",
-            "*.QVC",
-            "*.RACING",
-            "*.RADIO",
-            "*.RAID",
-            "*.RE",
-            "*.READ",
-            "*.REALESTATE",
-            "*.REALTOR",
-            "*.REALTY",
-            "*.RECIPES",
-            "*.RED",
-            "*.REDSTONE",
-            "*.REDUMBRELLA",
-            "*.REHAB",
-            "*.REISE",
-            "*.REISEN",
-            "*.REIT",
-            "*.RELIANCE",
-            "*.REN",
-            "*.RENT",
-            "*.RENTALS",
-            "*.REPAIR",
-            "*.REPORT",
-            "*.REPUBLICAN",
-            "*.REST",
-            "*.RESTAURANT",
-            "*.REVIEW",
-            "*.REVIEWS",
-            "*.REXROTH",
-            "*.RICH",
-            "*.RICHARDLI",
-            "*.RICOH",
-            "*.RIGHTATHOME",
-            "*.RIL",
-            "*.RIO",
-            "*.RIP",
-            "*.RMIT",
-            "*.RO",
-            "*.ROCHER",
-            "*.ROCKS",
-            "*.RODEO",
-            "*.ROGERS",
-            "*.ROOM",
-            "*.RS",
-            "*.RSVP",
-            "*.RU",
-            "*.RUGBY",
-            "*.RUHR",
-            "*.RUN",
-            "*.RW",
-            "*.RWE",
-            "*.RYUKYU",
-            "*.SA",
-            "*.SAARLAND",
-            "*.SAFE",
-            "*.SAFETY",
-            "*.SAKURA",
-            "*.SALE",
-            "*.SALON",
-            "*.SAMSCLUB",
-            "*.SAMSUNG",
-            "*.SANDVIK",
-            "*.SANDVIKCOROMANT",
-            "*.SANOFI",
-            "*.SAP",
-            "*.SARL",
-            "*.SAS",
-            "*.SAVE",
-            "*.SAXO",
-            "*.SB",
-            "*.SBI",
-            "*.SBS",
-            "*.SC",
-            "*.SCA",
-            "*.SCB",
-            "*.SCHAEFFLER",
-            "*.SCHMIDT",
-            "*.SCHOLARSHIPS",
-            "*.SCHOOL",
-            "*.SCHULE",
-            "*.SCHWARZ",
-            "*.SCIENCE",
-            "*.SCJOHNSON",
-            "*.SCOR",
-            "*.SCOT",
-            "*.SD",
-            "*.SE",
-            "*.SEARCH",
-            "*.SEAT",
-            "*.SECURE",
-            "*.SECURITY",
-            "*.SEEK",
-            "*.SELECT",
-            "*.SENER",
-            "*.SERVICES",
-            "*.SES",
-            "*.SEVEN",
-            "*.SEW",
-            "*.SEX",
-            "*.SEXY",
-            "*.SFR",
-            "*.SG",
-            "*.SH",
-            "*.SHANGRILA",
-            "*.SHARP",
-            "*.SHAW",
-            "*.SHELL",
-            "*.SHIA",
-            "*.SHIKSHA",
-            "*.SHOES",
-            "*.SHOP",
-            "*.SHOPPING",
-            "*.SHOUJI",
-            "*.SHOW",
-            "*.SHOWTIME",
-            "*.SHRIRAM",
-            "*.SI",
-            "*.SILK",
-            "*.SINA",
-            "*.SINGLES",
-            "*.SITE",
-            "*.SJ",
-            "*.SK",
-            "*.SKI",
-            "*.SKIN",
-            "*.SKY",
-            "*.SKYPE",
-            "*.SL",
-            "*.SLING",
-            "*.SM",
-            "*.SMART",
-            "*.SMILE",
-            "*.SN",
-            "*.SNCF",
-            "*.SO",
-            "*.SOCCER",
-            "*.SOCIAL",
-            "*.SOFTBANK",
-            "*.SOFTWARE",
-            "*.SOHU",
-            "*.SOLAR",
-            "*.SOLUTIONS",
-            "*.SONG",
-            "*.SONY",
-            "*.SOY",
-            "*.SPACE",
-            "*.SPIEGEL",
-            "*.SPORT",
-            "*.SPOT",
-            "*.SPREADBETTING",
-            "*.SR",
-            "*.SRL",
-            "*.SRT",
-            "*.ST",
-            "*.STADA",
-            "*.STAPLES",
-            "*.STAR",
-            "*.STARHUB",
-            "*.STATEBANK",
-            "*.STATEFARM",
-            "*.STATOIL",
-            "*.STC",
-            "*.STCGROUP",
-            "*.STOCKHOLM",
-            "*.STORAGE",
-            "*.STORE",
-            "*.STREAM",
-            "*.STUDIO",
-            "*.STUDY",
-            "*.STYLE",
-            "*.SU",
-            "*.SUCKS",
-            "*.SUPPLIES",
-            "*.SUPPLY",
-            "*.SUPPORT",
-            "*.SURF",
-            "*.SURGERY",
-            "*.SUZUKI",
-            "*.SV",
-            "*.SWATCH",
-            "*.SWIFTCOVER",
-            "*.SWISS",
-            "*.SX",
-            "*.SY",
-            "*.SYDNEY",
-            "*.SYMANTEC",
-            "*.SYSTEMS",
-            "*.SZ",
-            "*.TAB",
-            "*.TAIPEI",
-            "*.TALK",
-            "*.TAOBAO",
-            "*.TARGET",
-            "*.TATAMOTORS",
-            "*.TATAR",
-            "*.TATTOO",
-            "*.TAX",
-            "*.TAXI",
-            "*.TC",
-            "*.TCI",
-            "*.TD",
-            "*.TDK",
-            "*.TEAM",
-            "*.TECH",
-            "*.TECHNOLOGY",
-            "*.TEL",
-            "*.TELEFONICA",
-            "*.TEMASEK",
-            "*.TENNIS",
-            "*.TEVA",
-            "*.TF",
-            "*.TG",
-            "*.TH",
-            "*.THD",
-            "*.THEATER",
-            "*.THEATRE",
-            "*.TIAA",
-            "*.TICKETS",
-            "*.TIENDA",
-            "*.TIFFANY",
-            "*.TIPS",
-            "*.TIRES",
-            "*.TIROL",
-            "*.TJ",
-            "*.TJMAXX",
-            "*.TJX",
-            "*.TK",
-            "*.TKMAXX",
-            "*.TL",
-            "*.TM",
-            "*.TMALL",
-            "*.TN",
-            "*.TO",
-            "*.TODAY",
-            "*.TOKYO",
-            "*.TOOLS",
-            "*.TOP",
-            "*.TORAY",
-            "*.TOSHIBA",
-            "*.TOTAL",
-            "*.TOURS",
-            "*.TOWN",
-            "*.TOYOTA",
-            "*.TOYS",
-            "*.TR",
-            "*.TRADE",
-            "*.TRADING",
-            "*.TRAINING",
-            "*.TRAVEL",
-            "*.TRAVELCHANNEL",
-            "*.TRAVELERS",
-            "*.TRAVELERSINSURANCE",
-            "*.TRUST",
-            "*.TRV",
-            "*.TT",
-            "*.TUBE",
-            "*.TUI",
-            "*.TUNES",
-            "*.TUSHU",
-            "*.TV",
-            "*.TVS",
-            "*.TW",
-            "*.TZ",
-            "*.UA",
-            "*.UBANK",
-            "*.UBS",
-            "*.UCONNECT",
-            "*.UG",
-            "*.UK",
-            "*.UNICOM",
-            "*.UNIVERSITY",
-            "*.UNO",
-            "*.UOL",
-            "*.UPS",
-            "*.US",
-            "*.UY",
-            "*.UZ",
-            "*.VA",
-            "*.VACATIONS",
-            "*.VANA",
-            "*.VANGUARD",
-            "*.VC",
-            "*.VE",
-            "*.VEGAS",
-            "*.VENTURES",
-            "*.VERISIGN",
-            "*.VERSICHERUNG",
-            "*.VET",
-            "*.VG",
-            "*.VI",
-            "*.VIAJES",
-            "*.VIDEO",
-            "*.VIG",
-            "*.VIKING",
-            "*.VILLAS",
-            "*.VIN",
-            "*.VIP",
-            "*.VIRGIN",
-            "*.VISA",
-            "*.VISION",
-            "*.VISTAPRINT",
-            "*.VIVA",
-            "*.VIVO",
-            "*.VLAANDEREN",
-            "*.VN",
-            "*.VODKA",
-            "*.VOLKSWAGEN",
-            "*.VOLVO",
-            "*.VOTE",
-            "*.VOTING",
-            "*.VOTO",
-            "*.VOYAGE",
-            "*.VU",
-            "*.VUELOS",
-            "*.WALES",
-            "*.WALMART",
-            "*.WALTER",
-            "*.WANG",
-            "*.WANGGOU",
-            "*.WARMAN",
-            "*.WATCH",
-            "*.WATCHES",
-            "*.WEATHER",
-            "*.WEATHERCHANNEL",
-            "*.WEBCAM",
-            "*.WEBER",
-            "*.WEBSITE",
-            "*.WED",
-            "*.WEDDING",
-            "*.WEIBO",
-            "*.WEIR",
-            "*.WF",
-            "*.WHOSWHO",
-            "*.WIEN",
-            "*.WIKI",
-            "*.WILLIAMHILL",
-            "*.WIN",
-            "*.WINDOWS",
-            "*.WINE",
-            "*.WINNERS",
-            "*.WME",
-            "*.WOLTERSKLUWER",
-            "*.WOODSIDE",
-            "*.WORK",
-            "*.WORKS",
-            "*.WORLD",
-            "*.WOW",
-            "*.WS",
-            "*.WTC",
-            "*.WTF",
-            "*.XBOX",
-            "*.XEROX",
-            "*.XFINITY",
-            "*.XIHUAN",
-            "*.XIN",
-            "*.XN--11B4C3D",
-            "*.XN--1CK2E1B",
-            "*.XN--1QQW23A",
-            "*.XN--2SCRJ9C",
-            "*.XN--30RR7Y",
-            "*.XN--3BST00M",
-            "*.XN--3DS443G",
-            "*.XN--3E0B707E",
-            "*.XN--3HCRJ9C",
-            "*.XN--3OQ18VL8PN36A",
-            "*.XN--3PXU8K",
-            "*.XN--42C2D9A",
-            "*.XN--45BR5CYL",
-            "*.XN--45BRJ9C",
-            "*.XN--45Q11C",
-            "*.XN--4GBRIM",
-            "*.XN--54B7FTA0CC",
-            "*.XN--55QW42G",
-            "*.XN--55QX5D",
-            "*.XN--5SU34J936BGSG",
-            "*.XN--5TZM5G",
-            "*.XN--6FRZ82G",
-            "*.XN--6QQ986B3XL",
-            "*.XN--80ADXHKS",
-            "*.XN--80AO21A",
-            "*.XN--80AQECDR1A",
-            "*.XN--80ASEHDB",
-            "*.XN--80ASWG",
-            "*.XN--8Y0A063A",
-            "*.XN--90A3AC",
-            "*.XN--90AE",
-            "*.XN--90AIS",
-            "*.XN--9DBQ2A",
-            "*.XN--9ET52U",
-            "*.XN--9KRT00A",
-            "*.XN--B4W605FERD",
-            "*.XN--BCK1B9A5DRE4C",
-            "*.XN--C1AVG",
-            "*.XN--C2BR7G",
-            "*.XN--CCK2B3B",
-            "*.XN--CG4BKI",
-            "*.XN--CLCHC0EA0B2G2A9GCD",
-            "*.XN--CZR694B",
-            "*.XN--CZRS0T",
-            "*.XN--CZRU2D",
-            "*.XN--D1ACJ3B",
-            "*.XN--D1ALF",
-            "*.XN--E1A4C",
-            "*.XN--ECKVDTC9D",
-            "*.XN--EFVY88H",
-            "*.XN--ESTV75G",
-            "*.XN--FCT429K",
-            "*.XN--FHBEI",
-            "*.XN--FIQ228C5HS",
-            "*.XN--FIQ64B",
-            "*.XN--FIQS8S",
-            "*.XN--FIQZ9S",
-            "*.XN--FJQ720A",
-            "*.XN--FLW351E",
-            "*.XN--FPCRJ9C3D",
-            "*.XN--FZC2C9E2C",
-            "*.XN--FZYS8D69UVGM",
-            "*.XN--G2XX48C",
-            "*.XN--GCKR3F0F",
-            "*.XN--GECRJ9C",
-            "*.XN--GK3AT1E",
-            "*.XN--H2BREG3EVE",
-            "*.XN--H2BRJ9C",
-            "*.XN--H2BRJ9C8C",
-            "*.XN--HXT814E",
-            "*.XN--I1B6B1A6A2E",
-            "*.XN--IMR513N",
-            "*.XN--IO0A7I",
-            "*.XN--J1AEF",
-            "*.XN--J1AMH",
-            "*.XN--J6W193G",
-            "*.XN--JLQ61U9W7B",
-            "*.XN--JVR189M",
-            "*.XN--KCRX77D1X4A",
-            "*.XN--KPRW13D",
-            "*.XN--KPRY57D",
-            "*.XN--KPU716F",
-            "*.XN--KPUT3I",
-            "*.XN--L1ACC",
-            "*.XN--LGBBAT1AD8J",
-            "*.XN--MGB9AWBF",
-            "*.XN--MGBA3A3EJT",
-            "*.XN--MGBA3A4F16A",
-            "*.XN--MGBA7C0BBN0A",
-            "*.XN--MGBAAKC7DVF",
-            "*.XN--MGBAAM7A8H",
-            "*.XN--MGBAB2BD",
-            "*.XN--MGBAI9AZGQP6J",
-            "*.XN--MGBAYH7GPA",
-            "*.XN--MGBB9FBPOB",
-            "*.XN--MGBBH1A",
-            "*.XN--MGBBH1A71E",
-            "*.XN--MGBC0A9AZCG",
-            "*.XN--MGBCA7DZDO",
-            "*.XN--MGBERP4A5D4AR",
-            "*.XN--MGBGU82A",
-            "*.XN--MGBI4ECEXP",
-            "*.XN--MGBPL2FH",
-            "*.XN--MGBT3DHD",
-            "*.XN--MGBTX2B",
-            "*.XN--MGBX4CD0AB",
-            "*.XN--MIX891F",
-            "*.XN--MK1BU44C",
-            "*.XN--MXTQ1M",
-            "*.XN--NGBC5AZD",
-            "*.XN--NGBE9E0A",
-            "*.XN--NGBRX",
-            "*.XN--NODE",
-            "*.XN--NQV7F",
-            "*.XN--NQV7FS00EMA",
-            "*.XN--NYQY26A",
-            "*.XN--O3CW4H",
-            "*.XN--OGBPF8FL",
-            "*.XN--OTU796D",
-            "*.XN--P1ACF",
-            "*.XN--P1AI",
-            "*.XN--PBT977C",
-            "*.XN--PGBS0DH",
-            "*.XN--PSSY2U",
-            "*.XN--Q9JYB4C",
-            "*.XN--QCKA1PMC",
-            "*.XN--QXAM",
-            "*.XN--RHQV96G",
-            "*.XN--ROVU88B",
-            "*.XN--RVC1E0AM3E",
-            "*.XN--S9BRJ9C",
-            "*.XN--SES554G",
-            "*.XN--T60B56A",
-            "*.XN--TCKWE",
-            "*.XN--TIQ49XQYJ",
-            "*.XN--UNUP4Y",
-            "*.XN--VERMGENSBERATER-CTB",
-            "*.XN--VERMGENSBERATUNG-PWB",
-            "*.XN--VHQUV",
-            "*.XN--VUQ861B",
-            "*.XN--W4R85EL8FHU5DNRA",
-            "*.XN--W4RS40L",
-            "*.XN--WGBH1C",
-            "*.XN--WGBL6A",
-            "*.XN--XHQ521B",
-            "*.XN--XKC2AL3HYE2A",
-            "*.XN--XKC2DL3A5EE0H",
-            "*.XN--Y9A3AQ",
-            "*.XN--YFRO4I67O",
-            "*.XN--YGBI2AMMX",
-            "*.XN--ZFR164B",
-            "*.XXX",
-            "*.XYZ",
-            "*.YACHTS",
-            "*.YAHOO",
-            "*.YAMAXUN",
-            "*.YANDEX",
-            "*.YE",
-            "*.YODOBASHI",
-            "*.YOGA",
-            "*.YOKOHAMA",
-            "*.YOU",
-            "*.YOUTUBE",
-            "*.YT",
-            "*.YUN",
-            "*.ZA",
-            "*.ZAPPOS",
-            "*.ZARA",
-            "*.ZERO",
-            "*.ZIP",
-            "*.ZIPPO",
-            "*.ZM",
-            "*.ZONE",
-            "*.ZUERICH",
-            "*.ZW"
-    );
-}
diff --git a/security/crypto/src/main/java/androidx/security/config/TrustAnchorOptions.java b/security/crypto/src/main/java/androidx/security/config/TrustAnchorOptions.java
deleted file mode 100644
index 2136b76..0000000
--- a/security/crypto/src/main/java/androidx/security/config/TrustAnchorOptions.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package androidx.security.config;
-
-import androidx.annotation.NonNull;
-
-/**
- * Enum that enumerates valid trust anchors
- */
-public enum TrustAnchorOptions {
-    USER_SYSTEM(0),
-    SYSTEM_ONLY(1),
-    USER_ONLY(2),
-    LIMITED_SYSTEM(3);
-
-    private final int mType;
-
-    TrustAnchorOptions(int type) {
-        this.mType = type;
-    }
-
-    /**
-     * @return the mType value
-     */
-    public int getType() {
-        return this.mType;
-    }
-
-    /**
-     * @param id the id of the trust anchor
-     * @return the mType
-     */
-    @NonNull
-    public static TrustAnchorOptions fromId(int id) {
-        switch (id) {
-            case 0:
-                return USER_SYSTEM;
-            case 1:
-                return SYSTEM_ONLY;
-            case 2:
-                return USER_ONLY;
-            case 3:
-                return LIMITED_SYSTEM;
-        }
-        return USER_SYSTEM;
-    }
-
-}
diff --git a/security/crypto/src/main/java/androidx/security/context/SecureContextCompat.java b/security/crypto/src/main/java/androidx/security/context/SecureContextCompat.java
deleted file mode 100644
index 20888ee..0000000
--- a/security/crypto/src/main/java/androidx/security/context/SecureContextCompat.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package androidx.security.context;
-
-import android.annotation.TargetApi;
-import android.app.KeyguardManager;
-import android.content.Context;
-import android.os.Build;
-
-import androidx.annotation.NonNull;
-import androidx.security.SecureConfig;
-import androidx.security.crypto.FileCipher;
-
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.concurrent.Executor;
-
-/**
- * Class that provides access to an encrypted file input and output streams, as well as encrypted
- * SharedPreferences.
- */
-public class SecureContextCompat {
-
-    private static final String TAG = "SecureContextCompat";
-
-    private Context mContext;
-    private SecureConfig mSecureConfig;
-
-    private static final String DEFAULT_FILE_ENCRYPTION_KEY = "default_encryption_key";
-
-    /**
-     * A listener for getting access to encrypted files. Required when the key requires a
-     * Biometric Prompt.
-     */
-    public interface EncryptedFileInputStreamListener {
-        /**
-         * @param inputStream
-         */
-        void onEncryptedFileInput(@NonNull FileInputStream inputStream);
-    }
-
-    public SecureContextCompat(@NonNull Context context) {
-        this(context, SecureConfig.getDefault());
-    }
-
-    public SecureContextCompat(@NonNull Context context, @NonNull SecureConfig secureConfig) {
-        mContext = context;
-        mSecureConfig = secureConfig;
-    }
-
-    /**
-     * Open an encrypted private file associated with this Context's application
-     * package for reading.
-     *
-     * @param name The name of the file to open; can not contain path separators.
-     * @throws IOException
-     */
-    public void openEncryptedFileInput(@NonNull String name,
-                                       @NonNull Executor executor,
-            @NonNull EncryptedFileInputStreamListener listener) throws IOException {
-        new FileCipher(name, mContext.openFileInput(name), mSecureConfig, executor, listener);
-    }
-
-    /**
-     * Open a private encrypted file associated with this Context's application package for
-     * writing. Creates the file if it doesn't already exist.
-     * <p>
-     * The written file will be encrypted with the default keyPairAlias.
-     *
-     * @param name The name of the file to open; can not contain path separators.
-     * @param mode Operating mode.
-     * @return The resulting {@link FileOutputStream}.
-     * @throws IOException
-     */
-    @NonNull
-    public FileOutputStream openEncryptedFileOutput(@NonNull String name, int mode)
-            throws IOException {
-        return openEncryptedFileOutput(name, mode, DEFAULT_FILE_ENCRYPTION_KEY);
-    }
-
-    /**
-     * Open a private encrypted file associated with this Context's application package for
-     * writing. Creates the file if it doesn't already exist.
-     * <p>
-     * The written file will be encrypted with the specified keyPairAlias.
-     *
-     * @param name         The name of the file to open; can not contain path separators.
-     * @param mode         Operating mode.
-     * @param keyPairAlias The alias of the KeyPair used for encryption, the KeyPair will be
-     *                     created if it does not exist.
-     * @return The resulting {@link FileOutputStream}.
-     * @throws IOException
-     */
-    @NonNull
-    public FileOutputStream openEncryptedFileOutput(@NonNull String name, int mode,
-            @NonNull String keyPairAlias)
-            throws IOException {
-        FileCipher fileCipher = new FileCipher(keyPairAlias,
-                mContext.openFileOutput(name, mode), mSecureConfig);
-        return fileCipher.getFileOutputStream();
-    }
-
-
-    /**
-     * Checks to see if the device is locked.
-     *
-     * @return true if the device is locked, false otherwise.
-     */
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1)
-    public boolean deviceLocked() {
-        KeyguardManager keyGuardManager =
-                (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
-        return keyGuardManager.isDeviceLocked();
-    }
-
-}
diff --git a/security/crypto/src/main/java/androidx/security/crypto/EncryptedFile.java b/security/crypto/src/main/java/androidx/security/crypto/EncryptedFile.java
new file mode 100644
index 0000000..0b4ab81
--- /dev/null
+++ b/security/crypto/src/main/java/androidx/security/crypto/EncryptedFile.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.security.crypto;
+
+import static androidx.security.crypto.MasterKeys.KEYSTORE_PATH_URI;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.StreamingAead;
+import com.google.crypto.tink.config.TinkConfig;
+import com.google.crypto.tink.integration.android.AndroidKeysetManager;
+import com.google.crypto.tink.proto.KeyTemplate;
+import com.google.crypto.tink.streamingaead.StreamingAeadFactory;
+import com.google.crypto.tink.streamingaead.StreamingAeadKeyTemplates;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.channels.FileChannel;
+import java.security.GeneralSecurityException;
+
+/**
+ * Class used to create and read encrypted files.
+ *
+ * @code {
+ *  String masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);
+ *
+ *  File file = new File(context.getFilesDir(), "secret_data");
+ *  EncryptedFile encryptedFile = EncryptedFile.Builder(
+ *      file,
+ *      context,
+ *      masterKeyAlias,
+ *      EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
+ *  ).build();
+ *
+ *  // write to the encrypted file
+ *  FileOutputStream encryptedOutputStream = encryptedFile.openFileOutput();
+ *
+ *  // read the encrypted file
+ *  FileInputStream encryptedInputStream = encryptedFile.openFileInput();
+ * }
+ *
+ */
+public final class EncryptedFile {
+
+    private static final String KEYSET_PREF_NAME =
+            "__androidx_security_crypto_encrypted_file_pref__";
+    private static final String KEYSET_ALIAS =
+            "__androidx_security_crypto_encrypted_file_keyset__";
+
+    final File mFile;
+    final Context mContext;
+    final String mMasterKeyAlias;
+    final StreamingAead mStreamingAead;
+
+
+    EncryptedFile(
+            @NonNull File file,
+            @NonNull String masterKeyAlias,
+            @NonNull StreamingAead streamingAead,
+            @NonNull Context context) {
+        mFile = file;
+        mContext = context;
+        mMasterKeyAlias = masterKeyAlias;
+        mStreamingAead = streamingAead;
+    }
+
+    /**
+     * The encryption scheme to encrypt files.
+     */
+    public enum FileEncryptionScheme {
+        /**
+         * The file content is encrypted using {@link StreamingAead} with AES-GCM, with the
+         * file name as associated data.
+         *
+         * For more information please see the Tink documentation:
+         *
+         * {@link StreamingAeadKeyTemplates}.AES256_GCM_HKDF_4KB
+         */
+        AES256_GCM_HKDF_4KB(StreamingAeadKeyTemplates.AES256_GCM_HKDF_4KB);
+
+        private KeyTemplate mStreamingAeadKeyTemplate;
+
+        FileEncryptionScheme(KeyTemplate keyTemplate) {
+            mStreamingAeadKeyTemplate = keyTemplate;
+        }
+
+        KeyTemplate getKeyTemplate() {
+            return mStreamingAeadKeyTemplate;
+        }
+    }
+
+    /**
+     * Builder class to configure EncryptedFile
+     */
+    public static final class Builder {
+
+        public Builder(@NonNull File file,
+                @NonNull Context context,
+                @NonNull String masterKeyAlias,
+                @NonNull FileEncryptionScheme fileEncryptionScheme) {
+            mFile = file;
+            mFileEncryptionScheme = fileEncryptionScheme;
+            mContext = context;
+            mMasterKeyAlias = masterKeyAlias;
+        }
+
+        // Required parameters
+        File mFile;
+        final FileEncryptionScheme mFileEncryptionScheme;
+        final Context mContext;
+        final String mMasterKeyAlias;
+
+        // Optional parameters
+        String mKeysetPrefName = KEYSET_PREF_NAME;
+        String mKeysetAlias = KEYSET_ALIAS;
+
+        /**
+         * @param keysetPrefName The SharedPreferences file to store the keyset.
+         * @return This Builder
+         */
+        @NonNull
+        public Builder setKeysetPrefName(@NonNull String keysetPrefName) {
+            mKeysetPrefName = keysetPrefName;
+            return this;
+        }
+
+        /**
+         * @param keysetAlias The alias in the SharedPreferences file to store the keyset.
+         * @return This Builder
+         */
+        @NonNull
+        public Builder setKeysetAlias(@NonNull String keysetAlias) {
+            mKeysetAlias = keysetAlias;
+            return this;
+        }
+
+        /**
+         * @return An EncryptedFile with the specified parameters.
+         */
+        @NonNull
+        public EncryptedFile build() throws GeneralSecurityException, IOException {
+            TinkConfig.register();
+
+            KeysetHandle streadmingAeadKeysetHandle = new AndroidKeysetManager.Builder()
+                    .withKeyTemplate(mFileEncryptionScheme.getKeyTemplate())
+                    .withSharedPref(mContext, mKeysetAlias, mKeysetPrefName)
+                    .withMasterKeyUri(KEYSTORE_PATH_URI + mMasterKeyAlias)
+                    .build().getKeysetHandle();
+
+            StreamingAead streamingAead = StreamingAeadFactory.getPrimitive(
+                    streadmingAeadKeysetHandle);
+
+            EncryptedFile file = new EncryptedFile(mFile, mKeysetAlias, streamingAead,
+                    mContext);
+            return file;
+        }
+    }
+
+    /**
+     * Opens a FileOutputStream for writing that automatically encrypts the data based on the
+     * provided settings.
+     *
+     * Please ensure that the same master key and keyset are  used to decrypt or it
+     * will cause failures.
+     *
+     * @return The FileOutputStream that encrypts all data.
+     * @throws GeneralSecurityException when a bad master key or keyset has been used
+     * @throws IOException when the file already exists or is not available for writing
+     */
+    @NonNull
+    public FileOutputStream openFileOutput()
+            throws GeneralSecurityException, IOException {
+        if (mFile.exists()) {
+            throw new IOException("output file already exists, please use a new file: "
+                    + mFile.getName());
+        }
+        FileOutputStream fileOutputStream = new FileOutputStream(mFile);
+        OutputStream encryptingStream = mStreamingAead.newEncryptingStream(fileOutputStream,
+                mFile.getName().getBytes(UTF_8));
+        return new EncryptedFileOutputStream(fileOutputStream.getFD(), encryptingStream);
+    }
+
+    /**
+     * Opens a FileInputStream that reads encrypted files based on the previous settings.
+     *
+     * Please ensure that the same master key and keyset are  used to decrypt or it
+     * will cause failures.
+     *
+     * @return The input stream to read previously encrypted data.
+     * @throws GeneralSecurityException when a bad master key or keyset has been used
+     * @throws IOException when the file was not found
+     */
+    @NonNull
+    public FileInputStream openFileInput()
+            throws GeneralSecurityException, IOException {
+        if (!mFile.exists()) {
+            throw new IOException("file doesn't exist: " + mFile.getName());
+        }
+        FileInputStream fileInputStream = new FileInputStream(mFile);
+        InputStream decryptingStream = mStreamingAead.newDecryptingStream(fileInputStream,
+                mFile.getName().getBytes(UTF_8));
+        return new EncryptedFileInputStream(fileInputStream.getFD(), decryptingStream);
+    }
+
+    /**
+     * Encrypted file output stream
+     *
+     */
+    private static final class EncryptedFileOutputStream extends FileOutputStream {
+
+        private final OutputStream mEncryptedOutputStream;
+
+        EncryptedFileOutputStream(FileDescriptor descriptor, OutputStream encryptedOutputStream) {
+            super(descriptor);
+            mEncryptedOutputStream = encryptedOutputStream;
+        }
+
+        @Override
+        public void write(@NonNull byte[] b) throws IOException {
+            mEncryptedOutputStream.write(b);
+        }
+
+        @Override
+        public void write(int b) throws IOException {
+            mEncryptedOutputStream.write(b);
+        }
+
+        @Override
+        public void write(@NonNull byte[] b, int off, int len) throws IOException {
+            mEncryptedOutputStream.write(b, off, len);
+        }
+
+        @Override
+        public void close() throws IOException {
+            mEncryptedOutputStream.close();
+        }
+
+        @NonNull
+        @Override
+        public FileChannel getChannel() {
+            throw new UnsupportedOperationException("For encrypted files, please open the "
+                    + "relevant FileInput/FileOutputStream.");
+        }
+
+        @Override
+        public void flush() throws IOException {
+            mEncryptedOutputStream.flush();
+        }
+
+    }
+
+    /**
+     * Encrypted file input stream
+     */
+    private static final class EncryptedFileInputStream extends FileInputStream {
+
+        private final InputStream mEncryptedInputStream;
+
+        EncryptedFileInputStream(FileDescriptor descriptor,
+                InputStream encryptedInputStream) {
+            super(descriptor);
+            mEncryptedInputStream = encryptedInputStream;
+        }
+
+        @Override
+        public int read() throws IOException {
+            return mEncryptedInputStream.read();
+        }
+
+        @Override
+        public int read(@NonNull byte[] b) throws IOException {
+            return mEncryptedInputStream.read(b);
+        }
+
+        @Override
+        public int read(@NonNull byte[] b, int off, int len) throws IOException {
+            return mEncryptedInputStream.read(b, off, len);
+        }
+
+        @Override
+        public long skip(long n) throws IOException {
+            return mEncryptedInputStream.skip(n);
+        }
+
+        @Override
+        public int available() throws IOException {
+            return mEncryptedInputStream.available();
+        }
+
+        @Override
+        public void close() throws IOException {
+            mEncryptedInputStream.close();
+        }
+
+        @Override
+        public FileChannel getChannel() {
+            throw new UnsupportedOperationException("For encrypted files, please open the "
+                    + "relevant FileInput/FileOutputStream.");
+        }
+
+        @Override
+        public synchronized void mark(int readlimit) {
+            mEncryptedInputStream.mark(readlimit);
+        }
+
+        @Override
+        public synchronized void reset() throws IOException {
+            mEncryptedInputStream.reset();
+        }
+
+        @Override
+        public boolean markSupported() {
+            return mEncryptedInputStream.markSupported();
+        }
+
+    }
+
+}
diff --git a/security/crypto/src/main/java/androidx/security/crypto/EncryptedSharedPreferences.java b/security/crypto/src/main/java/androidx/security/crypto/EncryptedSharedPreferences.java
new file mode 100644
index 0000000..a06eb1d
--- /dev/null
+++ b/security/crypto/src/main/java/androidx/security/crypto/EncryptedSharedPreferences.java
@@ -0,0 +1,557 @@
+/*
+ * Copyright 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.security.crypto;
+
+import static androidx.security.crypto.MasterKeys.KEYSTORE_PATH_URI;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.ArraySet;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.crypto.tink.Aead;
+import com.google.crypto.tink.DeterministicAead;
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.aead.AeadFactory;
+import com.google.crypto.tink.aead.AeadKeyTemplates;
+import com.google.crypto.tink.config.TinkConfig;
+import com.google.crypto.tink.daead.DeterministicAeadFactory;
+import com.google.crypto.tink.daead.DeterministicAeadKeyTemplates;
+import com.google.crypto.tink.integration.android.AndroidKeysetManager;
+import com.google.crypto.tink.proto.KeyTemplate;
+import com.google.crypto.tink.subtle.Base64;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * An implementation of {@link SharedPreferences} that encrypts keys and values.
+ *
+ * @code {
+ *  String masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);
+ *
+ *  SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
+ *      "secret_shared_prefs",
+ *      masterKeyAlias,
+ *      context,
+ *      EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
+ *      EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
+ *  );
+ *
+ *  // use the shared preferences and editor as you normally would
+ *  SharedPreferences.Editor editor = sharedPreferences.edit();
+ *
+ * }
+ */
+public final class EncryptedSharedPreferences implements SharedPreferences {
+
+    private static final String KEY_KEYSET_ALIAS =
+            "__androidx_security_crypto_encrypted_prefs_key_keyset__";
+    private static final String VALUE_KEYSET_ALIAS =
+            "__androidx_security_crypto_encrypted_prefs_value_keyset__";
+
+    private static final String NULL_VALUE = "__NULL__";
+
+    final SharedPreferences mSharedPreferences;
+    final List<OnSharedPreferenceChangeListener> mListeners;
+    final String mFileName;
+    final String mMasterKeyAlias;
+
+    final Aead mValueAead;
+    final DeterministicAead mKeyDeterministicAead;
+
+    EncryptedSharedPreferences(@NonNull String name,
+            @NonNull String masterKeyAlias,
+            @NonNull SharedPreferences sharedPreferences,
+            @NonNull Aead aead,
+            @NonNull DeterministicAead deterministicAead) {
+        mFileName = name;
+        mSharedPreferences = sharedPreferences;
+        mMasterKeyAlias = masterKeyAlias;
+        mValueAead = aead;
+        mKeyDeterministicAead = deterministicAead;
+        mListeners = new ArrayList<>();
+    }
+
+    /**
+     * Opens an instance of encrypted SharedPreferences
+     *
+     * @param fileName The name of the file to open; can not contain path separators.
+     * @return The SharedPreferences instance that encrypts all data.
+     * @throws GeneralSecurityException when a bad master key or keyset has been attempted
+     * @throws IOException              when fileName can not be used
+     */
+    @NonNull
+    public static SharedPreferences create(@NonNull String fileName,
+            @NonNull String masterKeyAlias,
+            @NonNull Context context,
+            @NonNull PrefKeyEncryptionScheme prefKeyEncryptionScheme,
+            @NonNull PrefValueEncryptionScheme prefValueEncryptionScheme)
+            throws GeneralSecurityException, IOException {
+        TinkConfig.register();
+
+        KeysetHandle daeadKeysetHandle = new AndroidKeysetManager.Builder()
+                .withKeyTemplate(prefKeyEncryptionScheme.getKeyTemplate())
+                .withSharedPref(context, KEY_KEYSET_ALIAS, fileName)
+                .withMasterKeyUri(KEYSTORE_PATH_URI + masterKeyAlias)
+                .build().getKeysetHandle();
+        KeysetHandle aeadKeysetHandle = new AndroidKeysetManager.Builder()
+                .withKeyTemplate(prefValueEncryptionScheme.getKeyTemplate())
+                .withSharedPref(context, VALUE_KEYSET_ALIAS, fileName)
+                .withMasterKeyUri(KEYSTORE_PATH_URI + masterKeyAlias)
+                .build().getKeysetHandle();
+
+        DeterministicAead daead = DeterministicAeadFactory.getPrimitive(daeadKeysetHandle);
+        Aead aead = AeadFactory.getPrimitive(aeadKeysetHandle);
+
+        return new EncryptedSharedPreferences(fileName, masterKeyAlias,
+                context.getSharedPreferences(fileName, Context.MODE_PRIVATE), aead, daead);
+    }
+
+    /**
+     * The encryption scheme to encrypt keys.
+     */
+    public enum PrefKeyEncryptionScheme {
+        /**
+         * Pref keys are encrypted deterministically with AES256-SIV-CMAC (RFC 5297).
+         *
+         * For more information please see the Tink documentation:
+         *
+         * {@link DeterministicAeadKeyTemplates}.AES256_SIV
+         */
+        AES256_SIV(DeterministicAeadKeyTemplates.AES256_SIV);
+
+        private KeyTemplate mDeterministicAeadKeyTemplate;
+
+        PrefKeyEncryptionScheme(KeyTemplate keyTemplate) {
+            mDeterministicAeadKeyTemplate = keyTemplate;
+        }
+
+        KeyTemplate getKeyTemplate() {
+            return mDeterministicAeadKeyTemplate;
+        }
+    }
+
+    /**
+     * The encryption scheme to encrypt values.
+     */
+    public enum PrefValueEncryptionScheme {
+        /**
+         * Pref values are encrypted with AES256-GCM. The associated data is the encrypted pref key.
+         *
+         * For more information please see the Tink documentation:
+         *
+         * {@link AeadKeyTemplates}.AES256_GCM
+         */
+        AES256_GCM(AeadKeyTemplates.AES256_GCM);
+
+        private KeyTemplate mAeadKeyTemplate;
+
+        PrefValueEncryptionScheme(KeyTemplate keyTemplates) {
+            mAeadKeyTemplate = keyTemplates;
+        }
+
+        KeyTemplate getKeyTemplate() {
+            return mAeadKeyTemplate;
+        }
+    }
+
+    private static final class Editor implements SharedPreferences.Editor {
+        private final EncryptedSharedPreferences mEncryptedSharedPreferences;
+        private final SharedPreferences.Editor mEditor;
+        private final List<String> mKeysChanged;
+
+        Editor(EncryptedSharedPreferences encryptedSharedPreferences,
+                SharedPreferences.Editor editor) {
+            mEncryptedSharedPreferences = encryptedSharedPreferences;
+            mEditor = editor;
+            mKeysChanged = new CopyOnWriteArrayList<>();
+        }
+
+        @Override
+        @NonNull
+        public SharedPreferences.Editor putString(@Nullable String key, @Nullable String value) {
+            if (value == null) {
+                value = NULL_VALUE;
+            }
+            byte[] stringBytes = value.getBytes(UTF_8);
+            int stringByteLength = stringBytes.length;
+            ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + Integer.BYTES
+                    + stringByteLength);
+            buffer.putInt(EncryptedType.STRING.getId());
+            buffer.putInt(stringByteLength);
+            buffer.put(stringBytes);
+            putEncryptedObject(key, buffer.array());
+            return this;
+        }
+
+        @Override
+        @NonNull
+        public SharedPreferences.Editor putStringSet(@Nullable String key,
+                @Nullable Set<String> values) {
+            if (values == null) {
+                values = new ArraySet<>();
+                values.add(NULL_VALUE);
+            }
+            List<byte[]> byteValues = new ArrayList<>(values.size());
+            int totalBytes = values.size() * Integer.BYTES;
+            for (String strValue : values) {
+                byte[] byteValue = strValue.getBytes(UTF_8);
+                byteValues.add(byteValue);
+                totalBytes += byteValue.length;
+            }
+            totalBytes += Integer.BYTES;
+            ByteBuffer buffer = ByteBuffer.allocate(totalBytes);
+            buffer.putInt(EncryptedType.STRING_SET.getId());
+            for (byte[] bytes : byteValues) {
+                buffer.putInt(bytes.length);
+                buffer.put(bytes);
+            }
+            putEncryptedObject(key, buffer.array());
+            return this;
+        }
+
+        @Override
+        @NonNull
+        public SharedPreferences.Editor putInt(@Nullable String key, int value) {
+            ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + Integer.BYTES);
+            buffer.putInt(EncryptedType.INT.getId());
+            buffer.putInt(value);
+            putEncryptedObject(key, buffer.array());
+            return this;
+        }
+
+        @Override
+        @NonNull
+        public SharedPreferences.Editor putLong(@Nullable String key, long value) {
+            ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + Long.BYTES);
+            buffer.putInt(EncryptedType.LONG.getId());
+            buffer.putLong(value);
+            putEncryptedObject(key, buffer.array());
+            return this;
+        }
+
+        @Override
+        @NonNull
+        public SharedPreferences.Editor putFloat(@Nullable String key, float value) {
+            ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + Float.BYTES);
+            buffer.putInt(EncryptedType.FLOAT.getId());
+            buffer.putFloat(value);
+            putEncryptedObject(key, buffer.array());
+            return this;
+        }
+
+        @Override
+        @NonNull
+        public SharedPreferences.Editor putBoolean(@Nullable String key, boolean value) {
+            ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + Byte.BYTES);
+            buffer.putInt(EncryptedType.BOOLEAN.getId());
+            buffer.put(value ? (byte) 1 : (byte) 0);
+            putEncryptedObject(key, buffer.array());
+            return this;
+        }
+
+        @Override
+        @NonNull
+        public SharedPreferences.Editor remove(@Nullable String key) {
+            mEditor.remove(mEncryptedSharedPreferences.encryptKey(key));
+            mKeysChanged.remove(key);
+            return this;
+        }
+
+        @Override
+        @NonNull
+        public SharedPreferences.Editor clear() {
+            mEditor.clear();
+            mKeysChanged.clear();
+            return this;
+        }
+
+        @Override
+        public boolean commit() {
+            try {
+                return mEditor.commit();
+            } finally {
+                notifyListeners();
+            }
+        }
+
+        @Override
+        public void apply() {
+            mEditor.apply();
+            notifyListeners();
+        }
+
+        private void putEncryptedObject(String key, byte[] value) {
+            mKeysChanged.add(key);
+            if (key == null) {
+                key = NULL_VALUE;
+            }
+            try {
+                Pair<String, String> encryptedPair = mEncryptedSharedPreferences
+                        .encryptKeyValuePair(key, value);
+                mEditor.putString(encryptedPair.first, encryptedPair.second);
+            } catch (GeneralSecurityException ex) {
+                throw new SecurityException("Could not encrypt data: " + ex.getMessage(), ex);
+            }
+        }
+
+        private void notifyListeners() {
+            for (OnSharedPreferenceChangeListener listener :
+                    mEncryptedSharedPreferences.mListeners) {
+                for (String key : mKeysChanged) {
+                    listener.onSharedPreferenceChanged(mEncryptedSharedPreferences, key);
+                }
+            }
+        }
+    }
+
+    // SharedPreferences methods
+
+    @Override
+    @NonNull
+    public Map<String, ?> getAll() {
+        Map<String, ? super Object> allEntries = new HashMap<>();
+        for (Map.Entry<String, ?> entry : mSharedPreferences.getAll().entrySet()) {
+            String decryptedKey = decryptKey(entry.getKey());
+
+            allEntries.put(decryptedKey,
+                    getDecryptedObject(decryptedKey));
+        }
+        return allEntries;
+    }
+
+    @Nullable
+    @Override
+    public String getString(@Nullable String key, @Nullable String defValue) {
+        Object value = getDecryptedObject(key);
+        return (value != null && value instanceof String ? (String) value : defValue);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Nullable
+    @Override
+    public Set<String> getStringSet(@Nullable String key, @Nullable Set<String> defValues) {
+        Set<String> returnValues;
+        Object value = getDecryptedObject(key);
+        if (value instanceof Set) {
+            returnValues = (Set<String>) value;
+        } else {
+            returnValues = new ArraySet<>();
+        }
+        return returnValues.size() > 0 ? returnValues : defValues;
+    }
+
+    @Override
+    public int getInt(@Nullable String key, int defValue) {
+        Object value = getDecryptedObject(key);
+        return (value != null && value instanceof Integer ? (Integer) value : defValue);
+    }
+
+    @Override
+    public long getLong(@Nullable String key, long defValue) {
+        Object value = getDecryptedObject(key);
+        return (value != null && value instanceof Long ? (Long) value : defValue);
+    }
+
+    @Override
+    public float getFloat(@Nullable String key, float defValue) {
+        Object value = getDecryptedObject(key);
+        return (value != null && value instanceof Float ? (Float) value : defValue);
+    }
+
+    @Override
+    public boolean getBoolean(@Nullable String key, boolean defValue) {
+        Object value = getDecryptedObject(key);
+        return (value != null && value instanceof Boolean ? (Boolean) value : defValue);
+    }
+
+    @Override
+    public boolean contains(@Nullable String key) {
+        String encryptedKey = encryptKey(key);
+        return mSharedPreferences.contains(encryptedKey);
+    }
+
+    @Override
+    @NonNull
+    public SharedPreferences.Editor edit() {
+        return new Editor(this, mSharedPreferences.edit());
+    }
+
+    @Override
+    public void registerOnSharedPreferenceChangeListener(
+            @NonNull OnSharedPreferenceChangeListener listener) {
+        mListeners.add(listener);
+    }
+
+    @Override
+    public void unregisterOnSharedPreferenceChangeListener(
+            @NonNull OnSharedPreferenceChangeListener listener) {
+        mListeners.remove(listener);
+    }
+
+    /**
+     * Internal enum to set the type of encrypted data.
+     */
+    private enum EncryptedType {
+        STRING(0),
+        STRING_SET(1),
+        INT(2),
+        LONG(3),
+        FLOAT(4),
+        BOOLEAN(5);
+
+        int mId;
+
+        EncryptedType(int id) {
+            mId = id;
+        }
+
+        public int getId() {
+            return mId;
+        }
+
+        public static EncryptedType fromId(int id) {
+            switch (id) {
+                case 0:
+                    return STRING;
+                case 1:
+                    return STRING_SET;
+                case 2:
+                    return INT;
+                case 3:
+                    return LONG;
+                case 4:
+                    return FLOAT;
+                case 5:
+                    return BOOLEAN;
+            }
+            return null;
+        }
+    }
+
+    private Object getDecryptedObject(String key) {
+        if (key == null) {
+            key = NULL_VALUE;
+        }
+        Object returnValue = null;
+        try {
+            String encryptedKey = encryptKey(key);
+            String encryptedValue = mSharedPreferences.getString(encryptedKey, null);
+            if (encryptedValue != null) {
+                byte[] cipherText = Base64.decode(encryptedValue, Base64.DEFAULT);
+                byte[] value = mValueAead.decrypt(cipherText, encryptedKey.getBytes(UTF_8));
+                ByteBuffer buffer = ByteBuffer.wrap(value);
+                buffer.position(0);
+                int typeId = buffer.getInt();
+                EncryptedType type = EncryptedType.fromId(typeId);
+                switch (type) {
+                    case STRING:
+                        int stringLength = buffer.getInt();
+                        ByteBuffer stringSlice = buffer.slice();
+                        buffer.limit(stringLength);
+                        String stringValue = UTF_8.decode(stringSlice).toString();
+                        if (stringValue.equals(NULL_VALUE)) {
+                            returnValue = null;
+                        } else {
+                            returnValue = stringValue;
+                        }
+                        break;
+                    case INT:
+                        returnValue = buffer.getInt();
+                        break;
+                    case LONG:
+                        returnValue = buffer.getLong();
+                        break;
+                    case FLOAT:
+                        returnValue = buffer.getFloat();
+                        break;
+                    case BOOLEAN:
+                        returnValue = buffer.get() != (byte) 0;
+                        break;
+                    case STRING_SET:
+                        ArraySet<String> stringSet = new ArraySet<>();
+                        while (buffer.hasRemaining()) {
+                            int subStringLength = buffer.getInt();
+                            ByteBuffer subStringSlice = buffer.slice();
+                            subStringSlice.limit(subStringLength);
+                            buffer.position(buffer.position() + subStringLength);
+                            stringSet.add(UTF_8.decode(subStringSlice).toString());
+                        }
+                        if (stringSet.size() == 1 && NULL_VALUE.equals(stringSet.valueAt(0))) {
+                            returnValue = null;
+                        } else {
+                            returnValue = stringSet;
+                        }
+                        break;
+                }
+            }
+        } catch (GeneralSecurityException ex) {
+            throw new SecurityException("Could not decrypt value. " + ex.getMessage(), ex);
+        }
+        return returnValue;
+    }
+
+    String encryptKey(String key) {
+        if (key == null) {
+            key = NULL_VALUE;
+        }
+        try {
+            byte[] encryptedKeyBytes = mKeyDeterministicAead.encryptDeterministically(
+                    key.getBytes(UTF_8),
+                    mFileName.getBytes());
+            return Base64.encode(encryptedKeyBytes);
+        } catch (GeneralSecurityException ex) {
+            throw new SecurityException("Could not encrypt key. " + ex.getMessage(), ex);
+        }
+    }
+
+    String decryptKey(String encryptedKey) {
+        try {
+            byte[] clearText = mKeyDeterministicAead.decryptDeterministically(
+                    Base64.decode(encryptedKey, Base64.DEFAULT),
+                    mFileName.getBytes());
+            String key = new String(clearText, UTF_8);
+            if (key.equals(NULL_VALUE)) {
+                key = null;
+            }
+            return key;
+        } catch (GeneralSecurityException ex) {
+            throw new SecurityException("Could not decrypt key. " + ex.getMessage(), ex);
+        }
+    }
+
+    Pair<String, String> encryptKeyValuePair(String key, byte[] value)
+            throws GeneralSecurityException {
+        String encryptedKey = encryptKey(key);
+        byte[] cipherText = mValueAead.encrypt(value, encryptedKey.getBytes(UTF_8));
+        return new Pair<>(encryptedKey, Base64.encode(cipherText));
+    }
+
+}
diff --git a/security/crypto/src/main/java/androidx/security/crypto/EphemeralSecretKey.java b/security/crypto/src/main/java/androidx/security/crypto/EphemeralSecretKey.java
deleted file mode 100644
index da2633a..0000000
--- a/security/crypto/src/main/java/androidx/security/crypto/EphemeralSecretKey.java
+++ /dev/null
@@ -1,270 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package androidx.security.crypto;
-
-import android.security.keystore.KeyProperties;
-
-import androidx.annotation.NonNull;
-import androidx.security.SecureConfig;
-
-import java.security.GeneralSecurityException;
-import java.security.MessageDigest;
-import java.security.spec.KeySpec;
-import java.util.Arrays;
-import java.util.Locale;
-
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.DESKeySpec;
-import javax.crypto.spec.GCMParameterSpec;
-
-/**
- * A destroyable SecretKey. Can be completely evicted from memory on deletion.
- */
-public class EphemeralSecretKey implements KeySpec, SecretKey {
-
-    private static final long serialVersionUID = 7177238317307289223L;
-    private static final String TAG = "EphemeralSecretKey";
-
-    private byte[] mKey;
-
-    private String mAlgorithm;
-
-    private boolean mKeyDestroyed;
-
-    private SecureConfig mSecureConfig;
-
-    public EphemeralSecretKey(@NonNull byte[] key) {
-        this(key, KeyProperties.KEY_ALGORITHM_AES, SecureConfig.getDefault());
-    }
-
-    public EphemeralSecretKey(@NonNull byte[] key, @NonNull String algorithm) {
-        this(key, algorithm, SecureConfig.getDefault());
-    }
-
-    public EphemeralSecretKey(@NonNull byte[] key, @NonNull SecureConfig secureConfig) {
-        this(key, KeyProperties.KEY_ALGORITHM_AES, secureConfig);
-    }
-
-    /**
-     * Constructs a secret mKey from the given byte array.
-     *
-     * <p>This constructor does not check if the given bytes indeed specify a
-     * secret mKey of the specified mAlgorithm. For example, if the mAlgorithm is
-     * DES, this constructor does not check if <code>mKey</code> is 8 bytes
-     * long, and also does not check for weak or semi-weak keys.
-     * In order for those checks to be performed, an mAlgorithm-specific
-     * <i>mKey specification</i> class (in this case:
-     * {@link DESKeySpec DESKeySpec})
-     * should be used.
-     *
-     * @param key       the mKey material of the secret mKey. The contents of
-     *                  the array are copied to protect against subsequent modification.
-     * @param algorithm the name of the secret-mKey mAlgorithm to be associated
-     *                  with the given mKey material.
-     * @throws IllegalArgumentException if <code>mAlgorithm</code>
-     *                                  is null or <code>mKey</code> is null or empty.
-     */
-    public EphemeralSecretKey(@NonNull byte[] key, @NonNull String algorithm,
-            @NonNull SecureConfig secureConfig) {
-        if (key == null || algorithm == null) {
-            throw new IllegalArgumentException("Missing argument");
-        }
-        if (key.length == 0) {
-            throw new IllegalArgumentException("Empty mKey");
-        }
-        this.mKey = key;
-        this.mAlgorithm = algorithm;
-        this.mKeyDestroyed = false;
-        this.mSecureConfig = secureConfig;
-    }
-
-    /**
-     * Constructs a secret mKey from the given byte array, using the first
-     * <code>len</code> bytes of <code>mKey</code>, starting at
-     * <code>offset</code> inclusive.
-     *
-     * <p> The bytes that constitute the secret mKey are
-     * those between <code>mKey[offset]</code> and
-     * <code>mKey[offset+len-1]</code> inclusive.
-     *
-     * <p>This constructor does not check if the given bytes indeed specify a
-     * secret mKey of the specified mAlgorithm. For example, if the mAlgorithm is
-     * DES, this constructor does not check if <code>mKey</code> is 8 bytes
-     * long, and also does not check for weak or semi-weak keys.
-     * In order for those checks to be performed, an mAlgorithm-specific mKey
-     * specification class (in this case:
-     * {@link DESKeySpec DESKeySpec})
-     * must be used.
-     *
-     * @param key       the mKey material of the secret mKey. The first
-     *                  <code>len</code> bytes of the array beginning at
-     *                  <code>offset</code> inclusive are copied to protect
-     *                  against subsequent modification.
-     * @param offset    the offset in <code>mKey</code> where the mKey material
-     *                  starts.
-     * @param len       the length of the mKey material.
-     * @param algorithm the name of the secret-mKey mAlgorithm to be associated
-     *                  with the given mKey material.
-     * @throws IllegalArgumentException       if <code>mAlgorithm</code>
-     *                                        is null or <code>mKey</code> is null,
-     *                                        empty, or too short,
-     *                                        i.e. {@code mKey.length-offset<len}.
-     * @throws ArrayIndexOutOfBoundsException is thrown if
-     *                                        <code>offset</code> or <code>len</code>
-     *                                        index bytes outside the
-     *                                        <code>mKey</code>.
-     */
-    public EphemeralSecretKey(@NonNull byte[] key, int offset, int len,
-            @NonNull String algorithm) {
-        if (key == null || algorithm == null) {
-            throw new IllegalArgumentException("Missing argument");
-        }
-        if (key.length == 0) {
-            throw new IllegalArgumentException("Empty mKey");
-        }
-        if (key.length - offset < len) {
-            throw new IllegalArgumentException("Invalid offset/length combination");
-        }
-        if (len < 0) {
-            throw new ArrayIndexOutOfBoundsException("len is negative");
-        }
-        this.mKey = new byte[len];
-        System.arraycopy(key, offset, this.mKey, 0, len);
-        this.mAlgorithm = algorithm;
-        this.mKeyDestroyed = false;
-    }
-
-    /**
-     * Returns the name of the mAlgorithm associated with this secret mKey.
-     *
-     * @return the secret mKey mAlgorithm.
-     */
-    @NonNull
-    public String getAlgorithm() {
-        return this.mAlgorithm;
-    }
-
-    /**
-     * Returns the name of the encoding format for this secret mKey.
-     *
-     * @return the string "RAW".
-     */
-    @NonNull
-    public String getFormat() {
-        return "RAW";
-    }
-
-    /**
-     * Returns the mKey material of this secret mKey.
-     *
-     * @return the mKey material. Returns a new array
-     * each time this method is called.
-     */
-    @NonNull
-    public byte[] getEncoded() {
-        return this.mKey;
-    }
-
-    /**
-     * Calculates a hash code value for the object.
-     * Objects that are equal will also have the same hashcode.
-     */
-    public int hashCode() {
-        int retval = 0;
-        for (int i = 1; i < this.mKey.length; i++) {
-            retval += this.mKey[i] * i;
-        }
-        if (this.mAlgorithm.equalsIgnoreCase("TripleDES")) {
-            return (retval ^= "desede".hashCode());
-        } else {
-            return (retval ^= this.mAlgorithm.toLowerCase(Locale.ENGLISH).hashCode());
-        }
-    }
-
-    /**
-     * Tests for equality between the specified object and this
-     * object. Two SecretKeySpec objects are considered equal if
-     * they are both SecretKey instances which have the
-     * same case-insensitive mAlgorithm name and mKey encoding.
-     *
-     * @param obj the object to test for equality with this object.
-     * @return true if the objects are considered equal, false if
-     * <code>obj</code> is null or otherwise.
-     */
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-
-        if (!(obj instanceof SecretKey)) {
-            return false;
-        }
-
-        String thatAlg = ((SecretKey) obj).getAlgorithm();
-        if (!(thatAlg.equalsIgnoreCase(this.mAlgorithm))) {
-            if ((!(thatAlg.equalsIgnoreCase("DESede"))
-                    || !(this.mAlgorithm.equalsIgnoreCase("TripleDES")))
-                    && (!(thatAlg.equalsIgnoreCase("TripleDES"))
-                    || !(this.mAlgorithm.equalsIgnoreCase("DESede")))) {
-                return false;
-            }
-        }
-
-        byte[] thatKey = ((SecretKey) obj).getEncoded();
-
-        return MessageDigest.isEqual(this.mKey, thatKey);
-    }
-
-    @Override
-    public void destroy() {
-        if (!mKeyDestroyed) {
-            Arrays.fill(mKey, (byte) 0);
-            this.mKey = null;
-            mKeyDestroyed = true;
-        }
-    }
-
-
-    /**
-     * Manually destroy the cipher by zeroing out all instances of the mKey.
-     *
-     * @param cipher The Cipher used with this mKey.
-     * @param opmode The opmode of the cipher.
-     */
-    public void destroyCipherKey(@NonNull Cipher cipher, int opmode) {
-        try {
-            byte[] blankKey = new byte[mSecureConfig.getSymmetricKeySize() / 8];
-            byte[] iv = new byte[SecureConfig.AES_IV_SIZE_BYTES];
-            Arrays.fill(blankKey, (byte) 0);
-            Arrays.fill(iv, (byte) 0);
-            EphemeralSecretKey blankSecretKey =
-                    new EphemeralSecretKey(blankKey, mSecureConfig.getSymmetricKeyAlgorithm());
-            cipher.init(opmode, blankSecretKey,
-                    new GCMParameterSpec(mSecureConfig.getSymmetricGcmTagLength(), iv));
-        } catch (GeneralSecurityException e) {
-            throw new SecurityException("Could not destroy mKey.");
-        }
-    }
-
-    @Override
-    public boolean isDestroyed() {
-        return mKeyDestroyed;
-    }
-}
-
diff --git a/security/crypto/src/main/java/androidx/security/crypto/FileCipher.java b/security/crypto/src/main/java/androidx/security/crypto/FileCipher.java
deleted file mode 100644
index 19b36d3..0000000
--- a/security/crypto/src/main/java/androidx/security/crypto/FileCipher.java
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.crypto;
-
-import android.util.Log;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-import androidx.security.SecureConfig;
-import androidx.security.context.SecureContextCompat;
-
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.channels.FileChannel;
-import java.util.Arrays;
-import java.util.concurrent.Executor;
-
-/**
- * Class used to create and read encrypted files. Provides implementations
- * of EncryptedFileInput/Output Streams.
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public class FileCipher {
-
-    private String mFileName;
-    private String mKeyPairAlias;
-    private FileInputStream mFileInputStream;
-    private FileOutputStream mFileOutputStream;
-
-    private SecureContextCompat.EncryptedFileInputStreamListener mListener;
-
-    SecureConfig mSecureConfig;
-
-    public FileCipher(@NonNull String fileName, @NonNull FileInputStream fileInputStream,
-                      @NonNull SecureConfig secureConfig, @NonNull Executor executor,
-                      @NonNull SecureContextCompat.EncryptedFileInputStreamListener listener)
-            throws IOException {
-        mFileName = fileName;
-        mFileInputStream = fileInputStream;
-        mSecureConfig = secureConfig;
-        EncryptedFileInputStream encryptedFileInputStream =
-                new EncryptedFileInputStream(mFileInputStream);
-        setEncryptedFileInputStreamListener(listener);
-        encryptedFileInputStream.decrypt(listener);
-    }
-
-    public FileCipher(@NonNull String keyPairAlias, @NonNull FileOutputStream fileOutputStream,
-                      @NonNull SecureConfig secureConfig) {
-        mKeyPairAlias = keyPairAlias;
-        mFileOutputStream = new EncryptedFileOutputStream(mFileName, mKeyPairAlias,
-                fileOutputStream);
-        mSecureConfig = secureConfig;
-    }
-
-    /**
-     * @param listener the listener to call back on
-     */
-    public void setEncryptedFileInputStreamListener(
-            @NonNull SecureContextCompat.EncryptedFileInputStreamListener listener) {
-        mListener = listener;
-    }
-
-    /**
-     * @return  the file output stream
-     */
-    @NonNull
-    public FileOutputStream getFileOutputStream() {
-        return mFileOutputStream;
-    }
-
-    /**
-     * @return the file input stream
-     */
-    @NonNull
-    public FileInputStream getFileInputStream() {
-        return mFileInputStream;
-    }
-
-    /**
-     * Encrypted file output stream
-     *
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
-    class EncryptedFileOutputStream extends FileOutputStream {
-
-        private static final String TAG = "EncryptedFOS";
-
-        FileOutputStream mFileOutputStream;
-        private String mKeyPairAlias;
-
-        EncryptedFileOutputStream(String name, String keyPairAlias,
-                FileOutputStream fileOutputStream) {
-            super(new FileDescriptor());
-            this.mKeyPairAlias = keyPairAlias;
-            this.mFileOutputStream = fileOutputStream;
-        }
-
-        String getAsymKeyPairAlias() {
-            return this.mKeyPairAlias;
-        }
-
-        @Override
-        public void write(@NonNull byte[] b) {
-            SecureKeyStore secureKeyStore = SecureKeyStore.getDefault();
-            if (!secureKeyStore.keyExists(getAsymKeyPairAlias())) {
-                SecureKeyGenerator keyGenerator = SecureKeyGenerator.getDefault();
-                keyGenerator.generateAsymmetricKeyPair(getAsymKeyPairAlias());
-            }
-            SecureKeyGenerator secureKeyGenerator = SecureKeyGenerator.getDefault();
-            final EphemeralSecretKey secretKey = secureKeyGenerator.generateEphemeralDataKey();
-            final SecureCipher secureCipher = SecureCipher
-                    .getDefault(mSecureConfig.getBiometricKeyAuthCallback());
-            final Pair<byte[], byte[]> encryptedData =
-                    secureCipher.encryptEphemeralData(secretKey, b);
-            secureCipher.encryptAsymmetric(getAsymKeyPairAlias(),
-                    secretKey.getEncoded(),
-                    new SecureCipher.SecureAsymmetricEncryptionListener() {
-                        public void encryptionComplete(byte[] encryptedEphemeralKey) {
-                            byte[] encodedData = secureCipher.encodeEphemeralData(
-                            getAsymKeyPairAlias().getBytes(), encryptedEphemeralKey,
-                            encryptedData.first, encryptedData.second);
-                            secretKey.destroy();
-                            try {
-                                mFileOutputStream.write(encodedData);
-                            } catch (IOException e) {
-                            Log.e(TAG, "Failed to write secure file.");
-                            e.printStackTrace();
-                        }
-                    }
-                });
-        }
-
-        @Override
-        public void write(int b) throws IOException {
-            throw new UnsupportedOperationException("For encrypted files, you must write all data "
-                    + "simultaneously. Call #write(byte[]).");
-        }
-
-        @Override
-        public void write(@NonNull byte[] b, int off, int len) throws IOException {
-            throw new UnsupportedOperationException("For encrypted files, you must write all data "
-                    + "simultaneously. Call #write(byte[]).");
-        }
-
-        @Override
-        public void close() throws IOException {
-            mFileOutputStream.close();
-        }
-
-        @NonNull
-        @Override
-        public FileChannel getChannel() {
-            throw new UnsupportedOperationException("For encrypted files, you must write all data "
-                    + "simultaneously. Call #write(byte[]).");
-        }
-
-        @Override
-        protected void finalize() throws IOException {
-            super.finalize();
-        }
-
-        @Override
-        public void flush() throws IOException {
-            mFileOutputStream.flush();
-        }
-    }
-
-
-    /**
-     * Encrypted file input stream
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
-    class EncryptedFileInputStream extends FileInputStream {
-
-        // Was 25 characters, truncating to fix compile error
-        private static final String TAG = "EncryptedFIS";
-
-        private FileInputStream mFileInputStream;
-        byte[] mDecryptedData;
-        private int mReadStatus = 0;
-
-        EncryptedFileInputStream(FileInputStream fileInputStream) {
-            super(new FileDescriptor());
-            this.mFileInputStream = fileInputStream;
-        }
-
-        @Override
-        public int read() throws IOException {
-            throw new UnsupportedOperationException("For encrypted files, you must read all data "
-                    + "simultaneously. Call #read(byte[]).");
-        }
-
-        void decrypt(final SecureContextCompat.EncryptedFileInputStreamListener listener)
-                throws IOException {
-            final EncryptedFileInputStream thisStream = this;
-            if (this.mDecryptedData == null) {
-                try {
-                    byte[] encodedData = new byte[mFileInputStream.available()];
-                    mReadStatus = mFileInputStream.read(encodedData);
-                    SecureCipher secureCipher = SecureCipher.getDefault(
-                            mSecureConfig.getBiometricKeyAuthCallback());
-                    secureCipher.decryptEncodedData(encodedData,
-                            new SecureCipher.SecureDecryptionListener() {
-                                public void decryptionComplete(byte[] clearText) {
-                                    thisStream.mDecryptedData = clearText;
-                                    //Binder.clearCallingIdentity();
-                                    listener.onEncryptedFileInput(thisStream);
-                                }
-                            });
-                } catch (IOException ex) {
-                    throw ex;
-                }
-            }
-        }
-
-        private void destroyCache() {
-            if (mDecryptedData != null) {
-                Arrays.fill(mDecryptedData, (byte) 0);
-                mDecryptedData = null;
-            }
-        }
-
-        @Override
-        public int read(@NonNull byte[] b) {
-            System.arraycopy(mDecryptedData, 0, b, 0, mDecryptedData.length);
-            return mReadStatus;
-        }
-
-        @Override
-        public int read(@NonNull byte[] b, int off, int len) throws IOException {
-            throw new UnsupportedOperationException("For encrypted files, you must read all data "
-                    + "simultaneously. Call #read(byte[]).");
-        }
-
-        @Override
-        public long skip(long n) throws IOException {
-            throw new UnsupportedOperationException("For encrypted files, you must read all data "
-                    + "simultaneously. Call #read(byte[]).");
-        }
-
-        @Override
-        public int available() {
-            return mDecryptedData.length;
-        }
-
-        @Override
-        public void close() throws IOException {
-            destroyCache();
-            mFileInputStream.close();
-        }
-
-        @Override
-        public FileChannel getChannel() {
-            throw new UnsupportedOperationException("For encrypted files, you must read all data "
-                    + "simultaneously. Call #read(byte[]).");
-        }
-
-        @Override
-        protected void finalize() throws IOException {
-            destroyCache();
-            super.finalize();
-        }
-
-        @Override
-        public synchronized void mark(int readlimit) {
-            throw new UnsupportedOperationException("For encrypted files, you must read all data "
-                    + "simultaneously. Call #read(byte[]).");
-        }
-
-        @Override
-        public synchronized void reset() throws IOException {
-            throw new UnsupportedOperationException("For encrypted files, you must read all data "
-                    + "simultaneously. Call #read(byte[]).");
-        }
-
-        @Override
-        public boolean markSupported() {
-            return false;
-        }
-
-    }
-
-}
diff --git a/security/crypto/src/main/java/androidx/security/crypto/MasterKeys.java b/security/crypto/src/main/java/androidx/security/crypto/MasterKeys.java
new file mode 100644
index 0000000..8b04c5b
--- /dev/null
+++ b/security/crypto/src/main/java/androidx/security/crypto/MasterKeys.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 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.security.crypto;
+
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+
+import androidx.annotation.NonNull;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.util.Arrays;
+
+import javax.crypto.KeyGenerator;
+
+/**
+ * Convenient methods to create and obtain master keys in Android Keystore.
+ *
+ * <p>The master keys are used to encrypt data encryption keys for encrypting files and preferences.
+ *
+ */
+public final class MasterKeys {
+
+    private static final int KEY_SIZE = 256;
+
+    private static final String ANDROID_KEYSTORE = "AndroidKeyStore";
+    static final String KEYSTORE_PATH_URI = "android-keystore://";
+    static final String MASTER_KEY_ALIAS = "_androidx_security_master_key_";
+
+    @NonNull
+    public static final KeyGenParameterSpec AES256_GCM_SPEC =
+            createAES256GCMKeyGenParameterSpec(MASTER_KEY_ALIAS);
+
+    /**
+     * Provides a safe and easy to use KenGenParameterSpec with the settings.
+     * Algorithm: AES
+     * Block Mode: GCM
+     * Padding: No Padding
+     * Key Size: 256
+     *
+     * @param keyAlias The alias for the master key
+     * @return The spec for the master key with the specified keyAlias
+     */
+    @NonNull
+    private static KeyGenParameterSpec createAES256GCMKeyGenParameterSpec(
+            @NonNull String keyAlias) {
+        KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
+                keyAlias,
+                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+                .setKeySize(KEY_SIZE);
+        return builder.build();
+    }
+
+    /**
+     * Provides a safe and easy to use KenGenParameterSpec with the settings with a default
+     * key alias.
+     *
+     * Algorithm: AES
+     * Block Mode: GCM
+     * Padding: No Padding
+     * Key Size: 256
+     *
+     * @return The spec for the master key with the default key alias
+     */
+    @NonNull
+    private static KeyGenParameterSpec createAES256GCMKeyGenParameterSpec() {
+        return createAES256GCMKeyGenParameterSpec(MASTER_KEY_ALIAS);
+    }
+
+    /**
+     * Creates or gets the master key provided
+     *
+     * The encryption scheme is required fields to ensure that the type of
+     * encryption used is clear to developers.
+     *
+     * @param keyGenParameterSpec The key encryption scheme
+     * @return The key alias for the master key
+     */
+    @NonNull
+    public static String getOrCreate(
+            @NonNull KeyGenParameterSpec keyGenParameterSpec)
+            throws GeneralSecurityException, IOException {
+        validate(keyGenParameterSpec);
+        if (!MasterKeys.keyExists(keyGenParameterSpec.getKeystoreAlias())) {
+            generateKey(keyGenParameterSpec);
+        }
+        return keyGenParameterSpec.getKeystoreAlias();
+    }
+
+    private static void validate(KeyGenParameterSpec spec) {
+        if (spec.getKeySize() != KEY_SIZE) {
+            throw new IllegalArgumentException(
+                    "invalid key size, want " + KEY_SIZE + " bits got " + spec.getKeySize()
+                            + " bits");
+        }
+        if (spec.getBlockModes().equals(new String[] { KeyProperties.BLOCK_MODE_GCM })) {
+            throw new IllegalArgumentException(
+                    "invalid block mode, want " + KeyProperties.BLOCK_MODE_GCM + " got "
+                            + Arrays.toString(spec.getBlockModes()));
+        }
+        if (spec.getPurposes() != (KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)) {
+            throw new IllegalArgumentException(
+                    "invalid purposes mode, want PURPOSE_ENCRYPT | PURPOSE_DECRYPT got "
+                            + spec.getPurposes());
+        }
+        if (spec.getEncryptionPaddings().equals(new String[]
+                { KeyProperties.ENCRYPTION_PADDING_NONE })) {
+            throw new IllegalArgumentException(
+                    "invalid padding mode, want " + KeyProperties.ENCRYPTION_PADDING_NONE + " got "
+                            + Arrays.toString(spec.getEncryptionPaddings()));
+        }
+    }
+
+    private static void generateKey(@NonNull KeyGenParameterSpec keyGenParameterSpec)
+            throws GeneralSecurityException {
+        KeyGenerator keyGenerator = KeyGenerator.getInstance(
+                KeyProperties.KEY_ALGORITHM_AES,
+                ANDROID_KEYSTORE);
+        keyGenerator.init(keyGenParameterSpec);
+        keyGenerator.generateKey();
+    }
+
+    private static boolean keyExists(@NonNull String keyAlias)
+            throws GeneralSecurityException, IOException {
+        KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
+        keyStore.load(null);
+        return keyStore.containsAlias(keyAlias);
+    }
+
+}
diff --git a/security/crypto/src/main/java/androidx/security/crypto/SecureCipher.java b/security/crypto/src/main/java/androidx/security/crypto/SecureCipher.java
deleted file mode 100644
index 5cd7af0..0000000
--- a/security/crypto/src/main/java/androidx/security/crypto/SecureCipher.java
+++ /dev/null
@@ -1,665 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.crypto;
-
-import android.os.Build;
-import android.security.keystore.KeyProperties;
-import android.util.Log;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-import androidx.security.SecureConfig;
-import androidx.security.biometric.BiometricKeyAuthCallback;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.security.GeneralSecurityException;
-import java.security.Key;
-import java.security.KeyStore;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.spec.MGF1ParameterSpec;
-
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.GCMParameterSpec;
-import javax.crypto.spec.OAEPParameterSpec;
-import javax.crypto.spec.PSource;
-
-/**
- * Class that helps encrypt and decrypt data.
- */
-public class SecureCipher {
-
-    private static final String TAG = "SecureCipher";
-    private static final int FILE_ENCODING_VERSION = 1;
-
-    private SecureConfig mSecureConfig;
-
-    /**
-     * Listener for encryption that requires biometric prompt
-     */
-    public interface SecureAuthListener {
-        /**
-         * @param status the status of auth
-         */
-        void authComplete(@NonNull BiometricKeyAuthCallback.BiometricStatus status);
-    }
-
-    /**
-     * Listener for encrypting symmetric data
-     */
-    public interface SecureSymmetricEncryptionListener {
-        /**
-         * @param cipherText the encrypted cipher text
-         * @param iv the initialization vector used
-         */
-        void encryptionComplete(@NonNull byte[] cipherText, @NonNull byte[] iv);
-    }
-
-    /**
-     * Listener for encrypting asymmetric data
-     */
-    public interface SecureAsymmetricEncryptionListener {
-        /**
-         * @param cipherText the encrypted cipher text
-         */
-        void encryptionComplete(@NonNull byte[] cipherText);
-    }
-
-    /**
-     * The listener for decryption
-     */
-    public interface SecureDecryptionListener {
-        /**
-         * @param clearText the decrypted text
-         */
-        void decryptionComplete(@NonNull byte[] clearText);
-    }
-
-    /**
-     * The listener for signing data
-     */
-    public interface SecureSignListener {
-        /**
-         * @param signature the signature
-         */
-        void signComplete(@NonNull byte[] signature);
-    }
-
-    /**
-     * @return The secure cipher
-     */
-    @NonNull
-    public static SecureCipher getDefault() {
-        return new SecureCipher(SecureConfig.getDefault());
-    }
-
-    /**
-     * @param biometricKeyAuthCallback The callback to use when authenticating a key
-     * @return the cipher using biometric prompt
-     */
-    @NonNull
-    public static SecureCipher getDefault(
-            @NonNull BiometricKeyAuthCallback biometricKeyAuthCallback) {
-        SecureConfig secureConfig = SecureConfig.getDefault();
-        secureConfig.setSymmetricRequireUserAuth(true);
-        secureConfig.setAsymmetricRequireUserAuth(true);
-        secureConfig.setBiometricKeyAuthCallback(biometricKeyAuthCallback);
-        return new SecureCipher(secureConfig);
-    }
-
-    /**
-     * Gets an instance using the specified configuration
-     *
-     * @param secureConfig the config
-     * @return the secure cipher
-     */
-    @NonNull
-    public static SecureCipher getInstance(@NonNull SecureConfig secureConfig) {
-        return new SecureCipher(secureConfig);
-    }
-
-
-    public SecureCipher(@NonNull SecureConfig secureConfig) {
-        this.mSecureConfig = secureConfig;
-    }
-
-    /**
-     * The encoding mType for files
-     */
-    public enum SecureFileEncodingType {
-        SYMMETRIC(0),
-        ASYMMETRIC(1),
-        EPHEMERAL(2),
-        NOT_ENCRYPTED(1000);
-
-        private final int mType;
-
-        SecureFileEncodingType(int type) {
-            this.mType = type;
-        }
-
-        /**
-         * @return the id
-         */
-        public int getType() {
-            return this.mType;
-        }
-
-        /**
-         * @param id the id
-         * @return the encoding mType
-         */
-        @NonNull
-        public static SecureFileEncodingType fromId(int id) {
-            switch (id) {
-                case 0:
-                    return SYMMETRIC;
-                case 1:
-                    return ASYMMETRIC;
-                case 2:
-                    return EPHEMERAL;
-            }
-            return NOT_ENCRYPTED;
-        }
-
-    }
-
-
-    /**
-     * Encrypts data with an existing key alias from the AndroidKeyStore.
-     *
-     * @param keyAlias  The name of the existing SecretKey to retrieve from the AndroidKeyStore.
-     * @param clearData The unencrypted data to encrypt
-     */
-    public void encrypt(@NonNull String keyAlias,
-            @NonNull final byte[] clearData,
-            @NonNull final SecureSymmetricEncryptionListener callback) {
-        try {
-            KeyStore keyStore = KeyStore.getInstance(mSecureConfig.getAndroidKeyStore());
-            keyStore.load(null);
-            SecretKey key = (SecretKey) keyStore.getKey(keyAlias, null);
-            final Cipher cipher = Cipher.getInstance(
-                    mSecureConfig.getSymmetricCipherTransformation());
-            cipher.init(Cipher.ENCRYPT_MODE, key);
-            byte[] iv = cipher.getIV();
-            if (mSecureConfig.getSymmetricRequireUserAuthEnabled()) {
-                mSecureConfig.getBiometricKeyAuthCallback().authenticateKey(cipher,
-                        new SecureAuthListener() {
-                            public void authComplete(
-                                    BiometricKeyAuthCallback.BiometricStatus status) {
-
-                                switch (status) {
-                                    case SUCCESS:
-                                        try {
-                                            callback.encryptionComplete(cipher.doFinal(clearData),
-                                                    cipher.getIV());
-                                        } catch (GeneralSecurityException e) {
-                                            e.printStackTrace();
-                                        }
-                                        break;
-                                    default:
-                                        Log.i(TAG, "Failure");
-                                        callback.encryptionComplete(null, null);
-                                }
-                            }
-                        });
-            } else {
-                callback.encryptionComplete(cipher.doFinal(clearData), cipher.getIV());
-            }
-        } catch (GeneralSecurityException ex) {
-            throw new SecurityException(ex);
-        } catch (IOException ex) {
-            throw new SecurityException(ex);
-        }
-    }
-
-    /**
-     * Signs data based on the specific key that has been generated
-     *
-     * Uses SecureConfig.getSignatureAlgorithm for algorithm type
-     *
-     * @param keyAlias The key to use for signing
-     * @param clearData The data to sign
-     * @param callback The listener to call back with the signature
-     */
-    public void sign(@NonNull String keyAlias,
-            @NonNull final byte[] clearData,
-            @NonNull final SecureSignListener callback) {
-        byte[] dataSignature = new byte[0];
-        try {
-            KeyStore keyStore = KeyStore.getInstance(mSecureConfig.getAndroidKeyStore());
-            keyStore.load(null);
-            PrivateKey key = (PrivateKey) keyStore.getKey(keyAlias, null);
-            final Signature signature = Signature.getInstance(
-                    mSecureConfig.getSignatureAlgorithm());
-            signature.initSign(key);
-            signature.update(clearData);
-            if (mSecureConfig.getAsymmetricRequireUserAuthEnabled()) {
-                mSecureConfig.getBiometricKeyAuthCallback().authenticateKey(signature,
-                        new SecureAuthListener() {
-                            public void authComplete(
-                                    BiometricKeyAuthCallback.BiometricStatus status) {
-                                Log.i(TAG, "Finished success auth!");
-                                switch (status) {
-                                    case SUCCESS:
-                                        try {
-                                            byte[] sig = signature.sign();
-                                            Log.i(TAG, "Signed " + clearData.length
-                                                    + " bytes");
-                                            callback.signComplete(sig);
-
-                                        } catch (GeneralSecurityException e) {
-                                            e.printStackTrace();
-                                        }
-                                        break;
-                                    default:
-                                        Log.i(TAG, "Failure");
-                                        callback.signComplete(null);
-                                }
-                            }
-                        });
-            } else {
-                dataSignature = signature.sign();
-                callback.signComplete(dataSignature);
-            }
-        } catch (GeneralSecurityException ex) {
-            ex.printStackTrace();
-            //Log.e(TAG, ex.getMessage());
-        } catch (IOException ex) {
-            Log.e(TAG, ex.getMessage());
-            ex.printStackTrace();
-        }
-    }
-
-    /**
-     * Verifies signed data
-     *
-     * Uses SecureConfig.getSignatureAlgorithm for algorithm type
-     *
-     * @param keyAlias The key to use for signing
-     * @param clearData The signed data
-     * @param signature The signature
-     * @return true if the provided signature is valid, false otherwise
-     */
-    public boolean verify(@NonNull String keyAlias,
-            @NonNull final byte[] clearData,
-            @NonNull final byte[] signature) {
-        try {
-            KeyStore keyStore = KeyStore.getInstance(mSecureConfig.getAndroidKeyStore());
-            keyStore.load(null);
-            PublicKey publicKey = keyStore.getCertificate(keyAlias).getPublicKey();
-            final Signature signatureObject = Signature.getInstance(
-                    mSecureConfig.getSignatureAlgorithm());
-            signatureObject.initVerify(publicKey);
-            signatureObject.update(clearData);
-            return signatureObject.verify(signature);
-        } catch (GeneralSecurityException ex) {
-            ex.printStackTrace();
-            //Log.e(TAG, ex.getMessage());
-        } catch (IOException ex) {
-            Log.e(TAG, ex.getMessage());
-            ex.printStackTrace();
-        }
-        return false;
-    }
-
-    /**
-     * Encrypts data with a public key from the cert in the AndroidKeyStore.
-     *
-     * @param keyAlias  The name of the existing KeyPair to retrieve the
-     *                  PublicKey from the AndroidKeyStore.
-     * @param clearData The unencrypted data to encrypt
-     */
-    public void encryptAsymmetric(@NonNull String keyAlias,
-            @NonNull byte[] clearData, @NonNull SecureAsymmetricEncryptionListener callback) {
-        try {
-            KeyStore keyStore = KeyStore.getInstance(mSecureConfig.getAndroidKeyStore());
-            keyStore.load(null);
-            PublicKey publicKey = keyStore.getCertificate(keyAlias).getPublicKey();
-            Cipher cipher = Cipher.getInstance(
-                    mSecureConfig.getAsymmetricCipherTransformation());
-            // Check to see if there are other padding types with a complex config.
-            if (mSecureConfig.getAsymmetricPaddings().equals(
-                    KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)) {
-                cipher.init(Cipher.ENCRYPT_MODE, publicKey,
-                        new OAEPParameterSpec("SHA-256",
-                        "MGF1", new MGF1ParameterSpec("SHA-1"),
-                                PSource.PSpecified.DEFAULT));
-            } else {
-                cipher.init(Cipher.ENCRYPT_MODE, publicKey);
-            }
-            byte[] clearText = cipher.doFinal(clearData);
-            callback.encryptionComplete(clearText);
-        } catch (GeneralSecurityException ex) {
-            throw new SecurityException(ex);
-        } catch (IOException ex) {
-            throw new SecurityException(ex);
-        }
-    }
-
-    /**
-     * Encrypts data using an Ephemeral key, destroying any trace of the key from the Cipher used.
-     *
-     * @param ephemeralSecretKey The generated Ephemeral key
-     * @param clearData          The unencrypted data to encrypt
-     * @return A Pair of byte[]'s, first is the encrypted data, second is the IV
-     * (initialization vector)
-     * used to encrypt which is required for decryption
-     */
-    @NonNull
-    public Pair<byte[], byte[]> encryptEphemeralData(
-            @NonNull EphemeralSecretKey ephemeralSecretKey, @NonNull byte[] clearData) {
-        try {
-            SecureRandom secureRandom = null;
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-                secureRandom = SecureRandom.getInstanceStrong();
-            } else {
-                secureRandom = new SecureRandom();
-            }
-            byte[] iv = new byte[SecureConfig.AES_IV_SIZE_BYTES];
-            secureRandom.nextBytes(iv);
-            GCMParameterSpec parameterSpec = new GCMParameterSpec(
-                    mSecureConfig.getSymmetricGcmTagLength(), iv);
-            final Cipher cipher = Cipher.getInstance(
-                    mSecureConfig.getSymmetricCipherTransformation());
-            cipher.init(Cipher.ENCRYPT_MODE, ephemeralSecretKey, parameterSpec);
-            byte[] encryptedData = cipher.doFinal(clearData);
-            ephemeralSecretKey.destroyCipherKey(cipher, Cipher.ENCRYPT_MODE);
-            return new Pair<>(encryptedData, iv);
-        } catch (GeneralSecurityException ex) {
-            throw new SecurityException(ex);
-        }
-    }
-
-    /**
-     * Decrypts a previously encrypted byte[]
-     * <p>
-     * Destroys all traces of the key data in the Cipher.
-     *
-     * @param ephemeralSecretKey   The generated Ephemeral key
-     * @param encryptedData        The byte[] of encrypted data
-     * @param initializationVector The IV of which the encrypted data was encrypted with
-     * @return The byte[] of data that has been decrypted
-     */
-    @NonNull
-    public byte[] decryptEphemeralData(@NonNull EphemeralSecretKey ephemeralSecretKey,
-            @NonNull byte[] encryptedData, @NonNull byte[] initializationVector) {
-        try {
-            final Cipher cipher = Cipher.getInstance(
-                    mSecureConfig.getSymmetricCipherTransformation());
-            cipher.init(Cipher.DECRYPT_MODE, ephemeralSecretKey,
-                    new GCMParameterSpec(
-                            mSecureConfig.getSymmetricGcmTagLength(), initializationVector));
-            byte[] decryptedData = cipher.doFinal(encryptedData);
-            ephemeralSecretKey.destroyCipherKey(cipher, Cipher.DECRYPT_MODE);
-            return decryptedData;
-        } catch (GeneralSecurityException ex) {
-            throw new SecurityException(ex);
-        }
-    }
-
-    /**
-     * Decrypts a previously encrypted byte[]
-     *
-     * @param keyAlias             The name of the existing SecretKey to retrieve from the
-     *                             AndroidKeyStore.
-     * @param encryptedData        The byte[] of encrypted data
-     * @param initializationVector The IV of which the encrypted data was encrypted with
-     */
-    public void decrypt(@NonNull String keyAlias,
-            @NonNull final byte[] encryptedData, @NonNull byte[] initializationVector,
-            @NonNull final SecureDecryptionListener callback) {
-        byte[] decryptedData = new byte[0];
-        try {
-            KeyStore keyStore = KeyStore.getInstance(mSecureConfig.getAndroidKeyStore());
-            keyStore.load(null);
-            Key key = keyStore.getKey(keyAlias, null);
-            final Cipher cipher = Cipher.getInstance(
-                    mSecureConfig.getSymmetricCipherTransformation());
-            GCMParameterSpec spec = new GCMParameterSpec(
-                    mSecureConfig.getSymmetricGcmTagLength(), initializationVector);
-            cipher.init(Cipher.DECRYPT_MODE, key, spec);
-            if (mSecureConfig.getSymmetricRequireUserAuthEnabled()) {
-                mSecureConfig.getBiometricKeyAuthCallback().authenticateKey(cipher,
-                        new SecureAuthListener() {
-                            public void authComplete(
-                                    BiometricKeyAuthCallback.BiometricStatus status) {
-                                switch (status) {
-                                    case SUCCESS:
-                                        try {
-                                            callback.decryptionComplete(
-                                                    cipher.doFinal(encryptedData));
-                                        } catch (GeneralSecurityException e) {
-                                            e.printStackTrace();
-                                        }
-                                        break;
-                                    default:
-                                        Log.i(TAG, "Failure");
-                                        callback.decryptionComplete(null);
-                                }
-                            }
-                        });
-            } else {
-                callback.decryptionComplete(cipher.doFinal(encryptedData));
-            }
-        } catch (GeneralSecurityException ex) {
-            throw new SecurityException(ex);
-        } catch (IOException ex) {
-            throw new SecurityException(ex);
-        }
-    }
-
-    /**
-     * Decrypts a previously encrypted byte[] with the PrivateKey
-     *
-     * @param keyAlias      The name of the existing KeyPair to retrieve from the AndroidKeyStore.
-     * @param encryptedData The byte[] of encrypted data
-     */
-    public void decryptAsymmetric(@NonNull String keyAlias,
-            @NonNull final byte[] encryptedData,
-            @NonNull final SecureDecryptionListener callback) {
-        byte[] decryptedData = new byte[0];
-        try {
-            KeyStore keyStore = KeyStore.getInstance(mSecureConfig.getAndroidKeyStore());
-            keyStore.load(null);
-            PrivateKey key = (PrivateKey) keyStore.getKey(keyAlias, null);
-            final Cipher cipher = Cipher.getInstance(
-                    mSecureConfig.getAsymmetricCipherTransformation());
-            if (mSecureConfig.getAsymmetricPaddings().equals(
-                    KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)) {
-                cipher.init(Cipher.DECRYPT_MODE, key, new OAEPParameterSpec("SHA-256",
-                        "MGF1",
-                        new MGF1ParameterSpec("SHA-1"), PSource.PSpecified.DEFAULT));
-            } else {
-                cipher.init(Cipher.DECRYPT_MODE, key);
-            }
-            if (mSecureConfig.getAsymmetricRequireUserAuthEnabled()) {
-                mSecureConfig.getBiometricKeyAuthCallback().authenticateKey(cipher,
-                        new SecureAuthListener() {
-                            public void authComplete(
-                                    BiometricKeyAuthCallback.BiometricStatus status) {
-                                Log.i(TAG, "Finished success auth!");
-                                switch (status) {
-                                    case SUCCESS:
-                                        try {
-                                            byte[] clearData = cipher.doFinal(encryptedData);
-                                            Log.i(TAG, "Decrypted " + new String(clearData));
-                                            callback.decryptionComplete(clearData);
-
-                                        } catch (GeneralSecurityException e) {
-                                            e.printStackTrace();
-                                        }
-                                        break;
-                                    default:
-                                        Log.i(TAG, "Failure");
-                                        callback.decryptionComplete(null);
-                                }
-                            }
-                        });
-            } else {
-                decryptedData = cipher.doFinal(encryptedData);
-                callback.decryptionComplete(decryptedData);
-            }
-        } catch (GeneralSecurityException ex) {
-            ex.printStackTrace();
-            //Log.e(TAG, ex.getMessage());
-        } catch (IOException ex) {
-            Log.e(TAG, ex.getMessage());
-            ex.printStackTrace();
-        }
-    }
-
-    /**
-     * @param keyPairAlias
-     * @param encryptedKey
-     * @param cipherText
-     * @param iv
-     * @return
-     */
-    @NonNull
-    public byte[] encodeEphemeralData(@NonNull byte[] keyPairAlias, @NonNull byte[] encryptedKey,
-            @NonNull byte[] cipherText, @NonNull byte[] iv) {
-        ByteBuffer byteBuffer = ByteBuffer.allocate(((Integer.SIZE / 8) * 5) + iv.length
-                + keyPairAlias.length + encryptedKey.length + cipherText.length);
-        byteBuffer.putInt(SecureFileEncodingType.EPHEMERAL.getType());
-        byteBuffer.putInt(FILE_ENCODING_VERSION);
-        byteBuffer.putInt(encryptedKey.length);
-        byteBuffer.put(encryptedKey);
-        byteBuffer.putInt(iv.length);
-        byteBuffer.put(iv);
-        byteBuffer.putInt(keyPairAlias.length);
-        byteBuffer.put(keyPairAlias);
-        byteBuffer.put(cipherText);
-        return byteBuffer.array();
-    }
-
-    /**
-     * @param keyAlias
-     * @param cipherText
-     * @param iv
-     * @return
-     */
-    @NonNull
-    public byte[] encodeSymmetricData(@NonNull byte[] keyAlias, @NonNull byte[] cipherText,
-            @NonNull byte[] iv) {
-        ByteBuffer byteBuffer = ByteBuffer.allocate(((Integer.SIZE / 8) * 4) + iv.length
-                + keyAlias.length + cipherText.length);
-        byteBuffer.putInt(SecureFileEncodingType.SYMMETRIC.getType());
-        byteBuffer.putInt(FILE_ENCODING_VERSION);
-        byteBuffer.putInt(iv.length);
-        byteBuffer.put(iv);
-        byteBuffer.putInt(keyAlias.length);
-        byteBuffer.put(keyAlias);
-        byteBuffer.put(cipherText);
-        return byteBuffer.array();
-    }
-
-    /**
-     * @param keyPairAlias
-     * @param cipherText
-     * @return
-     */
-    @NonNull
-    public byte[] encodeAsymmetricData(@NonNull byte[] keyPairAlias,
-            @NonNull byte[] cipherText) {
-        ByteBuffer byteBuffer = ByteBuffer.allocate(((Integer.SIZE / 8) * 3)
-                + keyPairAlias.length + cipherText.length);
-        byteBuffer.putInt(SecureFileEncodingType.ASYMMETRIC.getType());
-        byteBuffer.putInt(FILE_ENCODING_VERSION);
-        byteBuffer.putInt(keyPairAlias.length);
-        byteBuffer.put(keyPairAlias);
-        byteBuffer.put(cipherText);
-        return byteBuffer.array();
-    }
-
-    /**
-     * @param encodedCipherText
-     * @param callback
-     */
-    @SuppressWarnings("fallthrough")
-    public void decryptEncodedData(@NonNull byte[] encodedCipherText,
-            @NonNull final SecureDecryptionListener callback) {
-        ByteBuffer byteBuffer = ByteBuffer.wrap(encodedCipherText);
-        int encodingTypeVal = byteBuffer.getInt();
-        SecureFileEncodingType encodingType = SecureFileEncodingType.fromId(encodingTypeVal);
-        int encodingVersion = byteBuffer.getInt();
-        byte[] encodedEphKey = null;
-        byte[] iv = null;
-        String keyAlias = null;
-        byte[] cipherText = null;
-
-        switch (encodingType) {
-            case EPHEMERAL:
-                int encodedEphKeyLength = byteBuffer.getInt();
-                encodedEphKey = new byte[encodedEphKeyLength];
-                byteBuffer.get(encodedEphKey);
-                // fall through
-            case SYMMETRIC:
-                int ivLength = byteBuffer.getInt();
-                iv = new byte[ivLength];
-                byteBuffer.get(iv);
-                // fall through
-            case ASYMMETRIC:
-                int keyAliasLength = byteBuffer.getInt();
-                byte[] keyAliasBytes = new byte[keyAliasLength];
-                byteBuffer.get(keyAliasBytes);
-                keyAlias = new String(keyAliasBytes);
-                cipherText = new byte[byteBuffer.remaining()];
-                byteBuffer.get(cipherText);
-                break;
-            case NOT_ENCRYPTED:
-                throw new SecurityException("Cannot determine file mType.");
-        }
-        switch (encodingType) {
-            case EPHEMERAL:
-                final byte[] ephemeralCipherText = cipherText;
-                final byte[] ephemeralIv = iv;
-                decryptAsymmetric(keyAlias, encodedEphKey,
-                        new SecureDecryptionListener() {
-                            @java.lang.Override
-                            public void decryptionComplete(byte[] clearText) {
-                                EphemeralSecretKey ephemeralSecretKey =
-                                        new EphemeralSecretKey(clearText);
-                                byte[] decrypted = decryptEphemeralData(
-                                        ephemeralSecretKey,
-                                        ephemeralCipherText, ephemeralIv);
-                                callback.decryptionComplete(decrypted);
-                                ephemeralSecretKey.destroy();
-                            }
-                        });
-
-                break;
-            case SYMMETRIC:
-                decrypt(
-                        keyAlias,
-                        cipherText, iv, callback);
-                break;
-            case ASYMMETRIC:
-                decryptAsymmetric(
-                        keyAlias,
-                        cipherText, callback);
-                break;
-            case NOT_ENCRYPTED:
-                throw new SecurityException("File not encrypted.");
-        }
-    }
-
-}
diff --git a/security/crypto/src/main/java/androidx/security/crypto/SecureKeyGenerator.java b/security/crypto/src/main/java/androidx/security/crypto/SecureKeyGenerator.java
deleted file mode 100644
index fa698e5..0000000
--- a/security/crypto/src/main/java/androidx/security/crypto/SecureKeyGenerator.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package androidx.security.crypto;
-
-import android.os.Build;
-import android.security.keystore.KeyGenParameterSpec;
-import android.security.keystore.KeyProperties;
-
-import androidx.annotation.NonNull;
-import androidx.security.SecureConfig;
-
-import java.security.GeneralSecurityException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.SecureRandom;
-
-import javax.crypto.KeyGenerator;
-
-/**
- * Class that provides easy and reliable key generation for both symmetric and asymmetric crypto.
- */
-public class SecureKeyGenerator {
-
-    private SecureConfig mSecureConfig;
-
-    @NonNull
-    public static SecureKeyGenerator getDefault() {
-        return new SecureKeyGenerator(SecureConfig.getDefault());
-    }
-
-    /**
-     * @param secureConfig The config
-     * @return The key generator
-     */
-    @NonNull
-    public static SecureKeyGenerator getInstance(@NonNull SecureConfig secureConfig) {
-        return new SecureKeyGenerator(secureConfig);
-    }
-
-    private SecureKeyGenerator(SecureConfig secureConfig) {
-        this.mSecureConfig = secureConfig;
-    }
-
-    /**
-     * <p>
-     * Generates a sensitive data key and adds the SecretKey to the AndroidKeyStore.
-     * Utilizes UnlockedDeviceProtection to ensure that the device must be unlocked in order to
-     * use the generated key.
-     * </p>
-     *
-     * @param keyAlias The name of the generated SecretKey to save into the AndroidKeyStore.
-     * @return true if the key was generated, false otherwise
-     */
-    //@TargetApi(Build.VERSION_CODES.P)
-    public boolean generateKey(@NonNull String keyAlias) {
-        boolean created = false;
-        try {
-            KeyGenerator keyGenerator = KeyGenerator.getInstance(
-                    mSecureConfig.getSymmetricKeyAlgorithm(), mSecureConfig.getAndroidKeyStore());
-            KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
-                    keyAlias, mSecureConfig.getSymmetricKeyPurposes())
-                    .setBlockModes(mSecureConfig.getSymmetricBlockModes())
-                    .setEncryptionPaddings(mSecureConfig.getSymmetricPaddings())
-                    .setKeySize(mSecureConfig.getSymmetricKeySize());
-            builder = builder.setUserAuthenticationRequired(
-                    mSecureConfig.getSymmetricRequireUserAuthEnabled());
-            builder = builder.setUserAuthenticationValidityDurationSeconds(
-                    mSecureConfig.getSymmetricRequireUserValiditySeconds());
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
-                builder = builder.setUnlockedDeviceRequired(
-                        mSecureConfig.getSymmetricSensitiveDataProtectionEnabled());
-            }
-            keyGenerator.init(builder.build());
-            keyGenerator.generateKey();
-            created = true;
-        } catch (NoSuchAlgorithmException ex) {
-            throw new SecurityException(ex);
-        } catch (InvalidAlgorithmParameterException ex) {
-            throw new SecurityException(ex);
-        } catch (NoSuchProviderException ex) {
-            throw new SecurityException(ex);
-        }
-        return created;
-    }
-
-    /**
-     * <p>
-     * Generates a sensitive data public/private key pair and adds the KeyPair to the
-     * AndroidKeyStore. Utilizes UnlockedDeviceProtection to ensure that the device
-     * must be unlocked in order to use the generated key.
-     * </p>
-     * <p>
-     * ANDROID P ONLY (API LEVEL 28>)
-     * </p>
-     *
-     * @param keyPairAlias The name of the generated SecretKey to save into the AndroidKeyStore.
-     * @return true if the key was generated, false otherwise
-     */
-    public boolean generateAsymmetricKeyPair(@NonNull String keyPairAlias) {
-        boolean created = false;
-        try {
-            KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance(
-                    mSecureConfig.getAsymmetricKeyPairAlgorithm(),
-                    mSecureConfig.getAndroidKeyStore());
-            KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
-                    keyPairAlias, mSecureConfig.getAsymmetricKeyPurposes())
-                    .setEncryptionPaddings(mSecureConfig.getAsymmetricPaddings())
-                    .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
-                    .setBlockModes(mSecureConfig.getAsymmetricBlockModes())
-                    .setKeySize(mSecureConfig.getAsymmetricKeySize());
-            builder = builder.setUserAuthenticationRequired(
-                    mSecureConfig.getAsymmetricRequireUserAuthEnabled());
-            builder = builder.setUserAuthenticationValidityDurationSeconds(
-                    mSecureConfig.getAsymmetricRequireUserValiditySeconds());
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
-                builder = builder.setUnlockedDeviceRequired(
-                        mSecureConfig.getAsymmetricSensitiveDataProtectionEnabled());
-            }
-            keyGenerator.initialize(builder.build());
-            keyGenerator.generateKeyPair();
-            created = true;
-        } catch (NoSuchProviderException | InvalidAlgorithmParameterException
-                | NoSuchAlgorithmException ex) {
-            throw new SecurityException(ex);
-        }
-        return created;
-    }
-
-    /**
-     * <p>
-     * Generates an Ephemeral symmetric key that can be fully destroyed and removed from memory.
-     * </p>
-     *
-     * @return The EphemeralSecretKey generated
-     */
-    @NonNull
-    public EphemeralSecretKey generateEphemeralDataKey() {
-        try {
-            SecureRandom secureRandom;
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-                secureRandom = SecureRandom.getInstanceStrong();
-            } else {
-                // Not best practices, TODO update this as per this SO thread
-                // https://stackoverflow.com/questions/36813098/securerandom-provider-crypto-
-                // unavailable-in-android-n-for-deterministially-gen
-                secureRandom = new SecureRandom();
-            }
-            byte[] key = new byte[mSecureConfig.getSymmetricKeySize() / 8];
-            secureRandom.nextBytes(key);
-            return new EphemeralSecretKey(key, mSecureConfig.getSymmetricKeyAlgorithm());
-        } catch (GeneralSecurityException ex) {
-            throw new SecurityException(ex);
-        }
-    }
-
-}
diff --git a/security/crypto/src/main/java/androidx/security/crypto/SecureKeyStore.java b/security/crypto/src/main/java/androidx/security/crypto/SecureKeyStore.java
deleted file mode 100644
index b3a0681..0000000
--- a/security/crypto/src/main/java/androidx/security/crypto/SecureKeyStore.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright 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.security.crypto;
-
-import android.security.keystore.KeyInfo;
-
-import androidx.annotation.NonNull;
-import androidx.security.SecureConfig;
-
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-import java.security.KeyFactory;
-import java.security.KeyStore;
-import java.security.PrivateKey;
-
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-
-/**
- * A class that provides simpler access to the AndroidKeyStore including commonly used utilities.
- */
-public class SecureKeyStore {
-
-    private static final String TAG = "SecureKeyStore";
-
-    private SecureConfig mSecureConfig;
-
-    @NonNull
-    public static SecureKeyStore getDefault() {
-        return new SecureKeyStore(SecureConfig.getDefault());
-    }
-
-    /**
-     * Gets an instance of SecureKeyStore based on the provided SecureConfig.
-     *
-     * @param secureConfig The SecureConfig to use the KeyStore.
-     * @return A SecureKeyStore that has been configured.
-     */
-    @NonNull
-    public static SecureKeyStore getInstance(@NonNull SecureConfig secureConfig) {
-        return new SecureKeyStore(secureConfig);
-    }
-
-    private SecureKeyStore(@NonNull SecureConfig secureConfig) {
-        this.mSecureConfig = secureConfig;
-    }
-
-    /**
-     * Checks to see if the specified key exists in the AndroidKeyStore
-     *
-     * @param keyAlias The name of the generated SecretKey to save into the AndroidKeyStore.
-     * @return true if the key is stored in secure hardware
-     */
-    public boolean keyExists(@NonNull String keyAlias) {
-        boolean exists = false;
-        try {
-            KeyStore keyStore = KeyStore.getInstance(mSecureConfig.getAndroidKeyStore());
-            keyStore.load(null);
-            exists = keyStore.containsAlias(keyAlias);
-            /*Certificate cert = keyStore.getCertificate(keyAlias);
-            if (cert != null) {
-                exists = cert.getPublicKey() != null;
-            }*/
-        } catch (GeneralSecurityException ex) {
-            throw new SecurityException(ex);
-        } catch (IOException ex) {
-            throw new SecurityException(ex);
-        }
-        return exists;
-    }
-
-
-    /**
-     * Delete a key from the specified keystore.
-     *
-     * @param keyAlias The key to delete from the KeyStore
-     */
-    public void deleteKey(@NonNull String keyAlias) {
-        try {
-            KeyStore keyStore = KeyStore.getInstance(mSecureConfig.getAndroidKeyStore());
-            keyStore.load(null);
-            keyStore.deleteEntry(keyAlias);
-        } catch (GeneralSecurityException ex) {
-            throw new SecurityException(ex);
-        } catch (IOException ex) {
-            throw new SecurityException(ex);
-        }
-    }
-
-    /**
-     * Checks to see if the specified key is stored in secure hardware
-     *
-     * @param keyAlias The name of the generated SecretKey to save into the AndroidKeyStore.
-     * @return true if the key is stored in secure hardware
-     */
-    public boolean checkKeyInsideSecureHardware(@NonNull String keyAlias) {
-        boolean inHardware = false;
-        try {
-            KeyStore keyStore = KeyStore.getInstance(mSecureConfig.getAndroidKeyStore());
-            keyStore.load(null);
-            SecretKey key = (SecretKey) keyStore.getKey(keyAlias, null);
-            SecretKeyFactory factory = SecretKeyFactory.getInstance(key.getAlgorithm(),
-                    mSecureConfig.getAndroidKeyStore());
-            KeyInfo keyInfo;
-            keyInfo = (KeyInfo) factory.getKeySpec(key, KeyInfo.class);
-            inHardware = keyInfo.isInsideSecureHardware();
-            return inHardware;
-        } catch (GeneralSecurityException e) {
-            return inHardware;
-        } catch (IOException e) {
-            return inHardware;
-        }
-    }
-
-    /**
-     * Checks to see if the specified private key is stored in secure hardware
-     *
-     * @param keyAlias The name of the generated SecretKey to save into the AndroidKeyStore.
-     * @return true if the key is stored in secure hardware
-     */
-    public boolean checkKeyInsideSecureHardwareAsymmetric(@NonNull String keyAlias) {
-        boolean inHardware = false;
-        try {
-            KeyStore keyStore = KeyStore.getInstance(mSecureConfig.getAndroidKeyStore());
-            keyStore.load(null);
-            PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, null);
-
-            KeyFactory factory = KeyFactory.getInstance(privateKey.getAlgorithm(),
-                    mSecureConfig.getAndroidKeyStore());
-            KeyInfo keyInfo;
-
-            keyInfo = factory.getKeySpec(privateKey, KeyInfo.class);
-            inHardware = keyInfo.isInsideSecureHardware();
-            return inHardware;
-
-        } catch (GeneralSecurityException e) {
-            return inHardware;
-        } catch (IOException e) {
-            return inHardware;
-        }
-    }
-
-}
diff --git a/security/crypto/src/main/java/androidx/security/net/SecureKeyManager.java b/security/crypto/src/main/java/androidx/security/net/SecureKeyManager.java
deleted file mode 100644
index 77c10b2..0000000
--- a/security/crypto/src/main/java/androidx/security/net/SecureKeyManager.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package androidx.security.net;
-
-
-import android.app.Activity;
-import android.content.Intent;
-import android.security.KeyChain;
-import android.security.KeyChainAliasCallback;
-import android.security.KeyChainException;
-
-import androidx.annotation.NonNull;
-import androidx.security.SecureConfig;
-
-import java.net.Socket;
-import java.security.Principal;
-import java.security.PrivateKey;
-import java.security.cert.X509Certificate;
-
-import javax.net.ssl.X509KeyManager;
-
-/**
- * Class that helps generate and manage crypto keys
- */
-public class SecureKeyManager implements X509KeyManager, KeyChainAliasCallback {
-    private static final String TAG = "SecureKeyManager";
-
-    private final String mAlias;
-    private X509Certificate[] mCertChain;
-    private PrivateKey mPrivateKey;
-    private static Activity sActivity;
-    private SecureConfig mSecureConfig;
-
-    public static void setContext(@NonNull Activity activity) {
-        sActivity = activity;
-    }
-
-    public enum CertType {
-        X509(0),
-        PKCS12(1),
-        NOT_SUPPORTED(1000);
-
-        private final int mType;
-
-        CertType(int type) {
-            this.mType = type;
-        }
-
-        /**
-         * @return the type as an int
-         */
-        public int getType() {
-            return this.mType;
-        }
-
-        /**
-         * @param id the id of the cert type
-         * @return the cert type
-         */
-        @NonNull
-        public static CertType fromId(int id) {
-            switch (id) {
-                case 0:
-                    return X509;
-                case 1:
-                    return PKCS12;
-            }
-            return NOT_SUPPORTED;
-        }
-    }
-
-
-    /**
-     * @param alias the key alias
-     * @return the key manager
-     */
-    @NonNull
-    public static SecureKeyManager getDefault(@NonNull String alias) {
-        return getDefault(alias, SecureConfig.getDefault());
-    }
-
-    /**
-     * @param alias the key alias
-     * @param secureConfig the configuration
-     * @return the key manager
-     */
-    @NonNull
-    public static SecureKeyManager getDefault(@NonNull String alias,
-            @NonNull SecureConfig secureConfig) {
-        SecureKeyManager keyManager = new SecureKeyManager(alias, secureConfig);
-        try {
-            KeyChain.choosePrivateKeyAlias(sActivity, keyManager,
-                    secureConfig.getClientCertAlgorithms(),
-                    null, null, -1, alias);
-        } catch (Exception ex) {
-            ex.printStackTrace();
-        }
-        return keyManager;
-    }
-
-    /**
-     * @param certType cert mType to install
-     * @param certData the cert data in byte[] format
-     * @param keyAlias the alias of they key to use
-     * @param secureConfig the crypto config
-     * @return the secure key manager instance
-     */
-    @NonNull
-    public static SecureKeyManager installCertManually(@NonNull CertType certType,
-            @NonNull byte[] certData, @NonNull String keyAlias,
-            @NonNull SecureConfig secureConfig) {
-        SecureKeyManager keyManager = new SecureKeyManager(keyAlias, secureConfig);
-        Intent intent = KeyChain.createInstallIntent();
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        switch (certType) {
-            case X509:
-                intent.putExtra(KeyChain.EXTRA_CERTIFICATE, certData);
-                break;
-            case PKCS12:
-                intent.putExtra(KeyChain.EXTRA_PKCS12, certData);
-                break;
-            default:
-                throw new SecurityException("Cert mType not supported.");
-        }
-        sActivity.startActivity(intent);
-        return keyManager;
-    }
-
-    public SecureKeyManager(@NonNull String alias, @NonNull SecureConfig secureConfig) {
-        this.mAlias = alias;
-        this.mSecureConfig = secureConfig;
-    }
-
-    @Override
-    @NonNull
-    public String chooseClientAlias(@NonNull String[] arg0,
-            @NonNull Principal[] arg1, @NonNull Socket arg2) {
-        return mAlias;
-    }
-
-    @Override
-    @NonNull
-    public X509Certificate[] getCertificateChain(@NonNull String alias) {
-        if (this.mAlias.equals(alias)) return mCertChain;
-        return null;
-    }
-
-    public void setCertChain(@NonNull X509Certificate[] certChain) {
-        this.mCertChain = certChain;
-    }
-
-    @Override
-    @NonNull
-    public PrivateKey getPrivateKey(@NonNull String alias) {
-        if (this.mAlias.equals(alias)) return mPrivateKey;
-        return null;
-    }
-
-    public void setPrivateKey(@NonNull PrivateKey privateKey) {
-        this.mPrivateKey = privateKey;
-    }
-
-    @Override
-    @NonNull
-    public final String chooseServerAlias(@NonNull String keyType,
-            @NonNull Principal[] issuers, @NonNull Socket socket) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    @NonNull
-    public final String[] getClientAliases(@NonNull String keyType, @NonNull Principal[] issuers) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    @NonNull
-    public final String[] getServerAliases(@NonNull String keyType, @NonNull Principal[] issuers) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void alias(@NonNull String alias) {
-        try {
-            mCertChain = KeyChain.getCertificateChain(sActivity.getApplicationContext(), alias);
-            mPrivateKey = KeyChain.getPrivateKey(sActivity.getApplicationContext(), alias);
-            if (mCertChain == null || mPrivateKey == null) {
-                throw new SecurityException("Could not retrieve the cert chain and private key"
-                        + " from client cert.");
-            }
-            this.setCertChain(mCertChain);
-            this.setPrivateKey(mPrivateKey);
-        } catch (KeyChainException ex) {
-            throw new SecurityException("Could not retrieve the cert chain and private key from"
-                    + " client cert.");
-        } catch (InterruptedException ex) {
-            throw new SecurityException("Could not retrieve the cert chain and private key from"
-                    + " client cert.");
-        }
-    }
-}
diff --git a/security/crypto/src/main/java/androidx/security/net/SecureURL.java b/security/crypto/src/main/java/androidx/security/net/SecureURL.java
deleted file mode 100644
index f93c8a8..0000000
--- a/security/crypto/src/main/java/androidx/security/net/SecureURL.java
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package androidx.security.net;
-
-
-import android.annotation.TargetApi;
-import android.os.Build;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.security.SecureConfig;
-import androidx.security.config.TldConstants;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.security.GeneralSecurityException;
-import java.security.KeyStore;
-import java.security.cert.CertPath;
-import java.security.cert.CertPathValidator;
-import java.security.cert.CertPathValidatorException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateFactory;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.PKIXParameters;
-import java.security.cert.PKIXRevocationChecker;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Map;
-
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSocket;
-
-/**
- * A URL that provides TLS, certification validity checks, and TLD verification automatically.
- */
-@TargetApi(Build.VERSION_CODES.N)
-public class SecureURL {
-
-    private static final String TAG = "SecureURL";
-
-    private URL mUrl;
-    private SecureConfig mSecureConfig;
-    private String mClientCertAlias;
-
-    public SecureURL(@NonNull String spec)
-            throws MalformedURLException {
-        this(spec, null, SecureConfig.getDefault());
-    }
-
-    public SecureURL(@NonNull String spec, @NonNull String clientCertAlias)
-            throws MalformedURLException {
-        this(spec, clientCertAlias, SecureConfig.getDefault());
-    }
-
-    public SecureURL(@NonNull String spec, @NonNull String clientCertAlias,
-            @NonNull SecureConfig secureConfig)
-            throws MalformedURLException {
-        this.mUrl = new URL(addProtocol(spec));
-        this.mClientCertAlias = clientCertAlias;
-        this.mSecureConfig = secureConfig;
-    }
-
-
-    /**
-     * Gets the hostname used to construct the underlying URL.
-     *
-     * @return the hostname associated with the Url.
-     */
-    @NonNull
-    public String getHostname() {
-        return this.mUrl.getHost();
-    }
-
-    /**
-     * Gets the port used to construct the underlying URL.
-     *
-     * @return the port associated with the Url.
-     */
-    public int getPort() {
-        int port = this.mUrl.getPort();
-        if (port == -1) {
-            port = this.mUrl.getDefaultPort();
-        }
-        return port;
-    }
-
-    private String addProtocol(@NonNull String spec) {
-        if (!spec.toLowerCase().startsWith("http://")
-                && !spec.toLowerCase().startsWith("https://")) {
-            return "https://" + spec;
-        }
-        return spec;
-    }
-
-
-    /**
-     * Gets the client cert alias.
-     *
-     * @return The client cert alias.
-     */
-    @NonNull
-    public String getClientCertAlias() {
-        return this.mClientCertAlias;
-    }
-
-
-    /**
-     * Opens a connection using default certs with a custom SSLSocketFactory.
-     *
-     * @return the UrlConnection of the newly opened connection.
-     * @throws IOException
-     */
-    @NonNull
-    public URLConnection openConnection() throws IOException {
-        HttpsURLConnection urlConnection = (HttpsURLConnection) this.mUrl.openConnection();
-        urlConnection.setSSLSocketFactory(new ValidatableSSLSocketFactory(this));
-        return urlConnection;
-    }
-
-    /**
-     * Opens a connection using the provided trusted list of CAs.
-     *
-     * @param trustedCAs list of CAs to be trusted by this connection
-     * @return The opened connection
-     * @throws IOException
-     */
-    @NonNull
-    public URLConnection openUserTrustedCertConnection(
-            @NonNull Map<String, InputStream> trustedCAs)
-            throws IOException {
-        HttpsURLConnection urlConnection = (HttpsURLConnection) this.mUrl.openConnection();
-        urlConnection.setSSLSocketFactory(new ValidatableSSLSocketFactory(this,
-                trustedCAs, mSecureConfig));
-        return urlConnection;
-    }
-
-    /**
-     * Checks the hostname against an open SSLSocket connect to the hostname for validity for certs
-     * and hostname validity. Only used internally by ValidatableSSLSocket.
-     * <p>
-     * Example Code:
-     * SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault();
-     * SSLSocket socket = (SSLSocket) sf.createSocket("https://"+hostname, 443);
-     * socket.startHandshake();
-     * boolean valid = SecurityExt.isValid(hostname, socket);
-     * </p>
-     *
-     * @param hostname The host name to check
-     * @param socket   The SSLSocket that is open to the URL of the host to check
-     * @return true if the SSLSocket has a valid cert and if the hostname is valid, false otherwise.
-     */
-    boolean isValid(@NonNull String hostname, @NonNull SSLSocket socket) {
-        try {
-            Log.i(TAG, "Hostname verifier: " + HttpsURLConnection
-                    .getDefaultHostnameVerifier().verify(hostname, socket.getSession()));
-            Log.i(TAG, "isValid Peer Certs: "
-                    + isValid(Arrays.asList(socket.getSession().getPeerCertificates())));
-            return HttpsURLConnection.getDefaultHostnameVerifier()
-                    .verify(hostname, socket.getSession())
-                    && isValid(Arrays.asList(socket.getSession().getPeerCertificates()))
-                    && validTldWildcards(Arrays.asList(socket.getSession().getPeerCertificates()));
-        } catch (SSLPeerUnverifiedException e) {
-            Log.i(TAG, "Valid Check failed: " + e.getMessage());
-            e.printStackTrace();
-            return false;
-        }
-    }
-
-    /**
-     * Checks the HttpsUrlConnection certificates for validity.
-     * <p>
-     * Example Code:
-     * SecureURL mUrl = new SecureURL("https://" + host);
-     * conn = (HttpsURLConnection) mUrl.openConnection();
-     * boolean valid = SecurityExt.isValid(conn);
-     * </p>
-     *
-     * @param conn The connection to check the certificates of
-     * @return true if the certificates for the HttpsUrlConnection are valid, false otherwise
-     */
-    public boolean isValid(@NonNull HttpsURLConnection conn) {
-        try {
-            return isValid(Arrays.asList(conn.getServerCertificates()))
-                    && validTldWildcards(Arrays.asList(conn.getServerCertificates()));
-        } catch (SSLPeerUnverifiedException e) {
-            Log.i(TAG, "Valid Check failed: " + e.getMessage());
-            e.printStackTrace();
-            return false;
-        }
-    }
-
-
-    /**
-     * Internal method to check a list of certificates for validity.
-     *
-     * @param certs list of certs to check
-     * @return true if the certs are valid, false otherwise
-     */
-    private boolean isValid(@NonNull List<? extends Certificate> certs) {
-        try {
-            List<Certificate> leafCerts = new ArrayList<>();
-            for (Certificate cert : certs) {
-                if (!isRootCA(cert)) {
-                    leafCerts.add(cert);
-                }
-            }
-            CertPath path = CertificateFactory.getInstance(mSecureConfig.getCertPath())
-                    .generateCertPath(leafCerts);
-            KeyStore ks = KeyStore.getInstance(mSecureConfig.getAndroidCAStore());
-            try {
-                ks.load(null, null);
-            } catch (IOException e) {
-                e.printStackTrace();
-                throw new AssertionError(e);
-            }
-            CertPathValidator cpv = CertPathValidator.getInstance(mSecureConfig
-                    .getCertPathValidator());
-            PKIXParameters params = new PKIXParameters(ks);
-            PKIXRevocationChecker checker = (PKIXRevocationChecker) cpv.getRevocationChecker();
-            checker.setOptions(EnumSet.of(PKIXRevocationChecker.Option.NO_FALLBACK));
-            params.addCertPathChecker(checker);
-            cpv.validate(path, params);
-            return true;
-        } catch (CertPathValidatorException e) {
-            // If this message prints out "Unable to determine revocation status due to
-            // network error"
-            // Make sure your network security config allows for clear text access of the relevant
-            // OCSP mUrl.
-            e.printStackTrace();
-            return false;
-        } catch (GeneralSecurityException e) {
-            e.printStackTrace();
-            return false;
-        }
-    }
-
-    /**
-     * Internal method to check if a cert is a CA.
-     *
-     * @param cert The cert to check
-     * @return true if the cert is a RootCA, false otherwise
-     */
-    private boolean isRootCA(@NonNull Certificate cert) {
-        boolean rootCA = false;
-        if (cert instanceof X509Certificate) {
-            X509Certificate x509Certificate = (X509Certificate) cert;
-            if (x509Certificate.getSubjectDN().getName().equals(
-                    x509Certificate.getIssuerDN().getName())) {
-                rootCA = true;
-            }
-        }
-        return rootCA;
-    }
-
-
-    private boolean validTldWildcards(@NonNull List<? extends Certificate> certs) {
-        // For a more complete list https://publicsuffix.org/list/public_suffix_list.dat
-        for (Certificate cert : certs) {
-            if (cert instanceof X509Certificate) {
-                X509Certificate x509Cert = (X509Certificate) cert;
-                try {
-                    Collection<List<?>> subAltNames = x509Cert.getSubjectAlternativeNames();
-                    if (subAltNames != null) {
-                        List<String> dnsNames = new ArrayList<>();
-                        for (List<?> tldList : subAltNames) {
-                            if (tldList.size() >= 2) {
-                                dnsNames.add(tldList.get(1).toString().toUpperCase());
-                            }
-                        }
-                        // Populate DNS NAMES, make sure they are lower case
-                        for (String dnsName : dnsNames) {
-                            if (TldConstants.VALID_TLDS.contains(dnsName)) {
-                                Log.i(TAG, "FAILED WILDCARD TldConstants CHECK: " + dnsName);
-                                return false;
-                            }
-                        }
-                    }
-                } catch (CertificateParsingException ex) {
-                    Log.i(TAG, "Cert Parsing Issue: " + ex.getMessage());
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-}
diff --git a/security/crypto/src/main/java/androidx/security/net/ValidatableSSLSocket.java b/security/crypto/src/main/java/androidx/security/net/ValidatableSSLSocket.java
deleted file mode 100644
index e7be287..0000000
--- a/security/crypto/src/main/java/androidx/security/net/ValidatableSSLSocket.java
+++ /dev/null
@@ -1,397 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package androidx.security.net;
-
-import android.annotation.TargetApi;
-import android.os.Build;
-
-import androidx.annotation.RestrictTo;
-import androidx.security.SecureConfig;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.net.SocketException;
-import java.nio.channels.SocketChannel;
-
-import javax.net.ssl.HandshakeCompletedListener;
-import javax.net.ssl.SSLParameters;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSocket;
-
-/**
- * A custom implementation of SSLSocket which forces TLS, and handles automatically doing
- * certificate validity checks.
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-class ValidatableSSLSocket extends SSLSocket {
-
-    private static final String TAG = "ValidatableSSLSocket";
-
-    private SSLSocket mSslSocket;
-    private String mHostname;
-    private SecureURL mSecureURL;
-    private boolean mHandshakeStarted = false;
-    private SecureConfig mSecureConfig;
-
-    ValidatableSSLSocket(SecureURL secureURL, Socket sslSocket, SecureConfig secureConfig)
-            throws IOException {
-        this.mSecureURL = secureURL;
-        this.mHostname = secureURL.getHostname();
-        this.mSslSocket = (SSLSocket) sslSocket;
-        this.mSecureConfig = secureConfig;
-        setSecureCiphers();
-        isValid();
-    }
-
-    private void setSecureCiphers() {
-        if (mSecureConfig.getUseStrongSSLCiphersEnabled()) {
-            this.mSslSocket.setEnabledCipherSuites(mSecureConfig.getStrongSSLCiphers());
-        }
-    }
-
-    private void isValid() throws IOException {
-        startHandshake();
-        try {
-            if (!mSecureURL.isValid(this.mHostname, this.mSslSocket)) {
-                throw new IOException("Found invalid certificate");
-            }
-        } catch (IOException ex) {
-            ex.printStackTrace();
-            throw new IOException("Found invalid certificate");
-        }
-    }
-
-    @Override
-    public void startHandshake() throws IOException {
-        if (!mHandshakeStarted) {
-            mSslSocket.startHandshake();
-            mHandshakeStarted = true;
-        }
-    }
-
-    @Override
-    public String[] getSupportedCipherSuites() {
-        return mSslSocket.getSupportedCipherSuites();
-    }
-
-    @Override
-    public String[] getEnabledCipherSuites() {
-        return mSslSocket.getEnabledCipherSuites();
-    }
-
-    @Override
-    public void setEnabledCipherSuites(String[] suites) {
-        mSslSocket.setEnabledCipherSuites(suites);
-    }
-
-    @Override
-    public String[] getSupportedProtocols() {
-        return mSslSocket.getSupportedProtocols();
-    }
-
-    @Override
-    public String[] getEnabledProtocols() {
-        return mSslSocket.getEnabledProtocols();
-    }
-
-    @Override
-    public void setEnabledProtocols(String[] protocols) {
-        mSslSocket.setEnabledProtocols(protocols);
-    }
-
-    @Override
-    public SSLSession getSession() {
-        return mSslSocket.getSession();
-    }
-
-    @Override
-    public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
-        mSslSocket.addHandshakeCompletedListener(listener);
-    }
-
-    @Override
-    public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
-        mSslSocket.removeHandshakeCompletedListener(listener);
-    }
-
-    @Override
-    public void setUseClientMode(boolean mode) {
-        mSslSocket.setUseClientMode(mode);
-    }
-
-    @Override
-    public boolean getUseClientMode() {
-        return mSslSocket.getUseClientMode();
-    }
-
-    @Override
-    public void setNeedClientAuth(boolean need) {
-        mSslSocket.setNeedClientAuth(need);
-    }
-
-    @Override
-    public boolean getNeedClientAuth() {
-        return mSslSocket.getNeedClientAuth();
-    }
-
-    @Override
-    public void setWantClientAuth(boolean want) {
-        mSslSocket.setWantClientAuth(want);
-    }
-
-    @Override
-    public boolean getWantClientAuth() {
-        return mSslSocket.getWantClientAuth();
-    }
-
-    @Override
-    public void setEnableSessionCreation(boolean flag) {
-        mSslSocket.setEnableSessionCreation(flag);
-    }
-
-    @Override
-    public boolean getEnableSessionCreation() {
-        return mSslSocket.getEnableSessionCreation();
-    }
-
-    @Override
-    @TargetApi(Build.VERSION_CODES.N)
-    public SSLSession getHandshakeSession() {
-        return mSslSocket.getHandshakeSession();
-    }
-
-    @Override
-    public SSLParameters getSSLParameters() {
-        return mSslSocket.getSSLParameters();
-    }
-
-    @Override
-    public void setSSLParameters(SSLParameters params) {
-        mSslSocket.setSSLParameters(params);
-    }
-
-    @Override
-    public String toString() {
-        return mSslSocket.toString();
-    }
-
-    @Override
-    public void connect(SocketAddress endpoint) throws IOException {
-        mSslSocket.connect(endpoint);
-    }
-
-    @Override
-    public void connect(SocketAddress endpoint, int timeout) throws IOException {
-        mSslSocket.connect(endpoint, timeout);
-    }
-
-    @Override
-    public void bind(SocketAddress bindpoint) throws IOException {
-        mSslSocket.bind(bindpoint);
-    }
-
-    @Override
-    public InetAddress getInetAddress() {
-        return mSslSocket.getInetAddress();
-    }
-
-    @Override
-    public InetAddress getLocalAddress() {
-        return mSslSocket.getLocalAddress();
-    }
-
-    @Override
-    public int getPort() {
-        return mSslSocket.getPort();
-    }
-
-    @Override
-    public int getLocalPort() {
-        return mSslSocket.getLocalPort();
-    }
-
-    @Override
-    public SocketAddress getRemoteSocketAddress() {
-        return mSslSocket.getRemoteSocketAddress();
-    }
-
-    @Override
-    public SocketAddress getLocalSocketAddress() {
-        return mSslSocket.getLocalSocketAddress();
-    }
-
-    @Override
-    public SocketChannel getChannel() {
-        return mSslSocket.getChannel();
-    }
-
-    @Override
-    public InputStream getInputStream() throws IOException {
-        return mSslSocket.getInputStream();
-    }
-
-    @Override
-    public OutputStream getOutputStream() throws IOException {
-        return mSslSocket.getOutputStream();
-    }
-
-    @Override
-    public void setTcpNoDelay(boolean on) throws SocketException {
-        mSslSocket.setTcpNoDelay(on);
-    }
-
-    @Override
-    public boolean getTcpNoDelay() throws SocketException {
-        return mSslSocket.getTcpNoDelay();
-    }
-
-    @Override
-    public void setSoLinger(boolean on, int linger) throws SocketException {
-        mSslSocket.setSoLinger(on, linger);
-    }
-
-    @Override
-    public int getSoLinger() throws SocketException {
-        return mSslSocket.getSoLinger();
-    }
-
-    @Override
-    public void sendUrgentData(int data) throws IOException {
-        mSslSocket.sendUrgentData(data);
-    }
-
-    @Override
-    public void setOOBInline(boolean on) throws SocketException {
-        mSslSocket.setOOBInline(on);
-    }
-
-    @Override
-    public boolean getOOBInline() throws SocketException {
-        return mSslSocket.getOOBInline();
-    }
-
-    @Override
-    public synchronized void setSoTimeout(int timeout) throws SocketException {
-        mSslSocket.setSoTimeout(timeout);
-    }
-
-    @Override
-    public synchronized int getSoTimeout() throws SocketException {
-        return mSslSocket.getSoTimeout();
-    }
-
-    @Override
-    public synchronized void setSendBufferSize(int size) throws SocketException {
-        mSslSocket.setSendBufferSize(size);
-    }
-
-    @Override
-    public synchronized int getSendBufferSize() throws SocketException {
-        return mSslSocket.getSendBufferSize();
-    }
-
-    @Override
-    public synchronized void setReceiveBufferSize(int size) throws SocketException {
-        mSslSocket.setReceiveBufferSize(size);
-    }
-
-    @Override
-    public synchronized int getReceiveBufferSize() throws SocketException {
-        return mSslSocket.getReceiveBufferSize();
-    }
-
-    @Override
-    public void setKeepAlive(boolean on) throws SocketException {
-        mSslSocket.setKeepAlive(on);
-    }
-
-    @Override
-    public boolean getKeepAlive() throws SocketException {
-        return mSslSocket.getKeepAlive();
-    }
-
-    @Override
-    public void setTrafficClass(int tc) throws SocketException {
-        mSslSocket.setTrafficClass(tc);
-    }
-
-    @Override
-    public int getTrafficClass() throws SocketException {
-        return mSslSocket.getTrafficClass();
-    }
-
-    @Override
-    public void setReuseAddress(boolean on) throws SocketException {
-        mSslSocket.setReuseAddress(on);
-    }
-
-    @Override
-    public boolean getReuseAddress() throws SocketException {
-        return mSslSocket.getReuseAddress();
-    }
-
-    @Override
-    public synchronized void close() throws IOException {
-        mSslSocket.close();
-    }
-
-    @Override
-    public void shutdownInput() throws IOException {
-        mSslSocket.shutdownInput();
-    }
-
-    @Override
-    public void shutdownOutput() throws IOException {
-        mSslSocket.shutdownOutput();
-    }
-
-    @Override
-    public boolean isConnected() {
-        return mSslSocket.isConnected();
-    }
-
-    @Override
-    public boolean isBound() {
-        return mSslSocket.isBound();
-    }
-
-    @Override
-    public boolean isClosed() {
-        return mSslSocket.isClosed();
-    }
-
-    @Override
-    public boolean isInputShutdown() {
-        return mSslSocket.isInputShutdown();
-    }
-
-    @Override
-    public boolean isOutputShutdown() {
-        return mSslSocket.isOutputShutdown();
-    }
-
-    @Override
-    public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
-        mSslSocket.setPerformancePreferences(connectionTime, latency, bandwidth);
-    }
-}
diff --git a/security/crypto/src/main/java/androidx/security/net/ValidatableSSLSocketFactory.java b/security/crypto/src/main/java/androidx/security/net/ValidatableSSLSocketFactory.java
deleted file mode 100644
index 0c678b2..0000000
--- a/security/crypto/src/main/java/androidx/security/net/ValidatableSSLSocketFactory.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package androidx.security.net;
-
-import static androidx.security.SecureConfig.SSL_TLS;
-
-import androidx.annotation.RestrictTo;
-import androidx.security.SecureConfig;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.UnknownHostException;
-import java.security.GeneralSecurityException;
-import java.security.KeyStore;
-import java.security.SecureRandom;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateFactory;
-import java.util.Enumeration;
-import java.util.Map;
-
-import javax.net.ssl.KeyManager;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManagerFactory;
-
-/**
- * A custom implementation of SSLSocketFactory which handles the creation of custom SSLSockets
- * that handle extra functionality and do validity checking.
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-class ValidatableSSLSocketFactory extends SSLSocketFactory {
-
-    private static final String TAG = "ValidatableSSLSocketFactory";
-
-    private SSLSocketFactory mSslSocketFactory;
-    private SecureURL mSecureURL;
-    private Socket mSocket;
-    private SecureConfig mSecureConfig;
-
-    ValidatableSSLSocketFactory(SecureURL secureURL, SSLSocketFactory sslSocketFactory,
-            SecureConfig secureConfig) throws IOException {
-        this.mSecureURL = secureURL;
-        this.mSslSocketFactory = sslSocketFactory;
-        this.mSecureConfig = secureConfig;
-        this.mSocket = new ValidatableSSLSocket(secureURL,
-                mSslSocketFactory.createSocket(mSecureURL.getHostname(), mSecureURL.getPort()),
-                mSecureConfig);
-    }
-
-    ValidatableSSLSocketFactory(SecureURL secureURL, SSLSocketFactory sslSocketFactory)
-            throws IOException {
-        this(secureURL, sslSocketFactory, SecureConfig.getDefault());
-    }
-
-    ValidatableSSLSocketFactory(SecureURL secureURL) throws IOException {
-        this(secureURL, (SSLSocketFactory) SSLSocketFactory.getDefault(),
-                SecureConfig.getDefault());
-    }
-
-    ValidatableSSLSocketFactory(SecureURL secureURL,
-            Map<String, InputStream> trustedCAs, SecureConfig secureConfig) throws IOException {
-        this(secureURL, createUserTrustSSLSocketFactory(trustedCAs, secureConfig, secureURL),
-                secureConfig);
-    }
-
-    @Override
-    public String[] getDefaultCipherSuites() {
-        return mSslSocketFactory.getDefaultCipherSuites();
-    }
-
-    @Override
-    public String[] getSupportedCipherSuites() {
-        return mSslSocketFactory.getSupportedCipherSuites();
-    }
-
-    @Override
-    public Socket createSocket(Socket s, String host, int port, boolean autoClose)
-            throws IOException {
-        if (mSocket == null) {
-            mSocket = new ValidatableSSLSocket(
-                    mSecureURL, mSslSocketFactory.createSocket(s, host, port, autoClose),
-                    mSecureConfig);
-        }
-        return mSocket;
-    }
-
-    @Override
-    public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
-        if (mSocket == null) {
-            mSocket = new ValidatableSSLSocket(mSecureURL,
-                    mSslSocketFactory.createSocket(host, port),
-                    mSecureConfig);
-        }
-        return mSocket;
-    }
-
-    @Override
-    public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
-            throws IOException, UnknownHostException {
-        if (mSocket == null) {
-            mSocket = new ValidatableSSLSocket(
-                    mSecureURL, mSslSocketFactory.createSocket(host, port, localHost, localPort),
-                    mSecureConfig);
-        }
-        return mSocket;
-    }
-
-    @Override
-    public Socket createSocket(InetAddress host, int port) throws IOException {
-        if (mSocket == null) {
-            mSocket = new ValidatableSSLSocket(mSecureURL, mSslSocketFactory
-                    .createSocket(host, port),
-                    mSecureConfig);
-        }
-        return mSocket;
-    }
-
-    @Override
-    public Socket createSocket(InetAddress address, int port, InetAddress localAddress,
-            int localPort) throws IOException {
-        if (mSocket == null) {
-            mSocket = new ValidatableSSLSocket(
-                    mSecureURL, mSslSocketFactory.createSocket(address, port, localAddress,
-                    localPort),
-                    mSecureConfig);
-        }
-        return mSocket;
-    }
-
-    // TODO Evaluate the need for all of these options
-    private static SSLSocketFactory createUserTrustSSLSocketFactory(Map<String, InputStream>
-            trustAnchors, SecureConfig secureConfig, SecureURL secureURL) {
-        try {
-            TrustManagerFactory tmf = TrustManagerFactory.getInstance(
-                    TrustManagerFactory.getDefaultAlgorithm());
-            KeyStore clientStore = KeyStore.getInstance(secureConfig.getKeystoreType());
-            clientStore.load(null, null);
-
-            KeyStore trustStore = null;
-            switch (secureConfig.getTrustAnchorOptions()) {
-                case USER_ONLY:
-                case USER_SYSTEM:
-                case LIMITED_SYSTEM:
-                    trustStore = KeyStore.getInstance(secureConfig.getKeystoreType());
-                    trustStore.load(null, null);
-                    break;
-            }
-
-            switch (secureConfig.getTrustAnchorOptions()) {
-                case USER_SYSTEM:
-                    KeyStore caStore = KeyStore.getInstance(secureConfig.getAndroidCAStore());
-                    caStore.load(null, null);
-                    Enumeration<String> caAliases = caStore.aliases();
-                    while (caAliases.hasMoreElements()) {
-                        String alias = caAliases.nextElement();
-                        trustStore.setCertificateEntry(alias, caStore.getCertificate(alias));
-                    }
-                    break;
-                case USER_ONLY:
-                case LIMITED_SYSTEM:
-                    for (Map.Entry<String, InputStream> ca : trustAnchors.entrySet()) {
-                        CertificateFactory cf = CertificateFactory
-                                .getInstance(secureConfig.getCertPath());
-                        Certificate userCert = cf.generateCertificate(ca.getValue());
-                        trustStore.setCertificateEntry(ca.getKey(), userCert);
-                    }
-                    break;
-            }
-
-            tmf.init(trustStore);
-            SSLContext sslContext = SSLContext.getInstance(SSL_TLS);
-
-            KeyManager[] keyManagersArray = new KeyManager[1];
-            keyManagersArray[0] = SecureKeyManager.getDefault(
-                    secureURL.getClientCertAlias(), secureConfig);
-            sslContext.init(keyManagersArray, tmf.getTrustManagers(), new SecureRandom());
-            return sslContext.getSocketFactory();
-        } catch (GeneralSecurityException ex) {
-            throw new SecurityException("Issue creating User SSLSocketFactory.");
-        } catch (IOException ex) {
-            throw new SecurityException("Issue creating User SSLSocketFactory.");
-        }
-    }
-
-}
diff --git a/studiow b/studiow
index 87fe5ff..9d1c4c6 100755
--- a/studiow
+++ b/studiow
@@ -134,6 +134,16 @@
   fi
 }
 
+function ensureLocalPropertiesUpdated() {
+  testPath="${projectDir}/local.properties"
+  populaterCommand="./gradlew help"
+  if [ ! -f "${testPath}" ]; then
+    cd "$scriptDir"
+    echo "Creating $testPath by running '$populaterCommand'"
+    eval $populaterCommand
+  fi
+}
+
 function runStudioLinux() {
   studioPath="${studioUnzippedPath}/android-studio/bin/studio.sh"
   echo "$studioPath &"
@@ -161,6 +171,7 @@
 function main() {
   updateStudio
   checkLicenseAgreement
+  ensureLocalPropertiesUpdated
   runStudio
 }
 
diff --git a/transition/src/main/java/androidx/transition/Transition.java b/transition/src/main/java/androidx/transition/Transition.java
index 7fe90fe..21895ca 100644
--- a/transition/src/main/java/androidx/transition/Transition.java
+++ b/transition/src/main/java/androidx/transition/Transition.java
@@ -1987,16 +1987,21 @@
      */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     void forceToEnd(ViewGroup sceneRoot) {
-        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
+        final ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
         int numOldAnims = runningAnimators.size();
-        if (sceneRoot != null) {
-            WindowIdImpl windowId = ViewUtils.getWindowId(sceneRoot);
-            for (int i = numOldAnims - 1; i >= 0; i--) {
-                AnimationInfo info = runningAnimators.valueAt(i);
-                if (info.mView != null && windowId != null && windowId.equals(info.mWindowId)) {
-                    Animator anim = runningAnimators.keyAt(i);
-                    anim.end();
-                }
+        if (sceneRoot == null || numOldAnims == 0) {
+            return;
+        }
+
+        WindowIdImpl windowId = ViewUtils.getWindowId(sceneRoot);
+        final ArrayMap<Animator, AnimationInfo> oldAnimators = new ArrayMap(runningAnimators);
+        runningAnimators.clear();
+
+        for (int i = numOldAnims - 1; i >= 0; i--) {
+            AnimationInfo info = oldAnimators.valueAt(i);
+            if (info.mView != null && windowId != null && windowId.equals(info.mWindowId)) {
+                Animator anim = oldAnimators.keyAt(i);
+                anim.end();
             }
         }
     }
diff --git a/work/workmanager-gcm/src/androidTest/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcherTest.kt b/work/workmanager-gcm/src/androidTest/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcherTest.kt
index b04c246..1948994 100644
--- a/work/workmanager-gcm/src/androidTest/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcherTest.kt
+++ b/work/workmanager-gcm/src/androidTest/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcherTest.kt
@@ -22,7 +22,7 @@
 import androidx.arch.core.executor.TaskExecutor
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
+import androidx.test.filters.MediumTest
 import androidx.work.Configuration
 import androidx.work.impl.WorkManagerImpl
 import androidx.work.impl.utils.SynchronousExecutor
@@ -36,7 +36,7 @@
 import java.util.concurrent.Executor
 
 @RunWith(AndroidJUnit4::class)
-@SmallTest
+@MediumTest
 class WorkManagerGcmDispatcherTest {
     lateinit var mContext: Context
     lateinit var mExecutor: Executor