[go: nahoru, domu]

Merge "Bump paging to 3.0.0-beta02" into androidx-main
diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml
index addc9e4..3248074 100644
--- a/.github/workflows/presubmit.yml
+++ b/.github/workflows/presubmit.yml
@@ -74,7 +74,7 @@
         env:
           JAVA_HOME: ${{ steps.setup-java.outputs.path }}
         with:
-          arguments: -q :ktlintCheckFile ${{ steps.ktlint-file-args.outputs.ktlint-file-args }}
+          arguments: -q :ktlintCheckFile ${{ steps.ktlint-file-args.outputs.ktlint-file-args }} --stacktrace
           build-root-directory: activity
           configuration-cache-enabled: true
           dependencies-cache-enabled: true
@@ -123,7 +123,7 @@
         env:
           JAVA_HOME: ${{ steps.setup-java.outputs.path }}
         with:
-          arguments: buildOnServer
+          arguments: buildOnServer --stacktrace
           build-root-directory: ${{ env.project-root }}
           configuration-cache-enabled: true
           dependencies-cache-enabled: true
@@ -180,7 +180,7 @@
         env:
           JAVA_HOME: ${{ steps.setup-java.outputs.path }}
         with:
-          arguments: buildOnServer
+          arguments: buildOnServer --stacktrace
           build-root-directory: ${{ env.project-root }}
           configuration-cache-enabled: true
           dependencies-cache-enabled: true
@@ -237,7 +237,7 @@
         env:
           JAVA_HOME: ${{ steps.setup-java.outputs.path }}
         with:
-          arguments: buildOnServer
+          arguments: buildOnServer --stacktrace
           build-root-directory: ${{ env.project-root }}
           configuration-cache-enabled: true
           dependencies-cache-enabled: true
@@ -294,7 +294,7 @@
         env:
           JAVA_HOME: ${{ steps.setup-java.outputs.path }}
         with:
-          arguments: buildOnServer
+          arguments: buildOnServer --stacktrace
           build-root-directory: ${{ env.project-root }}
           configuration-cache-enabled: true
           dependencies-cache-enabled: true
@@ -351,7 +351,7 @@
         env:
           JAVA_HOME: ${{ steps.setup-java.outputs.path }}
         with:
-          arguments: buildOnServer
+          arguments: buildOnServer --stacktrace
           build-root-directory: ${{ env.project-root }}
           configuration-cache-enabled: true
           dependencies-cache-enabled: true
@@ -408,7 +408,7 @@
         env:
           JAVA_HOME: ${{ steps.setup-java.outputs.path }}
         with:
-          arguments: buildOnServer
+          arguments: buildOnServer --stacktrace
           build-root-directory: ${{ env.project-root }}
           configuration-cache-enabled: true
           dependencies-cache-enabled: true
@@ -465,7 +465,7 @@
         env:
           JAVA_HOME: ${{ steps.setup-java.outputs.path }}
         with:
-          arguments: buildOnServer
+          arguments: buildOnServer --stacktrace
           build-root-directory: ${{ env.project-root }}
           configuration-cache-enabled: true
           dependencies-cache-enabled: true
@@ -522,7 +522,7 @@
         env:
           JAVA_HOME: ${{ steps.setup-java.outputs.path }}
         with:
-          arguments: buildOnServer
+          arguments: buildOnServer --stacktrace
           build-root-directory: ${{ env.project-root }}
           configuration-cache-enabled: true
           dependencies-cache-enabled: true
@@ -580,7 +580,7 @@
         env:
           JAVA_HOME: ${{ steps.setup-java.outputs.path }}
         with:
-          arguments: buildOnServer
+          arguments: buildOnServer --stacktrace
           build-root-directory: ${{ env.project-root }}
           configuration-cache-enabled: true
           dependencies-cache-enabled: true
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/SuggestionsAdapter.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/SuggestionsAdapter.java
index 87e1061..acba2f1 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/SuggestionsAdapter.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/SuggestionsAdapter.java
@@ -16,7 +16,6 @@
  * limitations under the License.
  */
 
-import android.annotation.SuppressLint;
 import android.app.SearchManager;
 import android.app.SearchableInfo;
 import android.content.ComponentName;
@@ -56,7 +55,6 @@
 /**
  * Provides the contents for the suggestion drop-down list.in {@link SearchView}.
  */
-@SuppressLint("RestrictedAPI") // Temporary until we have correct restriction scopes for 1.0
 class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListener {
 
     private static final boolean DBG = false;
@@ -88,10 +86,11 @@
     private int mIconName2Col = INVALID_INDEX;
     private int mFlagsCol = INVALID_INDEX;
 
-    // private final Runnable mStartSpinnerRunnable;
-    // private final Runnable mStopSpinnerRunnable;
+    @SuppressWarnings("deprecation")
     public SuggestionsAdapter(Context context, SearchView searchView, SearchableInfo searchable,
             WeakHashMap<String, Drawable.ConstantState> outsideDrawablesCache) {
+        // Auto-requery is discouraged, as it results in Cursor queries being performed on the
+        // application's UI thread.
         super(context, searchView.getSuggestionRowLayout(), null /* no initial cursor */,
                 true /* auto-requery */);
         mSearchView = searchView;
@@ -335,8 +334,9 @@
         if (mUrlColor == null) {
             // Lazily get the URL color from the current theme.
             TypedValue colorValue = new TypedValue();
-            mContext.getTheme().resolveAttribute(R.attr.textColorSearchUrl, colorValue, true);
-            mUrlColor = mContext.getResources().getColorStateList(colorValue.resourceId);
+            mProviderContext.getTheme().resolveAttribute(
+                    R.attr.textColorSearchUrl, colorValue, true);
+            mUrlColor = mProviderContext.getResources().getColorStateList(colorValue.resourceId);
         }
 
         SpannableString text = new SpannableString(url);
@@ -450,7 +450,7 @@
         } catch (RuntimeException e) {
             Log.w(LOG_TAG, "Search suggestions cursor threw exception.", e);
             // Put exception string in item title
-            View v = newView(mContext, mCursor, parent);
+            View v = newView(mProviderContext, getCursor(), parent);
             if (v != null) {
                 ChildViewCache views = (ChildViewCache) v.getTag();
                 TextView tv = views.mText1;
@@ -473,7 +473,7 @@
         } catch (RuntimeException e) {
             Log.w(LOG_TAG, "Search suggestions cursor threw exception.", e);
             // Put exception string in item title
-            final View v = newDropDownView(mContext, mCursor, parent);
+            final View v = newDropDownView(mProviderContext, getCursor(), parent);
             if (v != null) {
                 final ChildViewCache views = (ChildViewCache) v.getTag();
                 final TextView tv = views.mText1;
@@ -607,7 +607,7 @@
         }
 
         // Fall back to a default icon
-        return mContext.getPackageManager().getDefaultActivityIcon();
+        return mProviderContext.getPackageManager().getDefaultActivityIcon();
     }
 
     /**
@@ -642,7 +642,7 @@
      *         have an icon set.
      */
     private Drawable getActivityIcon(ComponentName component) {
-        PackageManager pm = mContext.getPackageManager();
+        PackageManager pm = mProviderContext.getPackageManager();
         final ActivityInfo activityInfo;
         try {
             activityInfo = pm.getActivityInfo(component, PackageManager.GET_META_DATA);
@@ -700,7 +700,7 @@
             throw new FileNotFoundException("No authority: " + uri);
         } else {
             try {
-                r = mContext.getPackageManager().getResourcesForApplication(authority);
+                r = mProviderContext.getPackageManager().getResourcesForApplication(authority);
             } catch (NameNotFoundException ex) {
                 throw new FileNotFoundException("No package found for authority: " + uri);
             }
@@ -773,6 +773,6 @@
         Uri uri = uriBuilder.build();
 
         // finally, make the query
-        return mContext.getContentResolver().query(uri, null, selection, selArgs, null);
+        return mProviderContext.getContentResolver().query(uri, null, selection, selArgs, null);
     }
 }
\ No newline at end of file
diff --git a/buildSrc/build_dependencies.gradle b/buildSrc/build_dependencies.gradle
index 647fe0a..cbfcbbe 100644
--- a/buildSrc/build_dependencies.gradle
+++ b/buildSrc/build_dependencies.gradle
@@ -22,7 +22,7 @@
 build_versions.agp = build_versions.studio["agp"]
 build_versions.lint = build_versions.studio["lint"]
 
-build_versions.kotlin = "1.4.30"
+build_versions.kotlin = "1.4.31"
 build_versions.kotlin_coroutines = "1.4.1"
 build_versions.ksp = "1.4.30-1.0.0-alpha03"
 
diff --git a/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt b/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
index afb2421..deca706 100644
--- a/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
@@ -182,8 +182,7 @@
         name = ERROR_PRONE_TASK,
         >
             val compileTask = compileTaskProvider.get()
-            it.classpath = compileTask.classpath +
-                project.files("${project.buildDir}/classes/kotlin/main")
+            it.classpath = compileTask.classpath
 
             it.source = compileTask.source
             it.destinationDir = file(buildDir.resolve("errorProne"))
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index 828d2e8..79f5afb 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -101,7 +101,7 @@
     val RECYCLERVIEW_SELECTION = Version("2.0.0-alpha01")
     val REMOTECALLBACK = Version("1.0.0-alpha02")
     val RESOURCEINSPECTION = Version("1.0.0-alpha01")
-    val ROOM = Version("2.3.0-rc01")
+    val ROOM = Version("2.3.0-beta03")
     val SAVEDSTATE = Version("1.2.0-alpha01")
     val SECURITY = Version("1.1.0-alpha03")
     val SECURITY_APP_AUTHENTICATOR = Version("1.0.0-alpha01")
@@ -131,18 +131,18 @@
     val VIEWPAGER = Version("1.1.0-alpha01")
     val VIEWPAGER2 = Version("1.1.0-alpha02")
     val WEAR = Version("1.2.0-alpha07")
-    val WEAR_COMPLICATIONS = Version("1.0.0-alpha08")
+    val WEAR_COMPLICATIONS = Version("1.0.0-alpha09")
     val WEAR_INPUT = Version("1.1.0-alpha02")
     val WEAR_ONGOING = Version("1.0.0-alpha03")
     val WEAR_PHONE_INTERACTIONS = Version("1.0.0-alpha03")
     val WEAR_REMOTE_INTERACTIONS = Version("1.0.0-alpha02")
     val WEAR_TILES = Version("1.0.0-alpha01")
     val WEAR_TILES_DATA = WEAR_TILES
-    val WEAR_WATCHFACE = Version("1.0.0-alpha08")
-    val WEAR_WATCHFACE_CLIENT = Version("1.0.0-alpha08")
-    val WEAR_WATCHFACE_DATA = Version("1.0.0-alpha08")
-    val WEAR_WATCHFACE_EDITOR = Version("1.0.0-alpha08")
-    val WEAR_WATCHFACE_STYLE = Version("1.0.0-alpha08")
+    val WEAR_WATCHFACE = Version("1.0.0-alpha09")
+    val WEAR_WATCHFACE_CLIENT = Version("1.0.0-alpha09")
+    val WEAR_WATCHFACE_DATA = Version("1.0.0-alpha09")
+    val WEAR_WATCHFACE_EDITOR = Version("1.0.0-alpha09")
+    val WEAR_WATCHFACE_STYLE = Version("1.0.0-alpha09")
     val WEBKIT = Version("1.5.0-alpha01")
     val WINDOW = Version("1.0.0-alpha04")
     val WINDOW_EXTENSIONS = Version("1.0.0-alpha01")
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index 90ec734..6d0f116 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -89,7 +89,7 @@
 val KSP_VERSION get() = kspVersion
 val KOTLIN_KSP_API get() = "com.google.devtools.ksp:symbol-processing-api:$KSP_VERSION"
 val KOTLIN_KSP get() = "com.google.devtools.ksp:symbol-processing:$KSP_VERSION"
-const val KOTLIN_GRADLE_PLUGIN = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.30"
+const val KOTLIN_GRADLE_PLUGIN = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.31"
 
 const val KOTLIN_METADATA = "me.eugeniomarletti.kotlin.metadata:kotlin-metadata:1.4.0"
 const val KOTLIN_METADATA_JVM = "org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0"
@@ -104,6 +104,7 @@
 const val MULTIDEX = "androidx.multidex:multidex:2.0.1"
 const val NULLAWAY = "com.uber.nullaway:nullaway:0.3.7"
 const val PLAY_CORE = "com.google.android.play:core:1.9.1"
+const val PLAY_SERVICES_BASE = "com.google.android.gms:play-services-base:17.0.0"
 const val REACTIVE_STREAMS = "org.reactivestreams:reactive-streams:1.0.0"
 const val RX_JAVA = "io.reactivex.rxjava2:rxjava:2.2.9"
 const val RX_JAVA3 = "io.reactivex.rxjava3:rxjava:3.0.0"
diff --git a/car/app/app/src/main/java/androidx/car/app/serialization/Bundler.java b/car/app/app/src/main/java/androidx/car/app/serialization/Bundler.java
index ea621d8..16d514c 100644
--- a/car/app/app/src/main/java/androidx/car/app/serialization/Bundler.java
+++ b/car/app/app/src/main/java/androidx/car/app/serialization/Bundler.java
@@ -65,6 +65,7 @@
  * <li>{@link String}
  * <li>{@link Parcelable} - parcelables will serialize using {@link Parcelable} logic.
  * <li>{@link IInterface}
+ * <li>{@link IBinder}
  * <li>{@link Map} - maps will be deserialize into {@link HashMap}s, and need to hold objects that
  * are also serializable as per this list.
  * <li>{@link List} - lists will deserialize into {@link ArrayList}s, and need to hold objects that
@@ -99,7 +100,7 @@
     private static final String TAG_2 = "tag_2";
 
     private static final int PRIMITIVE = 0;
-    private static final int BINDER = 1;
+    private static final int IINTERFACE = 1;
     private static final int MAP = 2;
     private static final int SET = 3;
     private static final int LIST = 4;
@@ -107,6 +108,7 @@
     private static final int IMAGE = 6;
     private static final int ENUM = 7;
     private static final int CLASS = 8;
+    private static final int IBINDER = 9;
 
     /**
      * Serializes an object into a {@link Bundle} for sending over IPC.
@@ -144,7 +146,9 @@
             } else if (isPrimitiveType(obj) || obj instanceof Parcelable) {
                 return serializePrimitive(obj, trace);
             } else if (obj instanceof IInterface) {
-                return serializeBinder((IInterface) obj);
+                return serializeIInterface((IInterface) obj);
+            } else if (obj instanceof IBinder) {
+                return serializeIBinder((IBinder) obj);
             } else if (obj instanceof Map) {
                 return serializeMap((Map<Object, Object>) obj, trace);
             } else if (obj instanceof List) {
@@ -200,8 +204,10 @@
             switch (classType) {
                 case PRIMITIVE:
                     return deserializePrimitive(bundle, trace);
-                case BINDER:
-                    return deserializeBinder(bundle, trace);
+                case IINTERFACE:
+                    return deserializeIInterface(bundle, trace);
+                case IBINDER:
+                    return deserializeIBinder(bundle, trace);
                 case MAP:
                     return deserializeMap(bundle, trace);
                 case SET:
@@ -256,18 +262,27 @@
         return bundle;
     }
 
-    private static Bundle serializeBinder(IInterface iInterface) {
+    private static Bundle serializeIInterface(IInterface iInterface) {
         Bundle bundle = new Bundle(3);
 
         String className = iInterface.getClass().getName();
 
-        bundle.putInt(TAG_CLASS_TYPE, BINDER);
+        bundle.putInt(TAG_CLASS_TYPE, IINTERFACE);
         bundle.putBinder(TAG_VALUE, iInterface.asBinder());
         bundle.putString(TAG_CLASS_NAME, className);
 
         return bundle;
     }
 
+    private static Bundle serializeIBinder(IBinder binder) {
+        Bundle bundle = new Bundle(2);
+
+        bundle.putInt(TAG_CLASS_TYPE, IBINDER);
+        bundle.putBinder(TAG_VALUE, binder);
+
+        return bundle;
+    }
+
     private static Bundle serializeMap(Map<Object, Object> map, Trace trace)
             throws BundlerException {
         Bundle bundle = new Bundle(2);
@@ -392,7 +407,8 @@
     }
 
     @SuppressWarnings("argument.type.incompatible") // so that we can invoke static asInterface
-    private static Object deserializeBinder(Bundle bundle, Trace trace) throws BundlerException {
+    private static Object deserializeIInterface(Bundle bundle, Trace trace)
+            throws BundlerException {
         IBinder binder = bundle.getBinder(TAG_VALUE);
         if (binder == null) {
             throw new TracedBundlerException("Bundle is missing the binder", trace);
@@ -426,6 +442,15 @@
         }
     }
 
+    private static Object deserializeIBinder(Bundle bundle, Trace trace) throws BundlerException {
+        IBinder binder = bundle.getBinder(TAG_VALUE);
+        if (binder == null) {
+            throw new TracedBundlerException("Bundle is missing the binder", trace);
+        }
+
+        return binder;
+    }
+
     @SuppressWarnings("argument.type.incompatible") // so that we can put null values in the map
     private static Object deserializeMap(Bundle bundle, Trace trace) throws BundlerException {
         ArrayList<Parcelable> list = bundle.getParcelableArrayList(TAG_VALUE);
@@ -641,7 +666,8 @@
     private static Map<Integer, String> initBundledTypeNames() {
         ArrayMap<Integer, String> map = new ArrayMap<>();
         map.put(PRIMITIVE, "primitive");
-        map.put(BINDER, "binder");
+        map.put(IINTERFACE, "iInterface");
+        map.put(IBINDER, "iBinder");
         map.put(MAP, "map");
         map.put(SET, "set");
         map.put(LIST, "list");
diff --git a/car/app/app/src/test/java/androidx/car/app/serialization/BundlerTest.java b/car/app/app/src/test/java/androidx/car/app/serialization/BundlerTest.java
index 5dc3718..8e10a451 100644
--- a/car/app/app/src/test/java/androidx/car/app/serialization/BundlerTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/serialization/BundlerTest.java
@@ -25,10 +25,11 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
+import android.os.Binder;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.RemoteException;
 
 import androidx.annotation.Nullable;
 import androidx.car.app.OnDoneCallback;
@@ -158,7 +159,15 @@
     }
 
     @Test
-    public void binderSerialization() throws BundlerException, RemoteException {
+    public void iBinderSerialization() throws BundlerException {
+        IBinder binder = new Binder();
+        Bundle bundle = Bundler.toBundle(binder);
+
+        assertThat(Bundler.fromBundle(bundle)).isEqualTo(binder);
+    }
+
+    @Test
+    public void iInterfaceSerialization() throws BundlerException {
         OnClickListener clickListener = mock(OnClickListener.class);
 
         ItemList itemList =
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
index c9e61d1..2a24198 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
@@ -129,7 +129,7 @@
             project: Project,
             configuration: CompilerConfiguration
         ) {
-            val KOTLIN_VERSION_EXPECTATION = "1.4.30"
+            val KOTLIN_VERSION_EXPECTATION = "1.4.31"
             KotlinCompilerVersion.getVersion()?.let { version ->
                 val suppressKotlinVersionCheck = configuration.get(
                     ComposeConfiguration.SUPPRESS_KOTLIN_VERSION_COMPATIBILITY_CHECK,
diff --git a/compose/runtime/runtime/compose-runtime-benchmark/build.gradle b/compose/runtime/runtime/compose-runtime-benchmark/build.gradle
index 698b80d..9d8fb3c 100644
--- a/compose/runtime/runtime/compose-runtime-benchmark/build.gradle
+++ b/compose/runtime/runtime/compose-runtime-benchmark/build.gradle
@@ -51,6 +51,7 @@
     androidTestImplementation(project(":compose:material:material"))
     androidTestImplementation(project(":compose:runtime:runtime"))
     androidTestImplementation(project(":compose:ui:ui-text"))
+    androidTestImplementation(project(":compose:ui:ui-util"))
 
     androidTestImplementation(JUNIT)
     androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
index 2c51dfd..f351dd3 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
@@ -22,6 +22,7 @@
 import androidx.compose.ui.inspection.compose.convertParameters
 import androidx.compose.ui.inspection.compose.flatten
 import androidx.compose.ui.inspection.framework.flatten
+import androidx.compose.ui.inspection.inspector.InspectorNode
 import androidx.compose.ui.inspection.inspector.LayoutInspectorTree
 import androidx.compose.ui.inspection.proto.StringTable
 import androidx.compose.ui.inspection.proto.convertAll
@@ -58,8 +59,25 @@
     private val environment: InspectorEnvironment
 ) : Inspector(connection) {
 
+    /** Cache data which allows us to reuse previously queried inspector nodes */
+    private class CacheData(
+        /** If the cached data includes system nodes or not */
+        val systemComposablesSkipped: Boolean,
+        /** The cached nodes themselves as a map from node id to InspectorNode */
+        val nodes: Map<Long, InspectorNode>,
+    )
+
     private val layoutInspectorTree = LayoutInspectorTree()
 
+    // Sidestep threading concerns by only ever accessing cachedNodes on the inspector thread
+    private val inspectorThread = Thread.currentThread()
+    private val _cachedNodes = mutableMapOf<Long, CacheData>()
+    private val cachedNodes: MutableMap<Long, CacheData>
+        get() {
+            check(Thread.currentThread() == inspectorThread)
+            return _cachedNodes
+        }
+
     override fun onReceiveCommand(data: ByteArray, callback: CommandCallback) {
         val command = Command.parseFrom(data)
         when (command.specializedCase) {
@@ -82,15 +100,19 @@
     ) {
         ThreadUtils.runOnMainThread {
             val stringTable = StringTable()
-            val composeRoots =
-                getComposableRoots(
-                    getComposablesCommand.rootViewId,
-                    getComposablesCommand.skipSystemComposables
-                )
-                    .map { it.createComposableRoot(stringTable) }
-                    .toList()
+            val composeViews = getAndroidComposeViews(
+                getComposablesCommand.rootViewId,
+                getComposablesCommand.skipSystemComposables
+            )
+
+            val composeRoots = composeViews.map { it.createComposableRoot(stringTable) }
 
             environment.executors().primary().execute {
+                cachedNodes[getComposablesCommand.rootViewId] = CacheData(
+                    getComposablesCommand.skipSystemComposables,
+                    composeViews.toInspectorNodes().associateBy { it.id }
+                )
+
                 callback.reply {
                     getComposablesResponse = GetComposablesResponse.newBuilder().apply {
                         addAllStrings(stringTable.toStringEntries())
@@ -105,31 +127,26 @@
         getParametersCommand: GetParametersCommand,
         callback: CommandCallback
     ) {
-        ThreadUtils.runOnMainThread {
-            val foundComposable = getComposableRoots(
+        val foundComposable =
+            getComposableNodes(
                 getParametersCommand.rootViewId,
                 getParametersCommand.skipSystemComposables
-            )
-                .flatMap { it.inspectorNodes }
-                .flatMap { it.flatten() }
-                .firstOrNull { it.id == getParametersCommand.composableId }
+            )[getParametersCommand.composableId]
 
-            environment.executors().primary().execute {
-                callback.reply {
-                    getParametersResponse = if (foundComposable != null) {
-                        val stringTable = StringTable()
-                        val parameters = foundComposable.convertParameters().convertAll(stringTable)
-                        GetParametersResponse.newBuilder().apply {
-                            parameterGroup = ParameterGroup.newBuilder().apply {
-                                composableId = getParametersCommand.composableId
-                                addAllParameter(parameters)
-                            }.build()
-                            addAllStrings(stringTable.toStringEntries())
-                        }.build()
-                    } else {
-                        GetParametersResponse.getDefaultInstance()
-                    }
-                }
+        callback.reply {
+            getParametersResponse = if (foundComposable != null) {
+                val stringTable = StringTable()
+                val parameters =
+                    foundComposable.convertParameters(layoutInspectorTree).convertAll(stringTable)
+                GetParametersResponse.newBuilder().apply {
+                    parameterGroup = ParameterGroup.newBuilder().apply {
+                        composableId = getParametersCommand.composableId
+                        addAllParameter(parameters)
+                    }.build()
+                    addAllStrings(stringTable.toStringEntries())
+                }.build()
+            } else {
+                GetParametersResponse.getDefaultInstance()
             }
         }
     }
@@ -138,58 +155,80 @@
         getAllParametersCommand: GetAllParametersCommand,
         callback: CommandCallback
     ) {
-        ThreadUtils.runOnMainThread {
-
-            val allComposables = getComposableRoots(
+        val allComposables =
+            getComposableNodes(
                 getAllParametersCommand.rootViewId,
                 getAllParametersCommand.skipSystemComposables
-            )
-                .flatMap { it.inspectorNodes }
-                .flatMap { it.flatten() }
-                .toList()
+            ).values
 
-            environment.executors().primary().execute {
-                callback.reply {
-                    val stringTable = StringTable()
-                    val parameterGroups = allComposables.map { composable ->
-                        val parameters = composable.convertParameters().convertAll(stringTable)
-                        ParameterGroup.newBuilder().apply {
-                            composableId = composable.id
-                            addAllParameter(parameters)
-                        }.build()
-                    }
-
-                    getAllParametersResponse = GetAllParametersResponse.newBuilder().apply {
-                        rootViewId = getAllParametersCommand.rootViewId
-                        addAllParameterGroups(parameterGroups)
-                        addAllStrings(stringTable.toStringEntries())
-                    }.build()
-                }
+        callback.reply {
+            val stringTable = StringTable()
+            val parameterGroups = allComposables.map { composable ->
+                val parameters =
+                    composable.convertParameters(layoutInspectorTree).convertAll(stringTable)
+                ParameterGroup.newBuilder().apply {
+                    composableId = composable.id
+                    addAllParameter(parameters)
+                }.build()
             }
+
+            getAllParametersResponse = GetAllParametersResponse.newBuilder().apply {
+                rootViewId = getAllParametersCommand.rootViewId
+                addAllParameterGroups(parameterGroups)
+                addAllStrings(stringTable.toStringEntries())
+            }.build()
         }
     }
 
-    private fun getComposableRoots(
+    /**
+     * Get all [InspectorNode]s found under the layout tree rooted by [rootViewId]. They will be
+     * mapped with their ID as the key.
+     *
+     * This will return cached data if possible, but may request new data otherwise, blocking the
+     * current thread potentially.
+     */
+    private fun getComposableNodes(
         rootViewId: Long,
         skipSystemComposables: Boolean
-    ): Sequence<AndroidComposeViewWrapper> {
+    ): Map<Long, InspectorNode> {
+        cachedNodes[rootViewId]?.let { cacheData ->
+            if (cacheData.systemComposablesSkipped == skipSystemComposables) {
+                return cacheData.nodes
+            }
+        }
+
+        return ThreadUtils.runOnMainThread {
+            getAndroidComposeViews(rootViewId, skipSystemComposables)
+                .toInspectorNodes()
+                .associateBy { it.id }
+        }.get()
+    }
+
+    /**
+     * Get all AndroidComposeView instances found within the layout tree rooted by [rootViewId].
+     */
+    private fun getAndroidComposeViews(
+        rootViewId: Long,
+        skipSystemComposables: Boolean
+    ): List<AndroidComposeViewWrapper> {
         ThreadUtils.assertOnMainThread()
 
+        layoutInspectorTree.resetGeneratedId()
         return WindowInspector.getGlobalWindowViews()
             .asSequence()
-            .filter { view -> view.visibility == View.VISIBLE && view.isAttachedToWindow }
-            // Note: When querying root views, there should only be 0 or 1 match here, but it's
-            // easier to handle this as a general filter, to avoid ? operators all the rest of
-            // the way down
-            .filter { it.uniqueDrawingId == rootViewId }
+            .filter { root ->
+                root.visibility == View.VISIBLE && root.isAttachedToWindow &&
+                    root.uniqueDrawingId == rootViewId
+            }
             .flatMap { it.flatten() }
-            .mapNotNull {
+            .mapNotNull { view ->
                 AndroidComposeViewWrapper.tryCreateFor(
                     layoutInspectorTree,
-                    it,
+                    view,
                     skipSystemComposables
                 )
             }
+            .toList()
     }
 }
 
@@ -198,3 +237,14 @@
     response.initResponse()
     reply(response.build().toByteArray())
 }
+
+/**
+ * Convert an [AndroidComposeViewWrapper] to a flat list of all inspector nodes (including children)
+ * that live underneath it.
+ */
+private fun List<AndroidComposeViewWrapper>.toInspectorNodes(): List<InspectorNode> {
+    return this
+        .flatMap { it.inspectorNodes }
+        .flatMap { it.flatten() }
+        .toList()
+}
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/ComposeExtensions.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/ComposeExtensions.kt
index d3b309b..a655403 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/ComposeExtensions.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/ComposeExtensions.kt
@@ -27,17 +27,15 @@
  * This method can take a long time, especially the first time, and should be called off the main
  * thread.
  */
-fun InspectorNode.convertParameters(): List<NodeParameter> {
+fun InspectorNode.convertParameters(layoutInspectorTree: LayoutInspectorTree): List<NodeParameter> {
     ThreadUtils.assertOffMainThread()
-    return LayoutInspectorTree().convertParameters(this)
+    return layoutInspectorTree.convertParameters(this)
 }
 
 /**
  * Flatten an inspector node into a list containing itself and all its children.
  */
 fun InspectorNode.flatten(): Sequence<InspectorNode> {
-    ThreadUtils.assertOnMainThread()
-
     val remaining = mutableListOf(this)
     return generateSequence {
         val next = remaining.removeLastOrNull()
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/util/ThreadUtils.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/util/ThreadUtils.kt
index fc96122..d088b13 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/util/ThreadUtils.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/util/ThreadUtils.kt
@@ -18,6 +18,8 @@
 
 import android.os.Handler
 import android.os.Looper
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.Future
 
 object ThreadUtils {
     fun assertOnMainThread() {
@@ -32,7 +34,21 @@
         }
     }
 
-    fun runOnMainThread(block: () -> Unit) {
-        Handler.createAsync(Looper.getMainLooper()).post(block)
+    /**
+     * Run some logic on the main thread, returning a future that will contain any data computed
+     * by and returned from the block.
+     *
+     * If this method is called from the main thread, it will run immediately.
+     */
+    fun <T> runOnMainThread(block: () -> T): Future<T> {
+        return if (!Looper.getMainLooper().isCurrentThread) {
+            val future = CompletableFuture<T>()
+            Handler.createAsync(Looper.getMainLooper()).post {
+                future.complete(block())
+            }
+            future
+        } else {
+            CompletableFuture.completedFuture(block())
+        }
     }
 }
diff --git a/compose/ui/ui-test-junit4/build.gradle b/compose/ui/ui-test-junit4/build.gradle
index d9596ce..aa26329 100644
--- a/compose/ui/ui-test-junit4/build.gradle
+++ b/compose/ui/ui-test-junit4/build.gradle
@@ -37,19 +37,25 @@
         api("androidx.activity:activity:1.2.0")
         api(ANDROIDX_TEST_EXT_JUNIT)
         api(JUNIT)
+        api(KOTLIN_STDLIB)
+        api(KOTLIN_STDLIB_COMMON)
 
-        implementation(KOTLIN_STDLIB)
-        implementation(ANDROIDX_TEST_RULES)
-        implementation(ANDROIDX_TEST_RUNNER)
+        implementation(project(":compose:animation:animation-core"))
+        implementation(project(":compose:runtime:runtime-saveable"))
+        implementation("androidx.annotation:annotation:1.1.0")
+        implementation("androidx.lifecycle:lifecycle-common:2.3.0")
+        implementation("androidx.lifecycle:lifecycle-runtime:2.3.0")
+        implementation(ANDROIDX_TEST_CORE)
+        implementation(ANDROIDX_TEST_MONITOR)
         implementation(ESPRESSO_CORE)
-        implementation(project(":compose:animation:animation"))
-        implementation(project(":compose:foundation:foundation"))
-
-        testImplementation(TRUTH)
+        implementation(ESPRESSO_IDLING_RESOURCE)
+        implementation(KOTLIN_COROUTINES_CORE)
+        implementation(KOTLIN_COROUTINES_TEST)
 
         androidTestImplementation(project(":compose:test-utils"))
         androidTestImplementation(project(":compose:material:material"))
-        androidTestImplementation(project(":compose:foundation:foundation"))
+        androidTestImplementation(ANDROIDX_TEST_RULES)
+        androidTestImplementation(ANDROIDX_TEST_RUNNER)
         androidTestImplementation(TRUTH)
         androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
         androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
@@ -67,38 +73,41 @@
 
         sourceSets {
             commonMain.dependencies {
-                api(project(":compose:ui:ui-test"))
-                implementation(project(":compose:animation:animation"))
-                implementation(project(":compose:foundation:foundation"))
-
-                implementation(KOTLIN_STDLIB_COMMON)
+                implementation(project(":compose:ui:ui-text"))
+                implementation(KOTLIN_STDLIB)
             }
 
             jvmMain.dependencies {
-                implementation("androidx.collection:collection:1.1.0")
-                implementation(JUNIT)
+                api(project(":compose:ui:ui-test"))
+                api(JUNIT)
+                api(KOTLIN_STDLIB)
+                api(KOTLIN_STDLIB_COMMON)
+
+                implementation("androidx.annotation:annotation:1.1.0")
+                implementation(KOTLIN_COROUTINES_CORE)
             }
 
             androidMain.dependencies {
-                api(JUNIT)
-                api(ANDROIDX_TEST_EXT_JUNIT)
                 api("androidx.activity:activity:1.2.0")
+                api(ANDROIDX_TEST_EXT_JUNIT)
 
-                implementation(ANDROIDX_TEST_RULES)
-                implementation(ANDROIDX_TEST_RUNNER)
+                implementation(project(":compose:animation:animation-core"))
+                implementation(project(":compose:runtime:runtime-saveable"))
+                implementation("androidx.lifecycle:lifecycle-common:2.3.0")
+                implementation("androidx.lifecycle:lifecycle-runtime:2.3.0")
+                implementation(ANDROIDX_TEST_CORE)
+                implementation(ANDROIDX_TEST_MONITOR)
                 implementation(ESPRESSO_CORE)
-            }
-
-            test.dependencies {
-                implementation(TRUTH)
+                implementation(ESPRESSO_IDLING_RESOURCE)
+                implementation(KOTLIN_COROUTINES_TEST)
             }
 
             androidAndroidTest.dependencies {
-                implementation(TRUTH)
                 implementation(project(":compose:test-utils"))
-                implementation(project(":compose:foundation:foundation"))
                 implementation(project(":compose:material:material"))
-
+                implementation(ANDROIDX_TEST_RULES)
+                implementation(ANDROIDX_TEST_RUNNER)
+                implementation(TRUTH)
                 implementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
                 implementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
                 implementation(MOCKITO_KOTLIN, {
@@ -107,11 +116,11 @@
             }
 
             desktopMain.dependencies {
-                implementation(JUNIT)
                 implementation(TRUTH)
                 implementation(SKIKO)
             }
 
+            androidMain.dependsOn(jvmMain)
             desktopMain.dependsOn(jvmMain)
             jvmMain.dependsOn(commonMain)
         }
diff --git a/compose/ui/ui-test/build.gradle b/compose/ui/ui-test/build.gradle
index 6848517..4fe73f6 100644
--- a/compose/ui/ui-test/build.gradle
+++ b/compose/ui/ui-test/build.gradle
@@ -32,27 +32,25 @@
 dependencies {
     kotlinPlugin(project(":compose:compiler:compiler"))
 
-    if(!AndroidXUiPlugin.isMultiplatformEnabled(project)) {
+    if (!AndroidXUiPlugin.isMultiplatformEnabled(project)) {
+        api(project(":compose:runtime:runtime"))
         api(project(":compose:ui:ui"))
         api(project(":compose:ui:ui-graphics"))
-        api(project(":compose:ui:ui-geometry"))
         api(project(":compose:ui:ui-text"))
         api(project(":compose:ui:ui-unit"))
-        api(project(":compose:ui:ui-util"))
-        api(project(":compose:runtime:runtime"))
+        api(KOTLIN_COROUTINES_CORE)
         api(KOTLIN_COROUTINES_TEST)
+        api(KOTLIN_STDLIB)
+        api(KOTLIN_STDLIB_COMMON)
 
-        implementation(KOTLIN_STDLIB)
+        implementation(project(":compose:ui:ui-util"))
+        implementation("androidx.annotation:annotation:1.1.0")
         implementation(ESPRESSO_CORE)
-        implementation("androidx.collection:collection:1.1.0")
-        implementation(project(":compose:runtime:runtime"))
 
-        testImplementation(TRUTH)
-
-        androidTestImplementation(project(':compose:material:material'))
-        androidTestImplementation(project(':compose:test-utils'))
-        androidTestImplementation(project(':compose:ui:ui-test-junit4'))
         androidTestImplementation(project(":activity:activity-compose"))
+        androidTestImplementation(project(":compose:material:material"))
+        androidTestImplementation(project(":compose:test-utils"))
+        androidTestImplementation(project(":compose:ui:ui-test-junit4"))
         androidTestImplementation(TRUTH)
         androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
         androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
@@ -63,7 +61,7 @@
 }
 
 
-if(AndroidXUiPlugin.isMultiplatformEnabled(project)) {
+if (AndroidXUiPlugin.isMultiplatformEnabled(project)) {
     kotlin {
         android()
         jvm("desktop")
@@ -71,39 +69,33 @@
         sourceSets {
             commonMain.dependencies {
                 api(project(":compose:ui:ui"))
-                api(project(":compose:ui:ui-geometry"))
                 api(project(":compose:ui:ui-text"))
                 api(project(":compose:ui:ui-unit"))
-                api(project(":compose:ui:ui-util"))
+                api(KOTLIN_STDLIB)
 
-                api(KOTLIN_COROUTINES_TEST)
-
-                implementation(KOTLIN_STDLIB_COMMON)
-                implementation(project(":compose:runtime:runtime"))
+                implementation(project(":compose:ui:ui-util"))
             }
 
             jvmMain.dependencies {
-                implementation("androidx.collection:collection:1.1.0")
+                api(project(":compose:runtime:runtime"))
+                api(KOTLIN_COROUTINES_CORE)
+                api(KOTLIN_COROUTINES_TEST)
+                api(KOTLIN_STDLIB_COMMON)
             }
 
             androidMain.dependencies {
-                api(project(":compose:runtime:runtime"))
                 api(project(":compose:ui:ui-graphics"))
 
+                implementation("androidx.annotation:annotation:1.1.0")
                 implementation(ESPRESSO_CORE)
             }
 
-            test.dependencies {
-                implementation(TRUTH)
-            }
-
             androidAndroidTest.dependencies {
-                implementation(TRUTH)
                 implementation(project(":compose:material:material"))
                 implementation(project(":compose:test-utils"))
                 implementation(project(":compose:ui:ui-test-junit4"))
                 implementation(project(":activity:activity-compose"))
-
+                implementation(TRUTH)
                 implementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
                 implementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
                 implementation(MOCKITO_KOTLIN, {
@@ -117,6 +109,7 @@
                 implementation(SKIKO)
             }
 
+            androidMain.dependsOn(jvmMain)
             desktopMain.dependsOn(jvmMain)
             jvmMain.dependsOn(commonMain)
         }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
index 68f644c..49ee7fc 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
@@ -32,7 +32,10 @@
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.selection.toggleable
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.foundation.text.BasicTextField
@@ -42,6 +45,7 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.platform.AndroidComposeView
 import androidx.compose.ui.platform.AndroidComposeViewAccessibilityDelegateCompat
+import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsActions
 import androidx.compose.ui.semantics.SemanticsProperties
@@ -55,6 +59,7 @@
 import androidx.compose.ui.test.assertIsOn
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
 import androidx.compose.ui.test.performSemanticsAction
 import androidx.compose.ui.text.AnnotatedString
@@ -63,6 +68,7 @@
 import androidx.compose.ui.text.input.PasswordVisualTransformation
 import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
 import androidx.core.view.ViewCompat
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -102,31 +108,8 @@
     private lateinit var container: OpenComposeView
     private lateinit var delegate: AndroidComposeViewAccessibilityDelegateCompat
     private lateinit var provider: AccessibilityNodeProvider
-    private lateinit var textLayoutResult: TextLayoutResult
 
     private val argument = ArgumentCaptor.forClass(AccessibilityEvent::class.java)
-    private var isTextFieldVisible by mutableStateOf(true)
-    private var textFieldSelectionOne = false
-    private var isPaneVisible by mutableStateOf(false)
-    private var paneTestTitle by mutableStateOf(PaneTitleOne)
-    private var textFieldValue = mutableStateOf(TextFieldValue(InitialText))
-
-    companion object {
-        private const val TimeOutInitialization: Long = 5000
-        private const val TopColTag = "topColumn"
-        private const val ToggleableTag = "toggleable"
-        private const val DisabledToggleableTag = "disabledToggleable"
-        private const val TextFieldTag = "textField"
-        private const val TextNodeTag = "textNode"
-        private const val ParentForOverlappedChildrenTag = "parentForOverlappedChildren"
-        private const val OverlappedChildOneTag = "overlappedChildOne"
-        private const val OverlappedChildTwoTag = "overlappedChildTwo"
-        private const val PaneTag = "pane"
-        private const val PaneTitleOne = "pane title one"
-        private const val PaneTitleTwo = "pane title two"
-        private const val InputText = "hello"
-        private const val InitialText = "h"
-    }
 
     @Before
     fun setup() {
@@ -144,77 +127,7 @@
             }
 
             activity.setContentView(container)
-            container.setContent {
-                var checked by remember { mutableStateOf(true) }
-                var value by remember { textFieldValue }
-                Column(Modifier.testTag(TopColTag)) {
-                    Box(
-                        Modifier
-                            .toggleable(value = checked,  checked = it })
-                            .testTag(ToggleableTag)
-                    ) {
-                        BasicText("ToggleableText")
-                        Box {
-                            BasicText("TextNode", Modifier.testTag(TextNodeTag))
-                        }
-                    }
-                    Box(
-                        Modifier
-                            .toggleable(
-                                value = checked,
-                                enabled = false,
-                                 checked = it }
-                            )
-                            .testTag(DisabledToggleableTag),
-                        content = {
-                            BasicText("ToggleableText")
-                        }
-                    )
-                    Box(Modifier.testTag(ParentForOverlappedChildrenTag)) {
-                        BasicText(
-                            "Child One",
-                            Modifier
-                                .zIndex(1f)
-                                .testTag(OverlappedChildOneTag)
-                                .requiredSize(50.dp)
-                        )
-                        BasicText(
-                            "Child Two",
-                            Modifier
-                                .testTag(OverlappedChildTwoTag)
-                                .requiredSize(50.dp)
-                        )
-                    }
-                    if (isTextFieldVisible) {
-                        BasicTextField(
-                            modifier = Modifier
-                                .semantics {
-                                    // Make sure this block will be executed when selection changes.
-                                    this.textSelectionRange = value.selection
-                                    if (value.selection == TextRange(1)) {
-                                        textFieldSelectionOne = true
-                                    }
-                                }
-                                .testTag(TextFieldTag),
-                            value = value,
-                             value = it },
-                             textLayoutResult = it },
-                            visualTransformation = PasswordVisualTransformation(),
-                            decorationBox = {
-                                BasicText("Label")
-                                it()
-                            }
-                        )
-                    }
-                }
-                if (isPaneVisible) {
-                    Box(
-                        Modifier
-                            .testTag(PaneTag)
-                            .semantics { paneTitle = paneTestTitle }
-                    ) {}
-                }
-            }
+
             androidComposeView = container.getChildAt(0) as AndroidComposeView
             delegate = ViewCompat.getAccessibilityDelegate(androidComposeView) as
                 AndroidComposeViewAccessibilityDelegateCompat
@@ -225,9 +138,21 @@
     }
 
     @Test
-    fun testCreateAccessibilityNodeInfo() {
-        val toggleableNode = rule.onNodeWithTag(ToggleableTag)
-            .fetchSemanticsNode("couldn't find node with tag $ToggleableTag")
+    fun testCreateAccessibilityNodeInfo_forToggleable() {
+        val tag = "Toggleable"
+        container.setContent {
+            var checked by remember { mutableStateOf(true) }
+            Box(
+                Modifier
+                    .toggleable(value = checked,  checked = it })
+                    .testTag(tag)
+            ) {
+                BasicText("ToggleableText")
+            }
+        }
+
+        val toggleableNode = rule.onNodeWithTag(tag)
+            .fetchSemanticsNode("couldn't find node with tag $tag")
         val accessibilityNodeInfo = provider.createAccessibilityNodeInfo(toggleableNode.id)
         assertEquals("android.view.View", accessibilityNodeInfo.className)
         val stateDescription = when {
@@ -255,13 +180,22 @@
 
     @Test
     fun testCreateAccessibilityNodeInfo_forTextField() {
-        textFieldValue.value = TextFieldValue(InitialText)
-        val textFieldNode = rule.onNodeWithTag(TextFieldTag)
-            .fetchSemanticsNode("couldn't find node with tag $TextFieldTag")
+        val tag = "TextField"
+        container.setContent {
+            var value by remember { mutableStateOf(TextFieldValue("hello")) }
+            BasicTextField(
+                modifier = Modifier.testTag(tag),
+                value = value,
+                 value = it }
+            )
+        }
+
+        val textFieldNode = rule.onNodeWithTag(tag)
+            .fetchSemanticsNode("couldn't find node with tag $tag")
         val accessibilityNodeInfo = provider.createAccessibilityNodeInfo(textFieldNode.id)
 
         assertEquals("android.widget.EditText", accessibilityNodeInfo.className)
-        assertEquals(InitialText, accessibilityNodeInfo.text.toString())
+        assertEquals("hello", accessibilityNodeInfo.text.toString())
         assertTrue(accessibilityNodeInfo.isFocusable)
         assertFalse(accessibilityNodeInfo.isFocused)
         assertTrue(accessibilityNodeInfo.isEditable)
@@ -307,21 +241,46 @@
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     fun reportedTexts_inTextFieldWithLabel_whenEditableTextNotEmpty() {
-        textFieldValue.value = TextFieldValue(InitialText)
-        val textFieldNode = rule.onNodeWithTag(TextFieldTag)
-            .fetchSemanticsNode("couldn't find node with tag $TextFieldTag")
+        val tag = "TextField"
+
+        container.setContent {
+            BasicTextField(
+                modifier = Modifier.testTag(tag),
+                value = "hello",
+                >
+                decorationBox = {
+                    BasicText("Label")
+                    it()
+                }
+            )
+        }
+
+        val textFieldNode = rule.onNodeWithTag(tag)
+            .fetchSemanticsNode("couldn't find node with tag $tag")
         val accessibilityNodeInfo = provider.createAccessibilityNodeInfo(textFieldNode.id)
 
-        assertEquals(InitialText, accessibilityNodeInfo.text.toString())
+        assertEquals("hello", accessibilityNodeInfo.text.toString())
         assertEquals("Label", accessibilityNodeInfo.hintText.toString())
     }
 
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     fun reportedText_inTextFieldWithLabel_whenEditableTextEmpty() {
-        textFieldValue.value = TextFieldValue()
-        val textFieldNode = rule.onNodeWithTag(TextFieldTag)
-            .fetchSemanticsNode("couldn't find node with tag $TextFieldTag")
+        val tag = "TextField"
+        container.setContent {
+            BasicTextField(
+                modifier = Modifier.testTag(tag),
+                value = "",
+                >
+                decorationBox = {
+                    BasicText("Label")
+                    it()
+                }
+            )
+        }
+
+        val textFieldNode = rule.onNodeWithTag(tag)
+            .fetchSemanticsNode("couldn't find node with tag $tag")
         val accessibilityNodeInfo = provider.createAccessibilityNodeInfo(textFieldNode.id)
 
         assertEquals("Label", accessibilityNodeInfo.text.toString())
@@ -330,38 +289,123 @@
 
     @Test
     fun testPerformAction_succeedOnEnabledNodes() {
-        rule.onNodeWithTag(ToggleableTag)
+        val tag = "Toggleable"
+        container.setContent {
+            var checked by remember { mutableStateOf(true) }
+            Box(
+                Modifier
+                    .toggleable(value = checked,  checked = it })
+                    .testTag(tag)
+            ) {
+                BasicText("ToggleableText")
+            }
+        }
+
+        rule.onNodeWithTag(tag)
             .assertIsDisplayed()
             .assertIsOn()
 
         waitForSubtreeEventToSend()
-        val toggleableNode = rule.onNodeWithTag(ToggleableTag)
-            .fetchSemanticsNode("couldn't find node with tag $ToggleableTag")
+        val toggleableNode = rule.onNodeWithTag(tag)
+            .fetchSemanticsNode("couldn't find node with tag $tag")
         rule.runOnUiThread {
             assertTrue(provider.performAction(toggleableNode.id, ACTION_CLICK, null))
         }
-        rule.onNodeWithTag(ToggleableTag)
+        rule.onNodeWithTag(tag)
             .assertIsOff()
+    }
 
-        val textFieldNode = rule.onNodeWithTag(TextFieldTag)
+    @Test
+    fun testPerformAction_failOnDisabledNodes() {
+        val tag = "DisabledToggleable"
+        container.setContent {
+            var checked by remember { mutableStateOf(true) }
+            Box(
+                Modifier
+                    .toggleable(
+                        value = checked,
+                        enabled = false,
+                         checked = it }
+                    )
+                    .testTag(tag),
+                content = {
+                    BasicText("ToggleableText")
+                }
+            )
+        }
+
+        rule.onNodeWithTag(tag)
             .assertIsDisplayed()
-            .fetchSemanticsNode("couldn't find node with tag $TextFieldTag")
+            .assertIsOn()
+
+        waitForSubtreeEventToSend()
+        val toggleableNode = rule.onNodeWithTag(tag)
+            .fetchSemanticsNode("couldn't find node with tag $tag")
+        rule.runOnUiThread {
+            assertFalse(provider.performAction(toggleableNode.id, ACTION_CLICK, null))
+        }
+        rule.onNodeWithTag(tag)
+            .assertIsOn()
+    }
+
+    @Test
+    fun testTextField_performClickAction_succeedOnEnabledNode() {
+        val tag = "TextField"
+        container.setContent {
+            BasicTextField(
+                modifier = Modifier.testTag(tag),
+                value = "value",
+                >
+            )
+        }
+
+        val textFieldNode = rule.onNodeWithTag(tag)
+            .assertIsDisplayed()
+            .fetchSemanticsNode("couldn't find node with tag $tag")
+
         rule.runOnUiThread {
             assertTrue(provider.performAction(textFieldNode.id, ACTION_CLICK, null))
         }
-        rule.onNodeWithTag(TextFieldTag)
+
+        rule.onNodeWithTag(tag)
             .assert(SemanticsMatcher.expectValue(SemanticsProperties.Focused, true))
+    }
+
+    @Test
+    fun testTextField_performSetSelectionAction_succeedOnEnabledNode() {
+        val tag = "TextField"
+        var textFieldSelectionOne = false
+        container.setContent {
+            var value by remember { mutableStateOf(TextFieldValue("hello")) }
+            BasicTextField(
+                modifier = Modifier
+                    .semantics {
+                        // Make sure this block will be executed when selection changes.
+                        this.textSelectionRange = value.selection
+                        if (value.selection == TextRange(1)) {
+                            textFieldSelectionOne = true
+                        }
+                    }
+                    .testTag(tag),
+                value = value,
+                 value = it }
+            )
+        }
+
+        val textFieldNode = rule.onNodeWithTag(tag)
+            .assertIsDisplayed()
+            .fetchSemanticsNode("couldn't find node with tag $tag")
         val argument = Bundle()
         argument.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 1)
         argument.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 1)
+
         rule.runOnUiThread {
             textFieldSelectionOne = false
             assertTrue(provider.performAction(textFieldNode.id, ACTION_SET_SELECTION, argument))
         }
-
         rule.waitUntil { textFieldSelectionOne }
 
-        rule.onNodeWithTag(TextFieldTag)
+        rule.onNodeWithTag(tag)
             .assert(
                 SemanticsMatcher.expectValue(
                     SemanticsProperties.TextSelectionRange,
@@ -371,26 +415,22 @@
     }
 
     @Test
-    fun testPerformAction_failOnDisabledNodes() {
-        rule.onNodeWithTag(DisabledToggleableTag)
-            .assertIsDisplayed()
-            .assertIsOn()
-
-        waitForSubtreeEventToSend()
-        val toggleableNode = rule.onNodeWithTag(DisabledToggleableTag)
-            .fetchSemanticsNode("couldn't find node with tag $DisabledToggleableTag")
-        rule.runOnUiThread {
-            assertFalse(provider.performAction(toggleableNode.id, ACTION_CLICK, null))
-        }
-        rule.onNodeWithTag(DisabledToggleableTag)
-            .assertIsOn()
-    }
-
-    @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     fun testAddExtraDataToAccessibilityNodeInfo() {
-        val textFieldNode = rule.onNodeWithTag(TextFieldTag)
-            .fetchSemanticsNode("couldn't find node with tag $TextFieldTag")
+        val tag = "TextField"
+        lateinit var textLayoutResult: TextLayoutResult
+
+        container.setContent {
+            BasicTextField(
+                modifier = Modifier.testTag(tag),
+                value = "texy",
+                >
+                 textLayoutResult = it }
+            )
+        }
+
+        val textFieldNode = rule.onNodeWithTag(tag)
+            .fetchSemanticsNode("couldn't find node with tag $tag")
         val info = AccessibilityNodeInfo.obtain()
         val argument = Bundle()
         argument.putInt(AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, 0)
@@ -406,8 +446,7 @@
         assertEquals(1, data!!.size)
         val rectF = data[0] as RectF
         val expectedRect = textLayoutResult.getBoundingBox(0).translate(
-            textFieldNode
-                .positionInWindow
+            textFieldNode.positionInWindow
         )
         assertEquals(expectedRect.left, rectF.left)
         assertEquals(expectedRect.top, rectF.top)
@@ -417,17 +456,29 @@
 
     @Test
     fun sendStateChangeEvent_whenClickToggleable() {
-        rule.onNodeWithTag(ToggleableTag)
+        val tag = "Toggleable"
+        container.setContent {
+            var checked by remember { mutableStateOf(true) }
+            Box(
+                Modifier
+                    .toggleable(value = checked,  checked = it })
+                    .testTag(tag)
+            ) {
+                BasicText("ToggleableText")
+            }
+        }
+
+        rule.onNodeWithTag(tag)
             .assertIsDisplayed()
             .assertIsOn()
 
         waitForSubtreeEventToSend()
-        rule.onNodeWithTag(ToggleableTag)
+        rule.onNodeWithTag(tag)
             .performClick()
             .assertIsOff()
 
-        val toggleableNode = rule.onNodeWithTag(ToggleableTag)
-            .fetchSemanticsNode("couldn't find node with tag $ToggleableTag")
+        val toggleableNode = rule.onNodeWithTag(tag)
+            .fetchSemanticsNode("couldn't find node with tag $tag")
 
         val stateEvent = delegate.createEvent(
             toggleableNode.id,
@@ -446,49 +497,59 @@
 
     @Test
     fun sendTextEvents_whenSetText() {
-        textFieldValue.value = TextFieldValue(InitialText)
+        val tag = "TextField"
+        val initialText = "h"
+        val text = "hello"
+        container.setContent {
+            var value by remember { mutableStateOf(TextFieldValue(initialText)) }
+            BasicTextField(
+                modifier = Modifier.testTag(tag),
+                value = value,
+                 value = it }
+            )
+        }
 
-        rule.onNodeWithTag(TextFieldTag)
+        rule.onNodeWithTag(tag)
             .assertIsDisplayed()
             .assert(
                 SemanticsMatcher.expectValue(
                     SemanticsProperties.EditableText,
-                    AnnotatedString(InitialText)
+                    AnnotatedString(initialText)
                 )
             )
 
         waitForSubtreeEventToSend()
-        rule.onNodeWithTag(TextFieldTag)
-            .performSemanticsAction(SemanticsActions.SetText) { it(AnnotatedString(InputText)) }
-        rule.onNodeWithTag(TextFieldTag)
+        rule.onNodeWithTag(tag)
+            .performSemanticsAction(SemanticsActions.SetText) { it(AnnotatedString(text)) }
+        rule.onNodeWithTag(tag)
             .assert(
                 SemanticsMatcher.expectValue(
                     SemanticsProperties.EditableText,
-                    AnnotatedString(InputText)
+                    AnnotatedString(text)
                 )
             )
 
-        val textFieldNode = rule.onNodeWithTag(TextFieldTag)
-            .fetchSemanticsNode("couldn't find node with tag $TextFieldTag")
+        val textFieldNode = rule.onNodeWithTag(tag)
+            .fetchSemanticsNode("couldn't find node with tag $tag")
 
         val textEvent = delegate.createEvent(
             textFieldNode.id,
             AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
         )
-        textEvent.fromIndex = InitialText.length
+        textEvent.fromIndex = initialText.length
         textEvent.removedCount = 0
-        textEvent.addedCount = InputText.length - InitialText.length
-        textEvent.beforeText = InitialText
-        textEvent.text.add(InputText)
+        textEvent.addedCount = text.length - initialText.length
+        textEvent.beforeText = initialText
+        textEvent.text.add(text)
 
         val selectionEvent = delegate.createEvent(
             textFieldNode.id,
             AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
         )
-        selectionEvent.fromIndex = InputText.length
-        selectionEvent.toIndex = InputText.length
-        selectionEvent.itemCount = InputText.length
-        selectionEvent.text.add(InputText)
+        selectionEvent.fromIndex = text.length
+        selectionEvent.toIndex = text.length
+        selectionEvent.itemCount = text.length
+        selectionEvent.text.add(text)
 
         rule.runOnIdle {
             verify(container, atLeastOnce()).requestSendAccessibilityEvent(
@@ -503,9 +564,25 @@
     @Test
     @Ignore("b/177656801")
     fun sendSubtreeChangeEvents_whenNodeRemoved() {
-        val topColumn = rule.onNodeWithTag(TopColTag)
-            .fetchSemanticsNode("couldn't find node with tag $TopColTag")
-        rule.onNodeWithTag(TextFieldTag)
+        val columnTag = "topColumn"
+        val textFieldTag = "TextFieldTag"
+        var isTextFieldVisible by mutableStateOf(true)
+
+        container.setContent {
+            Column(Modifier.testTag(columnTag)) {
+                if (isTextFieldVisible) {
+                    BasicTextField(
+                        modifier = Modifier.testTag(textFieldTag),
+                        value = "text",
+                        >
+                    )
+                }
+            }
+        }
+
+        val parentNode = rule.onNodeWithTag(columnTag)
+            .fetchSemanticsNode("couldn't find node with tag $columnTag")
+        rule.onNodeWithTag(textFieldTag)
             .assertExists()
         // wait for the subtree change events from initialization to send
         waitForSubtreeEventToSendAndVerify {
@@ -513,7 +590,7 @@
                 eq(androidComposeView),
                 argThat(
                     ArgumentMatcher {
-                        getAccessibilityEventSourceSemanticsNodeId(it) == topColumn.id &&
+                        getAccessibilityEventSourceSemanticsNodeId(it) == parentNode.id &&
                             it.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED &&
                             it.contentChangeTypes == AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE
                     }
@@ -524,14 +601,14 @@
         // TextField is removed compared to setup.
         isTextFieldVisible = false
 
-        rule.onNodeWithTag(TextFieldTag)
+        rule.onNodeWithTag(textFieldTag)
             .assertDoesNotExist()
         waitForSubtreeEventToSendAndVerify {
             verify(container, atLeastOnce()).requestSendAccessibilityEvent(
                 eq(androidComposeView),
                 argThat(
                     ArgumentMatcher {
-                        getAccessibilityEventSourceSemanticsNodeId(it) == topColumn.id &&
+                        getAccessibilityEventSourceSemanticsNodeId(it) == parentNode.id &&
                             it.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED &&
                             it.contentChangeTypes == AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE
                     }
@@ -543,9 +620,25 @@
     @Test
     @Ignore("b/178524529")
     fun traverseEventBeforeSelectionEvent_whenTraverseTextField() {
-        val textFieldNode = rule.onNodeWithTag(TextFieldTag)
+        val tag = "TextFieldTag"
+        val text = "h"
+        container.setContent {
+            var value by remember { mutableStateOf(TextFieldValue(text)) }
+            BasicTextField(
+                modifier = Modifier.testTag(tag),
+                value = value,
+                 value = it },
+                visualTransformation = PasswordVisualTransformation(),
+                decorationBox = {
+                    BasicText("Label")
+                    it()
+                }
+            )
+        }
+
+        val textFieldNode = rule.onNodeWithTag(tag)
             .assertIsDisplayed()
-            .fetchSemanticsNode("couldn't find node with tag $TextFieldTag")
+            .fetchSemanticsNode("couldn't find node with tag $tag")
 
         waitForSubtreeEventToSend()
         val args = Bundle()
@@ -566,10 +659,10 @@
             textFieldNode.id,
             AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
         )
-        selectionEvent.fromIndex = InitialText.length
-        selectionEvent.toIndex = InitialText.length
-        selectionEvent.itemCount = InitialText.length
-        selectionEvent.text.add(InitialText)
+        selectionEvent.fromIndex = text.length
+        selectionEvent.toIndex = text.length
+        selectionEvent.itemCount = text.length
+        selectionEvent.text.add(text)
 
         val traverseEvent = delegate.createEvent(
             textFieldNode.id,
@@ -580,7 +673,7 @@
         traverseEvent.action = AccessibilityNodeInfoCompat.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
         traverseEvent.movementGranularity =
             AccessibilityNodeInfoCompat.MOVEMENT_GRANULARITY_CHARACTER
-        traverseEvent.text.add(InitialText)
+        traverseEvent.text.add(text)
 
         rule.runOnIdle {
             verify(container, atLeastOnce()).requestSendAccessibilityEvent(
@@ -600,10 +693,25 @@
     @Test
     @Ignore("b/177656801")
     fun semanticsNodeBeingMergedLayoutChange_sendThrottledSubtreeEventsForMergedSemanticsNode() {
-        val toggleableNode = rule.onNodeWithTag(ToggleableTag)
-            .fetchSemanticsNode("couldn't find node with tag $ToggleableTag")
-        val textNode = rule.onNodeWithTag(TextNodeTag, useUnmergedTree = true)
-            .fetchSemanticsNode("couldn't find node with tag $TextNodeTag")
+        val tag = "Toggleable"
+        container.setContent {
+            var checked by remember { mutableStateOf(true) }
+            Box(
+                Modifier
+                    .toggleable(value = checked,  checked = it })
+                    .testTag(tag)
+            ) {
+                BasicText("ToggleableText")
+                Box {
+                    BasicText("TextNode")
+                }
+            }
+        }
+
+        val toggleableNode = rule.onNodeWithTag(tag)
+            .fetchSemanticsNode("couldn't find node with tag $tag")
+        val textNode = rule.onNodeWithText("TextNode", useUnmergedTree = true)
+            .fetchSemanticsNode("couldn't find node with text TextNode")
         // wait for the subtree change events from initialization to send
         waitForSubtreeEventToSendAndVerify {
             verify(container, atLeastOnce()).requestSendAccessibilityEvent(
@@ -642,10 +750,25 @@
     @Test
     @Ignore("b/177656801")
     fun layoutNodeWithoutSemanticsLayoutChange_sendThrottledSubtreeEventsForMergedSemanticsNode() {
-        val toggleableNode = rule.onNodeWithTag(ToggleableTag)
-            .fetchSemanticsNode("couldn't find node with tag $ToggleableTag")
-        val textNode = rule.onNodeWithTag(TextNodeTag, useUnmergedTree = true)
-            .fetchSemanticsNode("couldn't find node with tag $TextNodeTag")
+        val tag = "Toggleable"
+        container.setContent {
+            var checked by remember { mutableStateOf(true) }
+            Box(
+                Modifier
+                    .toggleable(value = checked,  checked = it })
+                    .testTag(tag)
+            ) {
+                BasicText("ToggleableText")
+                Box {
+                    BasicText("TextNode")
+                }
+            }
+        }
+
+        val toggleableNode = rule.onNodeWithTag(tag)
+            .fetchSemanticsNode("couldn't find node with tag $tag")
+        val textNode = rule.onNodeWithText("TextNode", useUnmergedTree = true)
+            .fetchSemanticsNode("couldn't find node with text TextNode")
         // wait for the subtree change events from initialization to send
         waitForSubtreeEventToSendAndVerify {
             verify(container, atLeastOnce()).requestSendAccessibilityEvent(
@@ -685,6 +808,18 @@
 
     @Test
     fun testGetVirtualViewAt() {
+        val tag = "Toggleable"
+        container.setContent {
+            var checked by remember { mutableStateOf(true) }
+            Box(
+                Modifier
+                    .toggleable(value = checked,  checked = it })
+                    .testTag(tag)
+            ) {
+                BasicText("ToggleableText")
+            }
+        }
+
         var rootNodeBoundsLeft = 0f
         var rootNodeBoundsTop = 0f
         rule.runOnIdle {
@@ -692,20 +827,53 @@
             rootNodeBoundsLeft = rootNode.boundsInWindow.left
             rootNodeBoundsTop = rootNode.boundsInWindow.top
         }
-        val toggleableNode = rule.onNodeWithTag(ToggleableTag)
-            .fetchSemanticsNode("couldn't find node with tag $ToggleableTag")
+
+        val toggleableNode = rule.onNodeWithTag(tag)
+            .fetchSemanticsNode("couldn't find node with tag $tag")
         val toggleableNodeBounds = toggleableNode.boundsInWindow
 
         val toggleableNodeId = delegate.getVirtualViewAt(
             (toggleableNodeBounds.left + toggleableNodeBounds.right) / 2 - rootNodeBoundsLeft,
             (toggleableNodeBounds.top + toggleableNodeBounds.bottom) / 2 - rootNodeBoundsTop
         )
-        assertEquals(toggleableNode.id, toggleableNodeId)
 
-        val overlappedChildOneNode = rule.onNodeWithTag(OverlappedChildOneTag)
-            .fetchSemanticsNode("couldn't find node with tag $OverlappedChildOneTag")
-        val overlappedChildTwoNode = rule.onNodeWithTag(OverlappedChildTwoTag)
-            .fetchSemanticsNode("couldn't find node with tag $OverlappedChildTwoTag")
+        assertEquals(toggleableNode.id, toggleableNodeId)
+    }
+
+    @Test
+    fun testGetVirtualViewAt_overlappedChildren() {
+        val childOneTag = "OverlappedChildOne"
+        val childTwoTag = "OverlappedChildTwo"
+        container.setContent {
+            Box {
+                BasicText(
+                    "Child One",
+                    Modifier
+                        .zIndex(1f)
+                        .testTag(childOneTag)
+                        .requiredSize(50.dp)
+                )
+                BasicText(
+                    "Child Two",
+                    Modifier
+                        .testTag(childTwoTag)
+                        .requiredSize(50.dp)
+                )
+            }
+        }
+
+        var rootNodeBoundsLeft = 0f
+        var rootNodeBoundsTop = 0f
+        rule.runOnIdle {
+            val rootNode = androidComposeView.semanticsOwner.rootSemanticsNode
+            rootNodeBoundsLeft = rootNode.boundsInWindow.left
+            rootNodeBoundsTop = rootNode.boundsInWindow.top
+        }
+
+        val overlappedChildOneNode = rule.onNodeWithTag(childOneTag)
+            .fetchSemanticsNode("couldn't find node with tag $childOneTag")
+        val overlappedChildTwoNode = rule.onNodeWithTag(childTwoTag)
+            .fetchSemanticsNode("couldn't find node with tag $childTwoTag")
         val overlappedChildNodeBounds = overlappedChildTwoNode.boundsInWindow
         val overlappedChildNodeId = delegate.getVirtualViewAt(
             (overlappedChildNodeBounds.left + overlappedChildNodeBounds.right) / 2 -
@@ -713,18 +881,41 @@
             (overlappedChildNodeBounds.top + overlappedChildNodeBounds.bottom) / 2 -
                 rootNodeBoundsTop
         )
+
         assertEquals(overlappedChildOneNode.id, overlappedChildNodeId)
         assertNotEquals(overlappedChildTwoNode.id, overlappedChildNodeId)
     }
 
     @Test
     fun testAccessibilityNodeInfoTreePruned() {
-        val parentNode = rule.onNodeWithTag(ParentForOverlappedChildrenTag)
-            .fetchSemanticsNode("couldn't find node with tag $ParentForOverlappedChildrenTag")
-        val overlappedChildOneNode = rule.onNodeWithTag(OverlappedChildOneTag)
-            .fetchSemanticsNode("couldn't find node with tag $OverlappedChildOneTag")
-        val overlappedChildTwoNode = rule.onNodeWithTag(OverlappedChildTwoTag)
-            .fetchSemanticsNode("couldn't find node with tag $OverlappedChildTwoTag")
+        val parentTag = "ParentForOverlappedChildren"
+        val childOneTag = "OverlappedChildOne"
+        val childTwoTag = "OverlappedChildTwo"
+        container.setContent {
+            Box(Modifier.testTag(parentTag)) {
+                BasicText(
+                    "Child One",
+                    Modifier
+                        .zIndex(1f)
+                        .testTag(childOneTag)
+                        .requiredSize(50.dp)
+                )
+                BasicText(
+                    "Child Two",
+                    Modifier
+                        .testTag(childTwoTag)
+                        .requiredSize(50.dp)
+                )
+            }
+        }
+
+        val parentNode = rule.onNodeWithTag(parentTag)
+            .fetchSemanticsNode("couldn't find node with tag $parentTag")
+        val overlappedChildOneNode = rule.onNodeWithTag(childOneTag)
+            .fetchSemanticsNode("couldn't find node with tag $childOneTag")
+        val overlappedChildTwoNode = rule.onNodeWithTag(childTwoTag)
+            .fetchSemanticsNode("couldn't find node with tag $childTwoTag")
+
         assertEquals(1, provider.createAccessibilityNodeInfo(parentNode.id).childCount)
         assertEquals(
             "Child One",
@@ -735,18 +926,33 @@
 
     @Test
     fun testPaneAppear() {
-        rule.onNodeWithTag(PaneTag).assertDoesNotExist()
+        val paneTag = "Pane"
+        var isPaneVisible by mutableStateOf(false)
+        val paneTestTitle by mutableStateOf("pane title")
+
+        container.setContent {
+            if (isPaneVisible) {
+                Box(
+                    Modifier
+                        .testTag(paneTag)
+                        .semantics { paneTitle = paneTestTitle }
+                ) {}
+            }
+        }
+
+        rule.onNodeWithTag(paneTag).assertDoesNotExist()
+
         isPaneVisible = true
-        rule.onNodeWithTag(PaneTag)
+        rule.onNodeWithTag(paneTag)
             .assert(
                 SemanticsMatcher.expectValue(
                     SemanticsProperties.PaneTitle,
-                    PaneTitleOne
+                    "pane title"
                 )
             )
             .assertIsDisplayed()
         waitForSubtreeEventToSend()
-        val paneNode = rule.onNodeWithTag(PaneTag).fetchSemanticsNode()
+        val paneNode = rule.onNodeWithTag(paneTag).fetchSemanticsNode()
         rule.runOnIdle {
             verify(container, times(1)).requestSendAccessibilityEvent(
                 eq(androidComposeView),
@@ -764,26 +970,42 @@
 
     @Test
     fun testPaneTitleChange() {
-        rule.onNodeWithTag(PaneTag).assertDoesNotExist()
+        val paneTag = "Pane"
+        var isPaneVisible by mutableStateOf(false)
+        var paneTestTitle by mutableStateOf("pane title")
+
+        container.setContent {
+            if (isPaneVisible) {
+                Box(
+                    Modifier
+                        .testTag(paneTag)
+                        .semantics { paneTitle = paneTestTitle }
+                ) {}
+            }
+        }
+
+        rule.onNodeWithTag(paneTag).assertDoesNotExist()
+
         isPaneVisible = true
-        rule.onNodeWithTag(PaneTag)
+        rule.onNodeWithTag(paneTag)
             .assert(
                 SemanticsMatcher.expectValue(
                     SemanticsProperties.PaneTitle,
-                    PaneTitleOne
+                    "pane title"
                 )
             )
             .assertIsDisplayed()
         waitForSubtreeEventToSend()
-        val paneNode = rule.onNodeWithTag(PaneTag).fetchSemanticsNode()
-        paneTestTitle = PaneTitleTwo
-        rule.onNodeWithTag(PaneTag)
+
+        paneTestTitle = "new pane title"
+        rule.onNodeWithTag(paneTag)
             .assert(
                 SemanticsMatcher.expectValue(
                     SemanticsProperties.PaneTitle,
-                    PaneTitleTwo
+                    "new pane title"
                 )
             )
+        val paneNode = rule.onNodeWithTag(paneTag).fetchSemanticsNode()
         rule.runOnIdle {
             verify(container, times(1)).requestSendAccessibilityEvent(
                 eq(androidComposeView),
@@ -801,19 +1023,31 @@
 
     @Test
     fun testPaneDisappear() {
-        rule.onNodeWithTag(PaneTag).assertDoesNotExist()
+        val paneTag = "Pane"
+        var isPaneVisible by mutableStateOf(false)
+        val paneTestTitle by mutableStateOf("pane title")
+
+        container.setContent {
+            if (isPaneVisible) {
+                Box(Modifier.testTag(paneTag).semantics { paneTitle = paneTestTitle }) {}
+            }
+        }
+
+        rule.onNodeWithTag(paneTag).assertDoesNotExist()
+
         isPaneVisible = true
-        rule.onNodeWithTag(PaneTag)
+        rule.onNodeWithTag(paneTag)
             .assert(
                 SemanticsMatcher.expectValue(
                     SemanticsProperties.PaneTitle,
-                    PaneTitleOne
+                    "pane title"
                 )
             )
             .assertIsDisplayed()
         waitForSubtreeEventToSend()
+
         isPaneVisible = false
-        rule.onNodeWithTag(PaneTag).assertDoesNotExist()
+        rule.onNodeWithTag(paneTag).assertDoesNotExist()
         rule.runOnIdle {
             verify(container, times(1)).requestSendAccessibilityEvent(
                 eq(androidComposeView),
@@ -830,9 +1064,18 @@
 
     @Test
     fun testEventForPasswordTextField() {
-        val textFieldNode = rule.onNodeWithTag(TextFieldTag)
-            .fetchSemanticsNode("Couldn't fetch node with tag $TextFieldTag")
+        val tag = "TextField"
+        container.setContent {
+            BasicTextField(
+                modifier = Modifier.testTag(tag),
+                value = "value",
+                >
+                visualTransformation = PasswordVisualTransformation()
+            )
+        }
 
+        val textFieldNode = rule.onNodeWithTag(tag)
+            .fetchSemanticsNode("Couldn't fetch node with tag $tag")
         val event = delegate.createEvent(
             textFieldNode.id,
             AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
@@ -841,6 +1084,54 @@
         assertTrue(event.isPassword)
     }
 
+    @Test
+    fun testDialog_setCorrectBounds() {
+        var dialogComposeView: AndroidComposeView? = null
+        container.setContent {
+            Dialog( {
+                dialogComposeView = LocalView.current as AndroidComposeView
+                delegate = ViewCompat.getAccessibilityDelegate(dialogComposeView!!) as
+                    AndroidComposeViewAccessibilityDelegateCompat
+
+                Box(Modifier.size(300.dp)) {
+                    BasicText(
+                        text = "text",
+                        modifier = Modifier.offset(10.dp, 10.dp).fillMaxSize()
+                    )
+                }
+            }
+        }
+
+        val textNode = rule.onNodeWithText("text").fetchSemanticsNode()
+        val info = AccessibilityNodeInfoCompat.obtain()
+        delegate.populateAccessibilityNodeInfoProperties(
+            textNode.id,
+            info,
+            textNode
+        )
+
+        val viewPosition = intArrayOf(0, 0)
+        dialogComposeView!!.getLocationOnScreen(viewPosition)
+        with(rule.density) {
+            val offset = 10.dp.roundToPx()
+            val size = 300.dp.roundToPx()
+            val textPositionOnScreenX = viewPosition[0] + offset
+            val textPositionOnScreenY = viewPosition[1] + offset
+
+            val textRect = android.graphics.Rect()
+            info.getBoundsInScreen(textRect)
+            assertEquals(
+                android.graphics.Rect(
+                    textPositionOnScreenX,
+                    textPositionOnScreenY,
+                    textPositionOnScreenX + size,
+                    textPositionOnScreenY + size
+                ),
+                textRect
+            )
+        }
+    }
+
     private fun eventIndex(list: List<AccessibilityEvent>, event: AccessibilityEvent): Int {
         for (i in list.indices) {
             if (ReflectionEquals(list[i], null).matches(event)) {
@@ -870,7 +1161,7 @@
     private fun waitForSubtreeEventToSend() {
         // When the subtree events are sent, we will also update our previousSemanticsNodes,
         // which will affect our next accessibility events from semantics tree comparison.
-        rule.mainClock.advanceTimeBy(TimeOutInitialization)
+        rule.mainClock.advanceTimeBy(5000)
         rule.waitForIdle()
     }
 }
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt
index 9c57132..59a95ba 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt
@@ -21,8 +21,10 @@
 import android.util.SparseArray
 import android.view.View
 import android.view.ViewStructure
+import android.view.autofill.AutofillId
 import android.view.autofill.AutofillManager
 import android.view.autofill.AutofillValue
+import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.graphics.toAndroidRect
@@ -70,14 +72,21 @@
 internal fun AndroidAutofill.populateViewStructure(root: ViewStructure) {
 
     // Add child nodes. The function returns the index to the first item.
-    var index = root.addChildCount(autofillTree.children.count())
+    var index = AutofillApi23Helper.addChildCount(root, autofillTree.children.count())
 
     for ((id, autofillNode) in autofillTree.children) {
-        root.newChild(index)?.apply {
-            setAutofillId(root.autofillId!!, id)
-            setId(id, view.context.packageName, null, null)
-            setAutofillType(View.AUTOFILL_TYPE_TEXT)
-            setAutofillHints(autofillNode.autofillTypes.map { it.androidType }.toTypedArray())
+        AutofillApi23Helper.newChild(root, index)?.also { child ->
+            AutofillApi26Helper.setAutofillId(
+                child,
+                AutofillApi26Helper.getAutofillId(root)!!,
+                id
+            )
+            AutofillApi23Helper.setId(child, id, view.context.packageName, null, null)
+            AutofillApi26Helper.setAutofillType(child, View.AUTOFILL_TYPE_TEXT)
+            AutofillApi26Helper.setAutofillHints(
+                child,
+                autofillNode.autofillTypes.map { it.androidType }.toTypedArray()
+            )
 
             if (autofillNode.boundingBox == null) {
                 // Do we need an exception here? warning? silently ignore? If the boundingbox is
@@ -89,7 +98,7 @@
                 )
             }
             autofillNode.boundingBox?.toAndroidRect()?.run {
-                setDimens(left, top, 0, 0, width(), height())
+                AutofillApi23Helper.setDimens(child, left, top, 0, 0, width(), height())
             }
         }
         index++
@@ -106,10 +115,102 @@
         val itemId = values.keyAt(index)
         val value = values[itemId]
         when {
-            value.isText -> autofillTree.performAutofill(itemId, value.textValue.toString())
-            value.isDate -> TODO("b/138604541: Add onFill() callback for date")
-            value.isList -> TODO("b/138604541: Add onFill() callback for list")
-            value.isToggle -> TODO("b/138604541:  Add onFill() callback for toggle")
+            AutofillApi26Helper.isText(value) -> autofillTree.performAutofill(
+                itemId,
+                AutofillApi26Helper.textValue(value).toString()
+            )
+            AutofillApi26Helper.isDate(value) ->
+                TODO("b/138604541: Add onFill() callback for date")
+            AutofillApi26Helper.isList(value) ->
+                TODO("b/138604541: Add onFill() callback for list")
+            AutofillApi26Helper.isToggle(value) ->
+                TODO("b/138604541:  Add onFill() callback for toggle")
         }
     }
+}
+
+/**
+ * This class is here to ensure that the classes that use this API will get verified and can be
+ * AOT compiled. It is expected that this class will soft-fail verification, but the classes
+ * which use this method will pass.
+ */
+@RequiresApi(26)
+internal object AutofillApi26Helper {
+    @RequiresApi(26)
+    @DoNotInline
+    fun setAutofillId(structure: ViewStructure, parent: AutofillId, virtualId: Int) =
+        structure.setAutofillId(parent, virtualId)
+
+    @RequiresApi(26)
+    @DoNotInline
+    fun getAutofillId(structure: ViewStructure) = structure.autofillId
+
+    @RequiresApi(26)
+    @DoNotInline
+    fun setAutofillType(structure: ViewStructure, type: Int) = structure.setAutofillType(type)
+
+    @RequiresApi(26)
+    @DoNotInline
+    fun setAutofillHints(structure: ViewStructure, hints: Array<String>) =
+        structure.setAutofillHints(hints)
+
+    @RequiresApi(26)
+    @DoNotInline
+    fun isText(value: AutofillValue) = value.isText
+
+    @RequiresApi(26)
+    @DoNotInline
+    fun isDate(value: AutofillValue) = value.isDate
+
+    @RequiresApi(26)
+    @DoNotInline
+    fun isList(value: AutofillValue) = value.isList
+
+    @RequiresApi(26)
+    @DoNotInline
+    fun isToggle(value: AutofillValue) = value.isToggle
+
+    @RequiresApi(26)
+    @DoNotInline
+    fun textValue(value: AutofillValue): CharSequence = value.textValue
+}
+
+/**
+ * This class is here to ensure that the classes that use this API will get verified and can be
+ * AOT compiled. It is expected that this class will soft-fail verification, but the classes
+ * which use this method will pass.
+ */
+@RequiresApi(23)
+internal object AutofillApi23Helper {
+    @RequiresApi(23)
+    @DoNotInline
+    fun newChild(structure: ViewStructure, index: Int): ViewStructure? =
+        structure.newChild(index)
+
+    @RequiresApi(23)
+    @DoNotInline
+    fun addChildCount(structure: ViewStructure, num: Int) =
+        structure.addChildCount(num)
+
+    @RequiresApi(23)
+    @DoNotInline
+    fun setId(
+        structure: ViewStructure,
+        id: Int,
+        packageName: String?,
+        typeName: String?,
+        entryName: String?
+    ) = structure.setId(id, packageName, typeName, entryName)
+
+    @RequiresApi(23)
+    @DoNotInline
+    fun setDimens(
+        structure: ViewStructure,
+        left: Int,
+        top: Int,
+        scrollX: Int,
+        scrollY: Int,
+        width: Int,
+        height: Int
+    ) = structure.setDimens(left, top, scrollX, scrollY, width, height)
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillDebugUtils.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillDebugUtils.android.kt
index 839587ce..79ada4f 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillDebugUtils.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillDebugUtils.android.kt
@@ -20,6 +20,7 @@
 import android.util.Log
 import android.view.View
 import android.view.autofill.AutofillManager
+import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.compose.ui.ExperimentalComposeUiApi
 
@@ -61,6 +62,7 @@
  */
 @ExperimentalComposeUiApi
 @RequiresApi(Build.VERSION_CODES.O)
+@DoNotInline
 internal fun AndroidAutofill.registerCallback() {
     autofillManager.registerCallback(AutofillCallback)
 }
@@ -70,6 +72,7 @@
  */
 @ExperimentalComposeUiApi
 @RequiresApi(Build.VERSION_CODES.O)
+@DoNotInline
 internal fun AndroidAutofill.unregisterCallback() {
     autofillManager.unregisterCallback(AutofillCallback)
 }
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index d9de0d8..6f66a7b 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -33,6 +33,7 @@
 import android.view.autofill.AutofillValue
 import android.view.inputmethod.EditorInfo
 import android.view.inputmethod.InputConnection
+import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
 import androidx.compose.runtime.getValue
@@ -307,9 +308,11 @@
         setWillNotDraw(false)
         isFocusable = true
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            focusable = View.FOCUSABLE
-            // not to add the default focus highlight to the whole compose view
-            defaultFocusHighlightEnabled = false
+            AndroidComposeViewVerificationHelperMethods.focusable(
+                this,
+                focusable = View.FOCUSABLE,
+                defaultFocusHighlightEnabled = false
+            )
         }
         isFocusableInTouchMode = true
         clipChildren = false
@@ -925,4 +928,20 @@
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 @InternalComposeUiApi // used by testing infra
 var textInputServiceFactory: (PlatformTextInputService) -> TextInputService =
-    { TextInputService(it) }
\ No newline at end of file
+    { TextInputService(it) }
+
+/**
+ * This class is here to ensure that the classes that use this API will get verified and can be
+ * AOT compiled. It is expected that this class will soft-fail verification, but the classes
+ * which use this method will pass.
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+internal object AndroidComposeViewVerificationHelperMethods {
+    @RequiresApi(Build.VERSION_CODES.O)
+    @DoNotInline
+    fun focusable(view: View, focusable: Int, defaultFocusHighlightEnabled: Boolean) {
+        view.focusable = focusable
+        // not to add the default focus highlight to the whole compose view
+        view.defaultFocusHighlightEnabled = defaultFocusHighlightEnabled
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
index cfa7cd7..91251f3 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
@@ -33,6 +33,7 @@
 import android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX
 import android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
 import android.view.accessibility.AccessibilityNodeProvider
+import androidx.annotation.DoNotInline
 import androidx.annotation.IntRange
 import androidx.annotation.RequiresApi
 import androidx.annotation.VisibleForTesting
@@ -268,12 +269,15 @@
         }
         info.packageName = view.context.packageName
         try {
+            val boundsInRoot = semanticsNode.boundsInRoot
+            val topLeftInScreen = view.localToScreen(boundsInRoot.topLeft)
+            val bottomRightInScreen = view.localToScreen(boundsInRoot.bottomRight)
             info.setBoundsInScreen(
                 android.graphics.Rect(
-                    semanticsNode.boundsInWindow.left.toInt(),
-                    semanticsNode.boundsInWindow.top.toInt(),
-                    semanticsNode.boundsInWindow.right.toInt(),
-                    semanticsNode.boundsInWindow.bottom.toInt()
+                    topLeftInScreen.x.toInt(),
+                    topLeftInScreen.y.toInt(),
+                    bottomRightInScreen.x.toInt(),
+                    bottomRightInScreen.y.toInt()
                 )
             )
         } catch (e: IllegalStateException) {
@@ -427,7 +431,10 @@
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !info.text.isNullOrEmpty() &&
             semanticsNode.config.contains(SemanticsActions.GetTextLayoutResult)
         ) {
-            info.unwrap().availableExtraData = listOf(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)
+            AccessibilityNodeInfoVerificationHelperMethods.setAvailableExtraData(
+                info.unwrap(),
+                listOf(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)
+            )
         }
 
         val rangeInfo =
@@ -2090,4 +2097,18 @@
 
     findAllSemanticNodesRecursive(root)
     return nodes
+}
+
+/**
+ * This class is here to ensure that the classes that use this API will get verified and can be
+ * AOT compiled. It is expected that this class will soft-fail verification, but the classes
+ * which use this method will pass.
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+internal object AccessibilityNodeInfoVerificationHelperMethods {
+    @RequiresApi(Build.VERSION_CODES.O)
+    @DoNotInline
+    fun setAvailableExtraData(node: AccessibilityNodeInfo, data: List<String>) {
+        node.availableExtraData = data
+    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.android.kt
index 179dcbf..ee209af 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.android.kt
@@ -19,7 +19,9 @@
 import android.util.Log
 import android.view.View
 import android.view.ViewGroup
+import androidx.annotation.DoNotInline
 import androidx.annotation.MainThread
+import androidx.annotation.RequiresApi
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composition
 import androidx.compose.runtime.CompositionContext
@@ -205,4 +207,17 @@
  */
 private fun inspectionWanted(owner: AndroidComposeView): Boolean =
     Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
-        owner.attributeSourceResourceMap.isNotEmpty()
+        WrapperVerificationHelperMethods.attributeSourceResourceMap(owner).isNotEmpty()
+
+/**
+ * This class is here to ensure that the classes that use this API will get verified and can be
+ * AOT compiled. It is expected that this class will soft-fail verification, but the classes
+ * which use this method will pass.
+ */
+@RequiresApi(Build.VERSION_CODES.Q)
+internal object WrapperVerificationHelperMethods {
+    @RequiresApi(Build.VERSION_CODES.Q)
+    @DoNotInline
+    fun attributeSourceResourceMap(view: View): Map<Int, Int> =
+        view.attributeSourceResourceMap
+}
\ No newline at end of file
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 6c0bc87..eb363ba 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -105,9 +105,9 @@
     docs("androidx.enterprise:enterprise-feedback:1.1.0")
     docs("androidx.enterprise:enterprise-feedback-testing:1.1.0")
     docs("androidx.exifinterface:exifinterface:1.3.2")
-    docs("androidx.fragment:fragment:1.3.0")
-    docs("androidx.fragment:fragment-ktx:1.3.0")
-    docs("androidx.fragment:fragment-testing:1.3.0")
+    docs("androidx.fragment:fragment:1.3.1")
+    docs("androidx.fragment:fragment-ktx:1.3.1")
+    docs("androidx.fragment:fragment-testing:1.3.1")
     docs("androidx.gridlayout:gridlayout:1.0.0")
     docs("androidx.heifwriter:heifwriter:1.1.0-alpha01")
     docs("androidx.hilt:hilt-common:1.0.0-alpha03")
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
index bcd5e7d..8412469 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
@@ -26,6 +26,8 @@
 import android.view.View
 import androidx.activity.ComponentActivity
 import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.ViewModelStore
+import androidx.lifecycle.testing.TestLifecycleOwner
 import androidx.navigation.test.R
 import androidx.test.annotation.UiThreadTest
 import androidx.test.core.app.ActivityScenario
@@ -225,6 +227,58 @@
 
     @UiThreadTest
     @Test
+    fun testSetViewModelStoreOwnerAfterGraphSet() {
+        val navController = createNavController()
+        navController.setViewModelStore(ViewModelStore())
+        val navGraph = navController.navigatorProvider.navigation(
+            id = 1,
+            startDestination = R.id.start_test
+        ) {
+            test(R.id.start_test)
+        }
+        navController.setGraph(navGraph, null)
+
+        try {
+            navController.setViewModelStore(ViewModelStore())
+        } catch (e: IllegalStateException) {
+            assertThat(e).hasMessageThat().contains(
+                "ViewModelStore should be set before setGraph call"
+            )
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    fun testSetSameViewModelStoreOwnerAfterGraphSet() {
+        val navController = createNavController()
+        val viewModelStore = ViewModelStore()
+        navController.setViewModelStore(viewModelStore)
+        val navGraph = navController.navigatorProvider.navigation(
+            id = 1,
+            startDestination = R.id.start_test
+        ) {
+            test(R.id.start_test)
+        }
+        navController.setGraph(navGraph, null)
+
+        navController.setViewModelStore(viewModelStore)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testSetSameLifecycleOwner() {
+        val navController = createNavController()
+        val lifecycleOwner = TestLifecycleOwner(Lifecycle.State.RESUMED)
+
+        navController.setLifecycleOwner(lifecycleOwner)
+        assertThat(lifecycleOwner.observerCount).isEqualTo(1)
+
+        navController.setLifecycleOwner(lifecycleOwner)
+        assertThat(lifecycleOwner.observerCount).isEqualTo(1)
+    }
+
+    @UiThreadTest
+    @Test
     fun testNavigate() {
         val navController = createNavController()
         navController.setGraph(R.navigation.nav_simple)
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
index 6850ae1..eae7d8f 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
@@ -1279,6 +1279,9 @@
     }
 
     void setLifecycleOwner(@NonNull LifecycleOwner owner) {
+        if (owner == mLifecycleOwner) {
+            return;
+        }
         mLifecycleOwner = owner;
         mLifecycleOwner.getLifecycle().addObserver(mLifecycleObserver);
     }
@@ -1305,6 +1308,9 @@
     }
 
     void setViewModelStore(@NonNull ViewModelStore viewModelStore) {
+        if (mViewModel == NavControllerViewModel.getInstance(viewModelStore)) {
+            return;
+        }
         if (!mBackStack.isEmpty()) {
             throw new IllegalStateException("ViewModelStore should be set before setGraph call");
         }
diff --git a/paging/common/build.gradle b/paging/common/build.gradle
index 61339c5..4d61a3f 100644
--- a/paging/common/build.gradle
+++ b/paging/common/build.gradle
@@ -52,7 +52,6 @@
     mavenGroup = LibraryGroups.PAGING
     inceptionYear = "2017"
     description = "Android Paging-Common"
-    legacyDisableKotlinStrictApiMode = true
 }
 
 // Allow usage of Kotlin's @OptIn.
diff --git a/paging/common/src/main/kotlin/androidx/paging/CachedPagingData.kt b/paging/common/src/main/kotlin/androidx/paging/CachedPagingData.kt
index d43cccf..6f6177f 100644
--- a/paging/common/src/main/kotlin/androidx/paging/CachedPagingData.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/CachedPagingData.kt
@@ -75,9 +75,9 @@
  * @param scope The coroutine scope where this page cache will be kept alive.
  */
 @CheckResult
-fun <T : Any> Flow<PagingData<T>>.cachedIn(
+public fun <T : Any> Flow<PagingData<T>>.cachedIn(
     scope: CoroutineScope
-) = cachedIn(scope, null)
+): Flow<PagingData<T>> = cachedIn(scope, null)
 
 internal fun <T : Any> Flow<PagingData<T>>.cachedIn(
     scope: CoroutineScope,
diff --git a/paging/common/src/main/kotlin/androidx/paging/CombinedLoadStates.kt b/paging/common/src/main/kotlin/androidx/paging/CombinedLoadStates.kt
index 19efffd..4714cc2 100644
--- a/paging/common/src/main/kotlin/androidx/paging/CombinedLoadStates.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/CombinedLoadStates.kt
@@ -19,7 +19,7 @@
 /**
  * Collection of pagination [LoadState]s for both a [PagingSource], and [RemoteMediator].
  */
-class CombinedLoadStates(
+public class CombinedLoadStates(
     /**
      * Convenience for combined behavior of [REFRESH][LoadType.REFRESH] [LoadState], which
      * generally defers to [mediator] if it exists, but if previously was [LoadState.Loading],
@@ -30,7 +30,7 @@
      * specifically, e.g., showing cached data when network loads via [mediator] fail,
      * [LoadStates] exposed via [source] and [mediator] should be used directly.
      */
-    val refresh: LoadState,
+    public val refresh: LoadState,
     /**
      * Convenience for combined behavior of [PREPEND][LoadType.REFRESH] [LoadState], which
      * generally defers to [mediator] if it exists, but if previously was [LoadState.Loading],
@@ -41,7 +41,7 @@
      * specifically, e.g., showing cached data when network loads via [mediator] fail,
      * [LoadStates] exposed via [source] and [mediator] should be used directly.
      */
-    val prepend: LoadState,
+    public val prepend: LoadState,
     /**
      * Convenience for combined behavior of [APPEND][LoadType.REFRESH] [LoadState], which
      * generally defers to [mediator] if it exists, but if previously was [LoadState.Loading],
@@ -52,17 +52,17 @@
      * specifically, e.g., showing cached data when network loads via [mediator] fail,
      * [LoadStates] exposed via [source] and [mediator] should be used directly.
      */
-    val append: LoadState,
+    public val append: LoadState,
     /**
      * [LoadStates] corresponding to loads from a [PagingSource].
      */
-    val source: LoadStates,
+    public val source: LoadStates,
 
     /**
      * [LoadStates] corresponding to loads from a [RemoteMediator], or `null` if [RemoteMediator]
      * not present.
      */
-    val mediator: LoadStates? = null,
+    public val mediator: LoadStates? = null,
 ) {
 
     override fun equals(other: Any?): Boolean {
diff --git a/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt b/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
index e7d9382..777d96a 100644
--- a/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
@@ -34,7 +34,7 @@
  */
 @Suppress("DEPRECATION")
 @RestrictTo(RestrictTo.Scope.LIBRARY)
-open class ContiguousPagedList<K : Any, V : Any>(
+public open class ContiguousPagedList<K : Any, V : Any>(
     final override val pagingSource: PagingSource<K, V>,
     coroutineScope: CoroutineScope,
     notifyDispatcher: CoroutineDispatcher,
@@ -52,6 +52,7 @@
 ),
     PagedStorage.Callback,
     LegacyPageFetcher.PageConsumer<V> {
+
     internal companion object {
         internal fun getPrependItemsRequested(
             prefetchDistance: Int,
@@ -102,7 +103,7 @@
                 ?: initialLastKey
         }
 
-    override val isDetached
+    override val isDetached: Boolean
         get() = pager.isDetached
 
     /**
@@ -368,7 +369,7 @@
         tryDispatchBoundaryCallbacks(true)
     }
 
-    override fun detach() = pager.detach()
+    override fun detach(): Unit = pager.detach()
 
     @MainThread
     override fun onInitialized(count: Int) {
@@ -399,8 +400,11 @@
         notifyInserted(endPosition + changed, added)
     }
 
-    override fun onPagesRemoved(startOfDrops: Int, count: Int) = notifyRemoved(startOfDrops, count)
+    override fun onPagesRemoved(startOfDrops: Int, count: Int) {
+        notifyRemoved(startOfDrops, count)
+    }
 
-    override fun onPagesSwappedToPlaceholder(startOfDrops: Int, count: Int) =
+    override fun onPagesSwappedToPlaceholder(startOfDrops: Int, count: Int) {
         notifyChanged(startOfDrops, count)
+    }
 }
diff --git a/paging/common/src/main/kotlin/androidx/paging/DataSource.kt b/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
index cbceeae..500558c 100644
--- a/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
@@ -96,7 +96,7 @@
  * position in data set. Note - this is distinct from e.g. Room's `<Value> Value type
  * loaded by the DataSource.
  */
-abstract class DataSource<Key : Any, Value : Any>
+public abstract class DataSource<Key : Any, Value : Any>
 // Since we currently rely on implementation details of two implementations, prevent external
 // subclassing, except through exposed subclasses.
 internal constructor(internal val type: KeyType) {
@@ -109,7 +109,7 @@
     /**
      * @return `true` if the data source is invalid, and can no longer be queried for data.
      */
-    open val isInvalid
+    public open val isInvalid: Boolean
         @WorkerThread
         get() = _invalid.get()
 
@@ -135,7 +135,7 @@
      * @param Key Key identifying items in DataSource.
      * @param Value Type of items in the list loaded by the DataSources.
      */
-    abstract class Factory<Key : Any, Value : Any> {
+    public abstract class Factory<Key : Any, Value : Any> {
         /**
          * Create a [DataSource].
          *
@@ -149,7 +149,7 @@
          *
          * @return the new DataSource.
          */
-        abstract fun create(): DataSource<Key, Value>
+        public abstract fun create(): DataSource<Key, Value>
 
         /**
          * Applies the given function to each value emitted by DataSources produced by this Factory.
@@ -165,8 +165,11 @@
          * @see DataSource.map
          * @see DataSource.mapByPage
          */
-        open fun <ToValue : Any> map(function: Function<Value, ToValue>): Factory<Key, ToValue> =
-            mapByPage(Function { list -> list.map { function.apply(it) } })
+        public open fun <ToValue : Any> map(
+            function: Function<Value, ToValue>
+        ): Factory<Key, ToValue> {
+            return mapByPage(Function { list -> list.map { function.apply(it) } })
+        }
 
         /**
          * Applies the given function to each value emitted by DataSources produced by this Factory.
@@ -185,8 +188,9 @@
          * @see DataSource.mapByPage
          */
         @JvmSynthetic // hidden to preserve Java source compat with arch.core.util.Function variant
-        open fun <ToValue : Any> map(function: (Value) -> ToValue): Factory<Key, ToValue> =
-            mapByPage(Function { list -> list.map(function) })
+        public open fun <ToValue : Any> map(function: (Value) -> ToValue): Factory<Key, ToValue> {
+            return mapByPage(Function { list -> list.map(function) })
+        }
 
         /**
          * Applies the given function to each value emitted by DataSources produced by this Factory.
@@ -202,7 +206,7 @@
          * @see DataSource.map
          * @see DataSource.mapByPage
          */
-        open fun <ToValue : Any> mapByPage(
+        public open fun <ToValue : Any> mapByPage(
             function: Function<List<Value>, List<ToValue>>
         ): Factory<Key, ToValue> = object : Factory<Key, ToValue>() {
             override fun create(): DataSource<Key, ToValue> =
@@ -226,12 +230,12 @@
          * @see DataSource.mapByPage
          */
         @JvmSynthetic // hidden to preserve Java source compat with arch.core.util.Function variant
-        open fun <ToValue : Any> mapByPage(
+        public open fun <ToValue : Any> mapByPage(
             function: (List<Value>) -> List<ToValue>
         ): Factory<Key, ToValue> = mapByPage(Function { function(it) })
 
         @JvmOverloads
-        fun asPagingSourceFactory(
+        public fun asPagingSourceFactory(
             fetchDispatcher: CoroutineDispatcher = Dispatchers.IO
         ): () -> PagingSource<Key, Value> = SuspendingPagingSourceFactory(
             delegate = {
@@ -255,7 +259,7 @@
      * @see DataSource.Factory.map
      * @see DataSource.Factory.mapByPage
      */
-    open fun <ToValue : Any> mapByPage(
+    public open fun <ToValue : Any> mapByPage(
         function: Function<List<Value>, List<ToValue>>
     ): DataSource<Key, ToValue> = WrapperDataSource(this, function)
 
@@ -276,7 +280,7 @@
      * @see DataSource.Factory.mapByPage
      */
     @JvmSynthetic // hidden to preserve Java source compat with arch.core.util.Function variant
-    open fun <ToValue : Any> mapByPage(
+    public open fun <ToValue : Any> mapByPage(
         function: (List<Value>) -> List<ToValue>
     ): DataSource<Key, ToValue> = mapByPage(Function { function(it) })
 
@@ -294,8 +298,11 @@
      * @see DataSource.Factory.map
      * @see DataSource.Factory.mapByPage
      */
-    open fun <ToValue : Any> map(function: Function<Value, ToValue>): DataSource<Key, ToValue> =
-        mapByPage { list -> list.map { function.apply(it) } }
+    public open fun <ToValue : Any> map(
+        function: Function<Value, ToValue>
+    ): DataSource<Key, ToValue> {
+        return mapByPage { list -> list.map { function.apply(it) } }
+    }
 
     /**
      * Applies the given function to each value emitted by the DataSource.
@@ -314,7 +321,7 @@
      *
      */
     @JvmSynthetic // hidden to preserve Java source compat with arch.core.util.Function variant
-    open fun <ToValue : Any> map(
+    public open fun <ToValue : Any> map(
         function: (Value) -> ToValue
     ): DataSource<Key, ToValue> = map(Function { function(it) })
 
@@ -332,7 +339,7 @@
      * Used to signal when a [DataSource] a data source has become invalid, and that a new data
      * source is needed to continue loading data.
      */
-    fun interface InvalidatedCallback {
+    public fun interface InvalidatedCallback {
         /**
          * Called when the data backing the list has become invalid. This callback is typically used
          * to signal that a new data source is needed.
@@ -342,7 +349,7 @@
          * invalidate it.
          */
         @AnyThread
-        fun onInvalidated()
+        public fun onInvalidated()
     }
 
     /**
@@ -358,7 +365,7 @@
      */
     @AnyThread
     @Suppress("RegistrationName")
-    open fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
+    public open fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
         onInvalidatedCallbacks.add(onInvalidatedCallback)
     }
 
@@ -369,7 +376,7 @@
      */
     @AnyThread
     @Suppress("RegistrationName")
-    open fun removeInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
+    public open fun removeInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
         onInvalidatedCallbacks.remove(onInvalidatedCallback)
     }
 
@@ -379,7 +386,7 @@
      * If invalidate has already been called, this method does nothing.
      */
     @AnyThread
-    open fun invalidate() {
+    public open fun invalidate() {
         if (_invalid.compareAndSet(false, true)) {
             onInvalidatedCallbacks.forEach { it.onInvalidated() }
         }
diff --git a/paging/common/src/main/kotlin/androidx/paging/ExperimentalPagingApi.kt b/paging/common/src/main/kotlin/androidx/paging/ExperimentalPagingApi.kt
index 359a6e8..c31ff68 100644
--- a/paging/common/src/main/kotlin/androidx/paging/ExperimentalPagingApi.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/ExperimentalPagingApi.kt
@@ -21,4 +21,4 @@
  * source-incompatible change in newer versions of the artifact that supplies it.
  */
 @RequiresOptIn
-annotation class ExperimentalPagingApi
\ No newline at end of file
+public annotation class ExperimentalPagingApi
\ No newline at end of file
diff --git a/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt b/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
index 7014826..19562a5 100644
--- a/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
@@ -29,7 +29,7 @@
  * @suppress
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
-class InitialPagedList<K : Any, V : Any>(
+public class InitialPagedList<K : Any, V : Any>(
     pagingSource: PagingSource<K, V>,
     coroutineScope: CoroutineScope,
     notifyDispatcher: CoroutineDispatcher,
diff --git a/paging/common/src/main/kotlin/androidx/paging/InvalidatingPagingSourceFactory.kt b/paging/common/src/main/kotlin/androidx/paging/InvalidatingPagingSourceFactory.kt
index 8004326..92dc909 100644
--- a/paging/common/src/main/kotlin/androidx/paging/InvalidatingPagingSourceFactory.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/InvalidatingPagingSourceFactory.kt
@@ -28,7 +28,7 @@
  *
  * @param pagingSourceFactory The [PagingSource] factory that returns a PagingSource when called
  */
-class InvalidatingPagingSourceFactory<Key : Any, Value : Any>(
+public class InvalidatingPagingSourceFactory<Key : Any, Value : Any>(
     private val pagingSourceFactory: () -> PagingSource<Key, Value>
 ) : () -> PagingSource<Key, Value> {
 
@@ -47,7 +47,7 @@
      * Calls [PagingSource.invalidate] on each [PagingSource] that was produced by this
      * [InvalidatingPagingSourceFactory]
      */
-    fun invalidate() {
+    public fun invalidate() {
         while (pagingSources.isNotEmpty()) {
             pagingSources.removeFirst().also {
                 if (!it.invalid) {
diff --git a/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt
index 10ea32b..68a85be 100644
--- a/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt
@@ -47,7 +47,9 @@
         "androidx.paging.PagingSource"
     )
 )
-abstract class ItemKeyedDataSource<Key : Any, Value : Any> : DataSource<Key, Value>(ITEM_KEYED) {
+public abstract class ItemKeyedDataSource<Key : Any, Value : Any> : DataSource<Key, Value>(
+    ITEM_KEYED
+) {
 
     /**
      * Holder object for inputs to [loadInitial].
@@ -64,13 +66,13 @@
      * @property placeholdersEnabled Defines whether placeholders are enabled, and whether the
      * loaded total count will be ignored.
      */
-    open class LoadInitialParams<Key : Any>(
+    public open class LoadInitialParams<Key : Any>(
         @JvmField
-        val requestedInitialKey: Key?,
+        public val requestedInitialKey: Key?,
         @JvmField
-        val requestedLoadSize: Int,
+        public val requestedLoadSize: Int,
         @JvmField
-        val placeholdersEnabled: Boolean
+        public val placeholdersEnabled: Boolean
     )
 
     /**
@@ -85,7 +87,12 @@
      * Returned page can be of this size, but it may be altered if that is easier, e.g. a network
      * data source where the backend defines page size.
      */
-    open class LoadParams<Key : Any>(@JvmField val key: Key, @JvmField val requestedLoadSize: Int)
+    public open class LoadParams<Key : Any>(
+        @JvmField
+        public val key: Key,
+        @JvmField
+        public val requestedLoadSize: Int
+    )
 
     /**
      * Callback for [loadInitial]
@@ -105,7 +112,7 @@
      *
      * @param Value Type of items being loaded.
      */
-    abstract class LoadInitialCallback<Value> : LoadCallback<Value>() {
+    public abstract class LoadInitialCallback<Value> : LoadCallback<Value>() {
         /**
          * Called to pass initial load state from a DataSource.
          *
@@ -125,7 +132,7 @@
          * Includes the number in the initial `data` parameter as well as any items that can be
          * loaded in front or behind of `data`.
          */
-        abstract fun onResult(data: List<Value>, position: Int, totalCount: Int)
+        public abstract fun onResult(data: List<Value>, position: Int, totalCount: Int)
     }
 
     /**
@@ -139,7 +146,7 @@
      *
      * @param Value Type of items being loaded.
      */
-    abstract class LoadCallback<Value> {
+    public abstract class LoadCallback<Value> {
         /**
          * Called to pass loaded data from a DataSource.
          *
@@ -154,7 +161,7 @@
          *
          * @param data List of items loaded from the [ItemKeyedDataSource].
          */
-        abstract fun onResult(data: List<Value>)
+        public abstract fun onResult(data: List<Value>)
     }
 
     @Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
@@ -252,7 +259,10 @@
      * @param params Parameters for initial load, including initial key and requested size.
      * @param callback Callback that receives initial load data.
      */
-    abstract fun loadInitial(params: LoadInitialParams<Key>, callback: LoadInitialCallback<Value>)
+    public abstract fun loadInitial(
+        params: LoadInitialParams<Key>,
+        callback: LoadInitialCallback<Value>
+    )
 
     /**
      * Load list data after the key specified in [LoadParams.key].
@@ -271,7 +281,7 @@
      * @param params Parameters for the load, including the key to load after, and requested size.
      * @param callback Callback that receives loaded data.
      */
-    abstract fun loadAfter(params: LoadParams<Key>, callback: LoadCallback<Value>)
+    public abstract fun loadAfter(params: LoadParams<Key>, callback: LoadCallback<Value>)
 
     /**
      * Load list data before the key specified in [LoadParams.key].
@@ -293,7 +303,7 @@
      * @param params Parameters for the load, including the key to load before, and requested size.
      * @param callback Callback that receives loaded data.
      */
-    abstract fun loadBefore(params: LoadParams<Key>, callback: LoadCallback<Value>)
+    public abstract fun loadBefore(params: LoadParams<Key>, callback: LoadCallback<Value>)
 
     /**
      * Return a key associated with the given item.
@@ -311,29 +321,29 @@
      * @param item Item to get the key from.
      * @return Key associated with given item.
      */
-    abstract fun getKey(item: Value): Key
+    public abstract fun getKey(item: Value): Key
 
     @Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
     internal override fun getKeyInternal(item: Value): Key = getKey(item)
 
     @Suppress("DEPRECATION")
-    final override fun <ToValue : Any> mapByPage(
+    public final override fun <ToValue : Any> mapByPage(
         function: Function<List<Value>, List<ToValue>>
     ): ItemKeyedDataSource<Key, ToValue> = WrapperItemKeyedDataSource(this, function)
 
     @Suppress("DEPRECATION")
-    final override fun <ToValue : Any> mapByPage(
+    public final override fun <ToValue : Any> mapByPage(
         function: (List<Value>) -> List<ToValue>
     ): ItemKeyedDataSource<Key, ToValue> = mapByPage(Function { function(it) })
 
     @Suppress("DEPRECATION")
-    final override fun <ToValue : Any> map(
+    public final override fun <ToValue : Any> map(
         function: Function<Value, ToValue>
     ): ItemKeyedDataSource<Key, ToValue> =
         mapByPage(Function { list -> list.map { function.apply(it) } })
 
     @Suppress("DEPRECATION")
-    final override fun <ToValue : Any> map(
+    public final override fun <ToValue : Any> map(
         function: (Value) -> ToValue
     ): ItemKeyedDataSource<Key, ToValue> = mapByPage(Function { list -> list.map(function) })
 }
diff --git a/paging/common/src/main/kotlin/androidx/paging/ItemSnapshotList.kt b/paging/common/src/main/kotlin/androidx/paging/ItemSnapshotList.kt
index 8fabddd..1512288 100644
--- a/paging/common/src/main/kotlin/androidx/paging/ItemSnapshotList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/ItemSnapshotList.kt
@@ -23,23 +23,23 @@
  * [AsyncPagingDataDiffer][androidx.pagingAsyncPagingDataDiffer] or a
  * [PagingDataAdapter][androidx.paging.PagingDataAdapter].
  */
-class ItemSnapshotList<T>(
+public class ItemSnapshotList<T>(
     /**
      * Number of placeholders before the presented [items], 0 if
      * [enablePlaceholders][androidx.paging.PagingConfig.enablePlaceholders] is `false`.
      */
     @IntRange(from = 0)
-    val placeholdersBefore: Int,
+    public val placeholdersBefore: Int,
     /**
      * Number of placeholders after the presented [items], 0 if
      * [enablePlaceholders][androidx.paging.PagingConfig.enablePlaceholders] is `false`.
      */
     @IntRange(from = 0)
-    val placeholdersAfter: Int,
+    public val placeholdersAfter: Int,
     /**
      * The presented data, excluding placeholders.
      */
-    val items: List<T>
+    public val items: List<T>
 ) : AbstractList<T?>() {
 
     /**
@@ -49,7 +49,7 @@
      *
      * @see items
      */
-    override val size: Int
+    public override val size: Int
         get() = placeholdersBefore + items.size + placeholdersAfter
 
     /**
diff --git a/paging/common/src/main/kotlin/androidx/paging/LoadState.kt b/paging/common/src/main/kotlin/androidx/paging/LoadState.kt
index d5ff053..11c5d59 100644
--- a/paging/common/src/main/kotlin/androidx/paging/LoadState.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/LoadState.kt
@@ -30,8 +30,8 @@
  *
  * @see LoadType
  */
-sealed class LoadState(
-    val endOfPaginationReached: Boolean
+public sealed class LoadState(
+    public val endOfPaginationReached: Boolean
 ) {
     /**
      * Indicates the [PagingData] is not currently loading, and no error currently observed.
@@ -41,7 +41,7 @@
      * should continue to make requests for additional data in this direction or if it should
      * halt as the end of the dataset has been reached.
      */
-    class NotLoading(
+    public class NotLoading(
         endOfPaginationReached: Boolean
     ) : LoadState(endOfPaginationReached) {
         override fun toString(): String {
@@ -66,7 +66,7 @@
     /**
      * Loading is in progress.
      */
-    object Loading : LoadState(false) {
+    public object Loading : LoadState(false) {
         override fun toString(): String {
             return "Loading(endOfPaginationReached=$endOfPaginationReached)"
         }
@@ -88,8 +88,8 @@
      *
      * @see androidx.paging.PagedList.retry
      */
-    class Error(
-        val error: Throwable
+    public class Error(
+        public val error: Throwable
     ) : LoadState(false) {
         override fun equals(other: Any?): Boolean {
             return other is Error &&
diff --git a/paging/common/src/main/kotlin/androidx/paging/LoadStates.kt b/paging/common/src/main/kotlin/androidx/paging/LoadStates.kt
index c474460..442148f 100644
--- a/paging/common/src/main/kotlin/androidx/paging/LoadStates.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/LoadStates.kt
@@ -22,16 +22,16 @@
 /**
  * Collection of pagination [LoadState]s - refresh, prepend, and append.
  */
-data class LoadStates(
+public data class LoadStates(
     /** [LoadState] corresponding to [LoadType.REFRESH] loads. */
-    val refresh: LoadState,
+    public val refresh: LoadState,
     /** [LoadState] corresponding to [LoadType.PREPEND] loads. */
-    val prepend: LoadState,
+    public val prepend: LoadState,
     /** [LoadState] corresponding to [LoadType.APPEND] loads. */
-    val append: LoadState
+    public val append: LoadState
 ) {
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    inline fun forEach(op: (LoadType, LoadState) -> Unit) {
+    public inline fun forEach(op: (LoadType, LoadState) -> Unit) {
         op(LoadType.REFRESH, refresh)
         op(LoadType.PREPEND, prepend)
         op(LoadType.APPEND, append)
diff --git a/paging/common/src/main/kotlin/androidx/paging/LoadType.kt b/paging/common/src/main/kotlin/androidx/paging/LoadType.kt
index 435357f..02f7e64 100644
--- a/paging/common/src/main/kotlin/androidx/paging/LoadType.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/LoadType.kt
@@ -25,7 +25,7 @@
  *
  * @see LoadState
  */
-enum class LoadType {
+public enum class LoadType {
     /**
      * [PagingData] content being refreshed, which can be a result of [PagingSource]
      * invalidation, refresh that may contain content updates, or the initial load.
diff --git a/paging/common/src/main/kotlin/androidx/paging/NullPaddedList.kt b/paging/common/src/main/kotlin/androidx/paging/NullPaddedList.kt
index 18dea6c..2d0659f 100644
--- a/paging/common/src/main/kotlin/androidx/paging/NullPaddedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/NullPaddedList.kt
@@ -26,10 +26,10 @@
  * @suppress
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-interface NullPaddedList<T> {
-    val placeholdersBefore: Int
-    fun getFromStorage(localIndex: Int): T
-    val placeholdersAfter: Int
-    val size: Int
-    val storageCount: Int
+public interface NullPaddedList<T> {
+    public val placeholdersBefore: Int
+    public fun getFromStorage(localIndex: Int): T
+    public val placeholdersAfter: Int
+    public val size: Int
+    public val storageCount: Int
 }
\ No newline at end of file
diff --git a/paging/common/src/main/kotlin/androidx/paging/PageFetcher.kt b/paging/common/src/main/kotlin/androidx/paging/PageFetcher.kt
index ee5eda6..4d2345e 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PageFetcher.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PageFetcher.kt
@@ -66,13 +66,21 @@
                     pagingSource = generateNewPagingSource(previousPagingSource = pagingSource)
                 }
 
-                var previousPagingState = previousGeneration?.snapshot?.refreshKeyInfo()
+                var previousPagingState = previousGeneration?.snapshot?.currentPagingState()
+
+                // If cached PagingState had pages loaded, but previous generation didn't, use
+                // the cached PagingState to handle cases where invalidation happens too quickly,
+                // so that getRefreshKey and remote refresh at least have some data to work with.
+                if (previousPagingState?.pages.isNullOrEmpty() &&
+                    previousGeneration?.state?.pages?.isNotEmpty() == true
+                ) {
+                    previousPagingState = previousGeneration.state
+                }
+
                 // If previous generation was invalidated before anchorPosition was established,
                 // re-use last PagingState that successfully loaded pages and has an anchorPosition.
                 // This prevents rapid invalidation from deleting the anchorPosition if the
-                // previous generation didn't have time to load before getting invalidated. We
-                // check for anchorPosition before overriding to prevent empty PagingState from
-                // overriding a PagingState with pages loaded, but no anchorPosition.
+                // previous generation didn't have time to load before getting invalidated.
                 if (previousPagingState?.anchorPosition == null &&
                     previousGeneration?.state?.anchorPosition != null
                 ) {
@@ -94,7 +102,8 @@
                         // initialization or PagingSource invalidation.
                         triggerRemoteRefresh = triggerRemoteRefresh,
                         remoteMediatorConnection = remoteMediatorAccessor,
-                        invalidate = this@PageFetcher::refresh
+                        invalidate = this@PageFetcher::refresh,
+                        previousPagingState = previousPagingState,
                     ),
                     state = previousPagingState,
                 )
diff --git a/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt b/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt
index 0b70976..c849a44 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt
@@ -58,7 +58,8 @@
     private val retryFlow: Flow<Unit>,
     private val triggerRemoteRefresh: Boolean = false,
     val remoteMediatorConnection: RemoteMediatorConnection<Key, Value>? = null,
-    private val invalidate: () -> Unit = {}
+    private val previousPagingState: PagingState<Key, Value>? = null,
+    private val invalidate: () -> Unit = {},
 ) {
     init {
         require(config.jumpThreshold == COUNT_UNDEFINED || pagingSource.jumpingSupported) {
@@ -148,7 +149,9 @@
 
         if (triggerRemoteRefresh) {
             remoteMediatorConnection?.let {
-                val pagingState = stateHolder.withLock { state -> state.currentPagingState(null) }
+                val pagingState = previousPagingState ?: stateHolder.withLock { state ->
+                    state.currentPagingState(null)
+                }
                 it.requestLoad(REFRESH, pagingState)
             }
         }
@@ -193,17 +196,8 @@
         pageEventChannelFlowJob.cancel()
     }
 
-    suspend fun refreshKeyInfo(): PagingState<Key, Value>? {
-        return stateHolder.withLock { state ->
-            lastHint?.let { lastHint ->
-                if (state.pages.isEmpty()) {
-                    // Default to initialKey if no pages loaded.
-                    null
-                } else {
-                    state.currentPagingState(lastHint)
-                }
-            }
-        }
+    suspend fun currentPagingState(): PagingState<Key, Value> {
+        return stateHolder.withLock { state -> state.currentPagingState(lastHint) }
     }
 
     private fun CoroutineScope.startConsumingHints() {
diff --git a/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt
index ac773ac..e3d0fd8 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt
@@ -46,7 +46,9 @@
         "androidx.paging.PagingSource"
     )
 )
-abstract class PageKeyedDataSource<Key : Any, Value : Any> : DataSource<Key, Value>(PAGE_KEYED) {
+public abstract class PageKeyedDataSource<Key : Any, Value : Any> : DataSource<Key, Value>(
+    PAGE_KEYED
+) {
 
     /**
      * Holder object for inputs to [loadInitial].
@@ -58,9 +60,9 @@
      * @property placeholdersEnabled Defines whether placeholders are enabled, and whether the
      * loaded total count will be ignored.
      */
-    open class LoadInitialParams<Key : Any>(
-        @JvmField val requestedLoadSize: Int,
-        @JvmField val placeholdersEnabled: Boolean
+    public open class LoadInitialParams<Key : Any>(
+        @JvmField public val requestedLoadSize: Int,
+        @JvmField public val placeholdersEnabled: Boolean
     )
 
     /**
@@ -75,7 +77,10 @@
      * Returned page can be of this size, but it may be altered if that is easier, e.g. a network
      * data source where the backend defines page size.
      */
-    open class LoadParams<Key : Any>(@JvmField val key: Key, @JvmField val requestedLoadSize: Int)
+    public open class LoadParams<Key : Any>(
+        @JvmField public val key: Key,
+        @JvmField public val requestedLoadSize: Int
+    )
 
     /**
      * Callback for [loadInitial] to return data and, optionally, position/count information.
@@ -95,7 +100,7 @@
      * @param Key Type of data used to query pages.
      * @param Value Type of items being loaded.
      */
-    abstract class LoadInitialCallback<Key, Value> {
+    public abstract class LoadInitialCallback<Key, Value> {
         /**
          * Called to pass initial load state from a DataSource.
          *
@@ -115,7 +120,7 @@
          * Includes the number in the initial `data` parameter as well as any items that can be
          * loaded in front or behind of `data`.
          */
-        abstract fun onResult(
+        public abstract fun onResult(
             data: List<Value>,
             position: Int,
             totalCount: Int,
@@ -138,7 +143,7 @@
          * @param nextPageKey Key for page after the initial load result, or `null` if no more data
          * can be loaded after.
          */
-        abstract fun onResult(data: List<Value>, previousPageKey: Key?, nextPageKey: Key?)
+        public abstract fun onResult(data: List<Value>, previousPageKey: Key?, nextPageKey: Key?)
     }
 
     /**
@@ -153,7 +158,7 @@
      * @param Key Type of data used to query pages.
      * @param Value Type of items being loaded.
      */
-    abstract class LoadCallback<Key, Value> {
+    public abstract class LoadCallback<Key, Value> {
         /**
          * Called to pass loaded data from a [DataSource].
          *
@@ -173,7 +178,7 @@
          * page in [loadAfter]), or `null` if there are no more pages to load in the current load
          * direction.
          */
-        abstract fun onResult(data: List<Value>, adjacentPageKey: Key?)
+        public abstract fun onResult(data: List<Value>, adjacentPageKey: Key?)
     }
 
     /**
@@ -270,7 +275,7 @@
      * @param params Parameters for initial load, including requested load size.
      * @param callback Callback that receives initial load data.
      */
-    abstract fun loadInitial(
+    public abstract fun loadInitial(
         params: LoadInitialParams<Key>,
         callback: LoadInitialCallback<Key, Value>
     )
@@ -293,7 +298,7 @@
      * size.
      * @param callback Callback that receives loaded data.
      */
-    abstract fun loadBefore(params: LoadParams<Key>, callback: LoadCallback<Key, Value>)
+    public abstract fun loadBefore(params: LoadParams<Key>, callback: LoadCallback<Key, Value>)
 
     /**
      * Append page with the key specified by [LoadParams.key].
@@ -313,7 +318,7 @@
      * size.
      * @param callback Callback that receives loaded data.
      */
-    abstract fun loadAfter(params: LoadParams<Key>, callback: LoadCallback<Key, Value>)
+    public abstract fun loadAfter(params: LoadParams<Key>, callback: LoadCallback<Key, Value>)
 
     @Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
     internal override fun getKeyInternal(item: Value): Key =
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedList.kt b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
index 2b22813..0dd12d2 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
@@ -113,15 +113,16 @@
  *
  * @param T The type of the entries in the list.
  */
+@Suppress("DEPRECATION")
 @Deprecated("PagedList is deprecated and has been replaced by PagingData")
-abstract class PagedList<T : Any> internal constructor(
+public abstract class PagedList<T : Any> internal constructor(
     /**
      * The [PagingSource] that provides data to this [PagedList].
      *
      * @suppress
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    open val pagingSource: PagingSource<*, T>,
+    public open val pagingSource: PagingSource<*, T>,
     internal val coroutineScope: CoroutineScope,
     internal val notifyDispatcher: CoroutineDispatcher,
     internal val storage: PagedStorage<T>,
@@ -131,13 +132,13 @@
      *
      * @return the Config of this PagedList
      */
-    val config: Config
+    public val config: Config
 ) : AbstractList<T>() {
     /**
      * @suppress
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    companion object {
+    public companion object {
         /**
          * Create a [PagedList] which loads data from the provided data source on a background
          * thread, posting updates to the main thread.
@@ -157,10 +158,9 @@
          *
          * @suppress
          */
-        @Suppress("DEPRECATION")
         @JvmStatic
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-        fun <K : Any, T : Any> create(
+        public fun <K : Any, T : Any> create(
             pagingSource: PagingSource<K, T>,
             initialPage: PagingSource.LoadResult.Page<K, T>?,
             coroutineScope: CoroutineScope,
@@ -253,7 +253,7 @@
             "longer supports constructing snapshots of loaded data manually.",
         replaceWith = ReplaceWith("Pager.flow", "androidx.paging.Pager")
     )
-    class Builder<Key : Any, Value : Any> {
+    public class Builder<Key : Any, Value : Any> {
         private val pagingSource: PagingSource<Key, Value>?
         private var dataSource: DataSource<Key, Value>?
         private val initialPage: PagingSource.LoadResult.Page<Key, Value>?
@@ -272,7 +272,7 @@
          * @param config [PagedList.Config] that defines how the [PagedList] loads data from its
          * [DataSource].
          */
-        constructor(dataSource: DataSource<Key, Value>, config: Config) {
+        public constructor(dataSource: DataSource<Key, Value>, config: Config) {
             this.pagingSource = null
             this.dataSource = dataSource
             this.initialPage = null
@@ -292,7 +292,7 @@
          * @param pageSize Size of loaded pages when the [PagedList] loads data from its
          * [DataSource].
          */
-        constructor(dataSource: DataSource<Key, Value>, pageSize: Int) : this(
+        public constructor(dataSource: DataSource<Key, Value>, pageSize: Int) : this(
             dataSource = dataSource,
             config = Config(pageSize)
         )
@@ -306,7 +306,7 @@
          * @param config [PagedList.Config] that defines how the [PagedList] loads data from its
          * [PagingSource].
          */
-        constructor(
+        public constructor(
             pagingSource: PagingSource<Key, Value>,
             initialPage: PagingSource.LoadResult.Page<Key, Value>,
             config: Config
@@ -335,7 +335,7 @@
          * @param pageSize Size of loaded pages when the [PagedList] loads data from its
          * [PagingSource].
          */
-        constructor(
+        public constructor(
             pagingSource: PagingSource<Key, Value>,
             initialPage: PagingSource.LoadResult.Page<Key, Value>,
             pageSize: Int
@@ -357,7 +357,9 @@
          * @param coroutineScope
          * @return this
          */
-        fun setCoroutineScope(coroutineScope: CoroutineScope) = this.apply {
+        public fun setCoroutineScope(
+            coroutineScope: CoroutineScope
+        ): Builder<Key, Value> = apply {
             this.coroutineScope = coroutineScope
         }
 
@@ -376,7 +378,9 @@
                 "kotlinx.coroutines.asCoroutineDispatcher"
             )
         )
-        fun setNotifyExecutor(notifyExecutor: Executor) = apply {
+        public fun setNotifyExecutor(
+            notifyExecutor: Executor
+        ): Builder<Key, Value> = apply {
             this.notifyDispatcher = notifyExecutor.asCoroutineDispatcher()
         }
 
@@ -387,7 +391,9 @@
          * where [PagedList.Callback] calls are dispatched. Generally, this is the ui/main thread.
          * @return this
          */
-        fun setNotifyDispatcher(notifyDispatcher: CoroutineDispatcher) = apply {
+        public fun setNotifyDispatcher(
+            notifyDispatcher: CoroutineDispatcher
+        ): Builder<Key, Value> = apply {
             this.notifyDispatcher = notifyDispatcher
         }
 
@@ -409,7 +415,9 @@
                 "kotlinx.coroutines.asCoroutineDispatcher"
             )
         )
-        fun setFetchExecutor(fetchExecutor: Executor) = apply {
+        public fun setFetchExecutor(
+            fetchExecutor: Executor
+        ): Builder<Key, Value> = apply {
             this.fetchDispatcher = fetchExecutor.asCoroutineDispatcher()
         }
 
@@ -423,7 +431,9 @@
          * generally a background thread pool for e.g. I/O or network loading.
          * @return this
          */
-        fun setFetchDispatcher(fetchDispatcher: CoroutineDispatcher) = apply {
+        public fun setFetchDispatcher(
+            fetchDispatcher: CoroutineDispatcher
+        ): Builder<Key, Value> = apply {
             this.fetchDispatcher = fetchDispatcher
         }
 
@@ -435,7 +445,9 @@
          * @param boundaryCallback [BoundaryCallback] for listening to out-of-data events.
          * @return this
          */
-        fun setBoundaryCallback(boundaryCallback: BoundaryCallback<Value>?) = apply {
+        public fun setBoundaryCallback(
+            boundaryCallback: BoundaryCallback<Value>?
+        ): Builder<Key, Value> = apply {
             this.boundaryCallback = boundaryCallback
         }
 
@@ -445,7 +457,9 @@
          * @param initialKey Key the [PagingSource] should load around as part of initialization.
          * @return this
          */
-        fun setInitialKey(initialKey: Key?) = apply {
+        public fun setInitialKey(
+            initialKey: Key?
+        ): Builder<Key, Value> = apply {
             this.initialKey = initialKey
         }
 
@@ -473,8 +487,7 @@
          *
          * @return The newly constructed [PagedList]
          */
-        @Suppress("DEPRECATION")
-        fun build(): PagedList<Value> {
+        public fun build(): PagedList<Value> {
             val fetchDispatcher = fetchDispatcher ?: Dispatchers.IO
             val pagingSource = pagingSource ?: dataSource?.let { dataSource ->
                 LegacyPagingSource(
@@ -509,7 +522,7 @@
      * the dispatcher defined by [PagedList.Builder.setNotifyDispatcher], which is generally the
      * main/UI thread.
      */
-    abstract class Callback {
+    public abstract class Callback {
         /**
          * Called when null padding items have been loaded to signal newly available data, or when
          * data that hasn't been used in a while has been dropped, and swapped back to null.
@@ -518,7 +531,7 @@
          * (including padded nulls).
          * @param count Number of items loaded.
          */
-        abstract fun onChanged(position: Int, count: Int)
+        public abstract fun onChanged(position: Int, count: Int)
 
         /**
          * Called when new items have been loaded at the end or beginning of the list.
@@ -527,7 +540,7 @@
          * `size - 1`.
          * @param count Number of items loaded.
          */
-        abstract fun onInserted(position: Int, count: Int)
+        public abstract fun onInserted(position: Int, count: Int)
 
         /**
          * Called when items have been removed at the end or beginning of the list, and have not
@@ -537,7 +550,7 @@
          * `size - 1`.
          * @param count Number of items loaded.
          */
-        abstract fun onRemoved(position: Int, count: Int)
+        public abstract fun onRemoved(position: Int, count: Int)
     }
 
     /**
@@ -547,12 +560,12 @@
      * [setPageSize][PagedList.Config.Builder.setPageSize], which defines number of items loaded at
      * a time.
      */
-    class Config internal constructor(
+    public class Config internal constructor(
         /**
          * Size of each page loaded by the PagedList.
          */
         @JvmField
-        val pageSize: Int,
+        public val pageSize: Int,
         /**
          * Prefetch distance which defines how far ahead to load.
          *
@@ -562,18 +575,18 @@
          * @see PagedList.loadAround
          */
         @JvmField
-        val prefetchDistance: Int,
+        public val prefetchDistance: Int,
         /**
          * Defines whether the [PagedList] may display null placeholders, if the [PagingSource]
          * provides them.
          */
         @JvmField
-        val enablePlaceholders: Boolean,
+        public val enablePlaceholders: Boolean,
         /**
          * Size hint for initial load of PagedList, often larger than a regular page.
          */
         @JvmField
-        val initialLoadSizeHint: Int,
+        public val initialLoadSizeHint: Int,
         /**
          * Defines the maximum number of items that may be loaded into this pagedList before pages
          * should be dropped.
@@ -584,14 +597,14 @@
          * @see PagedList.Config.Builder.setMaxSize
          */
         @JvmField
-        val maxSize: Int
+        public val maxSize: Int
     ) {
         /**
          * Builder class for [PagedList.Config].
          *
          * You must at minimum specify page size with [setPageSize].
          */
-        class Builder {
+        public class Builder {
             private var pageSize = -1
             private var prefetchDistance = -1
             private var initialLoadSizeHint = -1
@@ -618,7 +631,9 @@
              *
              * @throws IllegalArgumentException if pageSize is < `1`.
              */
-            fun setPageSize(@IntRange(from = 1) pageSize: Int) = apply {
+            public fun setPageSize(
+                @IntRange(from = 1) pageSize: Int
+            ): Builder = apply {
                 if (pageSize < 1) {
                     throw IllegalArgumentException("Page size must be a positive number")
                 }
@@ -640,7 +655,9 @@
              * @param prefetchDistance Distance the [PagedList] should prefetch.
              * @return this
              */
-            fun setPrefetchDistance(@IntRange(from = 0) prefetchDistance: Int) = apply {
+            public fun setPrefetchDistance(
+                @IntRange(from = 0) prefetchDistance: Int
+            ): Builder = apply {
                 this.prefetchDistance = prefetchDistance
             }
 
@@ -671,7 +688,9 @@
              * @param enablePlaceholders `false` if null placeholders should be disabled.
              * @return this
              */
-            fun setEnablePlaceholders(enablePlaceholders: Boolean) = apply {
+            public fun setEnablePlaceholders(
+                enablePlaceholders: Boolean
+            ): Builder = apply {
                 this.enablePlaceholders = enablePlaceholders
             }
 
@@ -686,7 +705,9 @@
              * @param initialLoadSizeHint Number of items to load while initializing the [PagedList]
              * @return this
              */
-            fun setInitialLoadSizeHint(@IntRange(from = 1) initialLoadSizeHint: Int) = apply {
+            public fun setInitialLoadSizeHint(
+                @IntRange(from = 1) initialLoadSizeHint: Int
+            ): Builder = apply {
                 this.initialLoadSizeHint = initialLoadSizeHint
             }
 
@@ -720,7 +741,7 @@
              * @see Config.MAX_SIZE_UNBOUNDED
              * @see Config.maxSize
              */
-            fun setMaxSize(@IntRange(from = 2) maxSize: Int) = apply {
+            public fun setMaxSize(@IntRange(from = 2) maxSize: Int): Builder = apply {
                 this.maxSize = maxSize
             }
 
@@ -734,7 +755,7 @@
              * @throws IllegalArgumentException if maximum size is less than pageSize +
              * 2*prefetchDistance
              */
-            fun build(): Config {
+            public fun build(): Config {
                 if (prefetchDistance < 0) {
                     prefetchDistance = pageSize
                 }
@@ -840,11 +861,11 @@
      * @param T Type loaded by the [PagedList].
      */
     @MainThread
-    abstract class BoundaryCallback<T : Any> {
+    public abstract class BoundaryCallback<T : Any> {
         /**
          * Called when zero items are returned from an initial load of the PagedList's data source.
          */
-        open fun onZeroItemsLoaded() {}
+        public open fun onZeroItemsLoaded() {}
 
         /**
          * Called when the item at the front of the PagedList has been loaded, and access has
@@ -854,7 +875,7 @@
          *
          * @param itemAtFront The first item of PagedList
          */
-        open fun onItemAtFrontLoaded(itemAtFront: T) {}
+        public open fun onItemAtFrontLoaded(itemAtFront: T) {}
 
         /**
          * Called when the item at the end of the PagedList has been loaded, and access has
@@ -864,19 +885,19 @@
          *
          * @param itemAtEnd The first item of [PagedList]
          */
-        open fun onItemAtEndLoaded(itemAtEnd: T) {}
+        public open fun onItemAtEndLoaded(itemAtEnd: T) {}
     }
 
     /**
      * @suppress
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    abstract class LoadStateManager {
-        var refreshState: LoadState = LoadState.NotLoading.Incomplete
-        var startState: LoadState = LoadState.NotLoading.Incomplete
-        var endState: LoadState = LoadState.NotLoading.Incomplete
+    public abstract class LoadStateManager {
+        public var refreshState: LoadState = LoadState.NotLoading.Incomplete
+        public var startState: LoadState = LoadState.NotLoading.Incomplete
+        public var endState: LoadState = LoadState.NotLoading.Incomplete
 
-        fun setState(type: LoadType, state: LoadState) {
+        public fun setState(type: LoadType, state: LoadState) {
             // deduplicate signals
             when (type) {
                 LoadType.REFRESH -> {
@@ -900,9 +921,9 @@
          * @suppress
          */
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // protected otherwise.
-        abstract fun onStateChanged(type: LoadType, state: LoadState)
+        public abstract fun onStateChanged(type: LoadType, state: LoadState)
 
-        fun dispatchCurrentLoadState(callback: (LoadType, LoadState) -> Unit) {
+        public fun dispatchCurrentLoadState(callback: (LoadType, LoadState) -> Unit) {
             callback(LoadType.REFRESH, refreshState)
             callback(LoadType.PREPEND, startState)
             callback(LoadType.APPEND, endState)
@@ -913,7 +934,7 @@
      * @suppress
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // protected otherwise
-    fun getNullPaddedList(): NullPaddedList<T> = storage
+    public fun getNullPaddedList(): NullPaddedList<T> = storage
 
     internal var refreshRetryCallback: Runnable? = null
 
@@ -925,7 +946,7 @@
      * @suppress
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    fun lastLoad(): Int = storage.lastLoadAroundIndex
+    public fun lastLoad(): Int = storage.lastLoadAroundIndex
 
     internal val requiredRemainder = config.prefetchDistance * 2 + config.pageSize
 
@@ -940,7 +961,7 @@
      *
      * @see loadedCount
      */
-    override val size
+    override val size: Int
         get() = storage.size
 
     /**
@@ -952,7 +973,7 @@
             "offers indirect ways of controlling fetch ('loadAround()', 'retry()') so that " +
             "you should not need to access the DataSource/PagingSource."
     )
-    val dataSource: DataSource<*, T>
+    public val dataSource: DataSource<*, T>
         @Suppress("DocumentExceptions")
         get() {
             val pagingSource = pagingSource
@@ -976,7 +997,7 @@
      *
      * @return Key of position most recently passed to [loadAround].
      */
-    abstract val lastKey: Any?
+    public abstract val lastKey: Any?
 
     /**
      * True if the [PagedList] has detached the [PagingSource] it was loading from, and will no
@@ -986,19 +1007,19 @@
      *
      * @return `true` if the data source is detached.
      */
-    abstract val isDetached: Boolean
+    public abstract val isDetached: Boolean
 
     /**
      * @suppress
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
-    abstract fun dispatchCurrentLoadState(callback: (LoadType, LoadState) -> Unit)
+    public abstract fun dispatchCurrentLoadState(callback: (LoadType, LoadState) -> Unit)
 
     /**
      * @suppress
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
-    abstract fun loadAroundInternal(index: Int)
+    public abstract fun loadAroundInternal(index: Int)
 
     /**
      * Detach the [PagedList] from its [PagingSource], and attempt to load no more data.
@@ -1007,7 +1028,7 @@
      * signal to stop loading. The [PagedList] will continue to present existing data, but will not
      * initiate new loads.
      */
-    abstract fun detach()
+    public abstract fun detach()
 
     /**
      * Returns the number of items loaded in the [PagedList].
@@ -1021,7 +1042,7 @@
      *
      * @see size
      */
-    val loadedCount
+    public val loadedCount: Int
         get() = storage.storageCount
 
     /**
@@ -1034,7 +1055,7 @@
      *
      * @return `true` if the [PagedList] is immutable.
      */
-    open val isImmutable
+    public open val isImmutable: Boolean
         get() = isDetached
 
     /**
@@ -1046,14 +1067,14 @@
      * If placeholders are enabled, this value is always `0`, since `get(i)` will return either
      * the data in its original index, or null if it is not loaded.
      */
-    val positionOffset: Int
+    public val positionOffset: Int
         get() = storage.positionOffset
 
     /**
      * @suppress
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    open fun setInitialLoadState(loadType: LoadType, loadState: LoadState) {
+    public open fun setInitialLoadState(loadType: LoadType, loadState: LoadState) {
     }
 
     /**
@@ -1069,13 +1090,13 @@
      * @see addWeakLoadStateListener
      * @see removeWeakLoadStateListener
      */
-    open fun retry() {}
+    public open fun retry() {}
 
     /**
      * @suppress
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
-    fun setRetryCallback(refreshRetryCallback: Runnable?) {
+    public fun setRetryCallback(refreshRetryCallback: Runnable?) {
         this.refreshRetryCallback = refreshRetryCallback
     }
 
@@ -1095,7 +1116,7 @@
      *
      * @see size
      */
-    override fun get(index: Int) = storage[index]
+    public override fun get(index: Int): T? = storage[index]
 
     /**
      * Load adjacent items to passed index.
@@ -1104,7 +1125,7 @@
      *
      * @throws IndexOutOfBoundsException if index is not within bounds.
      */
-    fun loadAround(index: Int) {
+    public fun loadAround(index: Int) {
         if (index < 0 || index >= size) {
             throw IndexOutOfBoundsException("Index: $index, Size: $size")
         }
@@ -1120,7 +1141,7 @@
      *
      * @return Immutable snapshot of [PagedList] data.
      */
-    fun snapshot(): List<T> = when {
+    public fun snapshot(): List<T> = when {
         isImmutable -> this
         else -> SnapshotPagedList(this)
     }
@@ -1132,7 +1153,7 @@
      *
      * @see removeWeakLoadStateListener
      */
-    fun addWeakLoadStateListener(listener: (LoadType, LoadState) -> Unit) {
+    public fun addWeakLoadStateListener(listener: (LoadType, LoadState) -> Unit) {
         // Clean up any empty weak refs.
         loadStateListeners.removeAll { it.get() == null }
 
@@ -1148,7 +1169,7 @@
      *
      * @see addWeakLoadStateListener
      */
-    fun removeWeakLoadStateListener(listener: (LoadType, LoadState) -> Unit) {
+    public fun removeWeakLoadStateListener(listener: (LoadType, LoadState) -> Unit) {
         loadStateListeners.removeAll { it.get() == null || it.get() === listener }
     }
 
@@ -1178,7 +1199,7 @@
             "tracked by attaching a Callback to the PagedList that is mutating, and tracking " +
             "changes since calling PagedList.snapshot()."
     )
-    fun addWeakCallback(previousSnapshot: List<T>?, callback: Callback) {
+    public fun addWeakCallback(previousSnapshot: List<T>?, callback: Callback) {
         if (previousSnapshot != null && previousSnapshot !== this) {
             dispatchNaiveUpdatesSinceSnapshot(size, previousSnapshot.size, callback)
         }
@@ -1198,7 +1219,7 @@
      * @see removeWeakCallback
      */
     @Suppress("RegistrationName")
-    fun addWeakCallback(callback: Callback) {
+    public fun addWeakCallback(callback: Callback) {
         // first, clean up any empty weak refs
         callbacks.removeAll { it.get() == null }
 
@@ -1214,7 +1235,7 @@
      * @see addWeakCallback
      */
     @Suppress("RegistrationName")
-    fun removeWeakCallback(callback: Callback) {
+    public fun removeWeakCallback(callback: Callback) {
         callbacks.removeAll { it.get() == null || it.get() === callback }
     }
 
@@ -1227,7 +1248,7 @@
      * @suppress
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
-    fun notifyChanged(position: Int, count: Int) {
+    public fun notifyChanged(position: Int, count: Int) {
         if (count == 0) return
         callbacks.reversed().forEach { it.get()?.onChanged(position, count) }
     }
@@ -1236,7 +1257,7 @@
      * @suppress
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
-    fun notifyRemoved(position: Int, count: Int) {
+    public fun notifyRemoved(position: Int, count: Int) {
         if (count == 0) return
         callbacks.reversed().forEach { it.get()?.onRemoved(position, count) }
     }
@@ -1262,7 +1283,7 @@
 )
 @JvmSynthetic
 @Deprecated("DataSource is deprecated and has been replaced by PagingSource")
-fun <Key : Any, Value : Any> PagedList(
+public fun <Key : Any, Value : Any> PagedList(
     dataSource: DataSource<Key, Value>,
     config: PagedList.Config,
     notifyExecutor: Executor,
@@ -1270,7 +1291,6 @@
     boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
     initialKey: Key? = null
 ): PagedList<Value> {
-    @Suppress("DEPRECATION")
     return PagedList.Builder(dataSource, config)
         .setNotifyExecutor(notifyExecutor)
         .setFetchExecutor(fetchExecutor)
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedListConfig.kt b/paging/common/src/main/kotlin/androidx/paging/PagedListConfig.kt
index 77e3f62..0e4645f 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagedListConfig.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedListConfig.kt
@@ -31,7 +31,7 @@
     "DEPRECATION"
 )
 @JvmSynthetic
-fun Config(
+public fun Config(
     pageSize: Int,
     prefetchDistance: Int = pageSize,
     enablePlaceholders: Boolean = true,
diff --git a/paging/common/src/main/kotlin/androidx/paging/Pager.kt b/paging/common/src/main/kotlin/androidx/paging/Pager.kt
index 8f2483d..3513a38 100644
--- a/paging/common/src/main/kotlin/androidx/paging/Pager.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/Pager.kt
@@ -37,7 +37,7 @@
  * RxJava support is available as extension properties provided by the
  * `androidx.paging:paging-rxjava2` artifact.
  */
-class Pager<Key : Any, Value : Any>
+public class Pager<Key : Any, Value : Any>
 // Experimental usage is propagated to public API via constructor argument.
 @ExperimentalPagingApi constructor(
     config: PagingConfig,
@@ -48,7 +48,7 @@
     // Experimental usage is internal, so opt-in is allowed here.
     @JvmOverloads
     @OptIn(ExperimentalPagingApi::class)
-    constructor(
+    public constructor(
         config: PagingConfig,
         initialKey: Key? = null,
         pagingSourceFactory: () -> PagingSource<Key, Value>
@@ -59,7 +59,7 @@
      * invalidated by [PagingSource.invalidate] or calls to [AsyncPagingDataDiffer.refresh] or
      * [PagingDataAdapter.refresh].
      */
-    val flow: Flow<PagingData<Value>> = PageFetcher(
+    public val flow: Flow<PagingData<Value>> = PageFetcher(
         pagingSourceFactory = if (
             pagingSourceFactory is SuspendingPagingSourceFactory<Key, Value>
         ) {
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingConfig.kt b/paging/common/src/main/kotlin/androidx/paging/PagingConfig.kt
index 14b74ea..5b091ac 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingConfig.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingConfig.kt
@@ -24,7 +24,7 @@
  * An object used to configure loading behavior within a [Pager], as it loads content from a
  * [PagingSource].
  */
-class PagingConfig @JvmOverloads constructor(
+public class PagingConfig @JvmOverloads public constructor(
     /**
      * Defines the number of items loaded at once from the [PagingSource].
      *
@@ -45,7 +45,7 @@
      * [Page][PagingSource.LoadResult.Page].
      */
     @JvmField
-    val pageSize: Int,
+    public val pageSize: Int,
 
     /**
      * Prefetch distance which defines how far from the edge of loaded content an access must be to
@@ -61,7 +61,7 @@
      */
     @JvmField
     @IntRange(from = 0)
-    val prefetchDistance: Int = pageSize,
+    public val prefetchDistance: Int = pageSize,
 
     /**
      * Defines whether [PagingData] may display `null` placeholders, if the [PagingSource]
@@ -76,7 +76,7 @@
      * 2) [enablePlaceholders] is set to `true`
      */
     @JvmField
-    val enablePlaceholders: Boolean = true,
+    public val enablePlaceholders: Boolean = true,
 
     /**
      * Defines requested load size for initial load from [PagingSource], typically larger than
@@ -89,7 +89,7 @@
      */
     @JvmField
     @IntRange(from = 1)
-    val initialLoadSize: Int = pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER,
+    public val initialLoadSize: Int = pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER,
     /**
      * Defines the maximum number of items that may be loaded into [PagingData] before pages should
      * be dropped.
@@ -115,7 +115,7 @@
      */
     @JvmField
     @IntRange(from = 2)
-    val maxSize: Int = MAX_SIZE_UNBOUNDED,
+    public val maxSize: Int = MAX_SIZE_UNBOUNDED,
 
     /**
      * Defines a threshold for the number of items scrolled outside the bounds of loaded items
@@ -131,7 +131,7 @@
      * @see PagingSource.jumpingSupported
      */
     @JvmField
-    val jumpThreshold: Int = COUNT_UNDEFINED
+    public val jumpThreshold: Int = COUNT_UNDEFINED
 ) {
     init {
         if (!enablePlaceholders && prefetchDistance == 0) {
@@ -154,13 +154,13 @@
         }
     }
 
-    companion object {
+    public companion object {
         /**
          * When [maxSize] is set to [MAX_SIZE_UNBOUNDED], the maximum number of items loaded is
          * unbounded, and pages will never be dropped.
          */
         @Suppress("MinMaxConstant")
-        const val MAX_SIZE_UNBOUNDED = Int.MAX_VALUE
+        public const val MAX_SIZE_UNBOUNDED: Int = Int.MAX_VALUE
         internal const val DEFAULT_INITIAL_PAGE_MULTIPLIER = 3
     }
 }
\ No newline at end of file
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingData.kt b/paging/common/src/main/kotlin/androidx/paging/PagingData.kt
index 7a320d3..d408608 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingData.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingData.kt
@@ -25,12 +25,12 @@
  * Each refresh of data (generally either pushed by local storage, or pulled from the network)
  * will have a separate corresponding [PagingData].
  */
-class PagingData<T : Any> internal constructor(
+public class PagingData<T : Any> internal constructor(
     internal val flow: Flow<PageEvent<T>>,
     internal val receiver: UiReceiver
 ) {
 
-    companion object {
+    public companion object {
         internal val NOOP_RECEIVER = object : UiReceiver {
             override fun accessHint(viewportHint: ViewportHint) {}
 
@@ -51,7 +51,7 @@
          */
         @Suppress("UNCHECKED_CAST")
         @JvmStatic // Convenience for Java developers.
-        fun <T : Any> empty() = EMPTY as PagingData<T>
+        public fun <T : Any> empty(): PagingData<T> = EMPTY as PagingData<T>
 
         /**
          * Create a [PagingData] that immediately displays a static list of items when submitted to
@@ -60,7 +60,7 @@
          * @param data Static list of [T] to display.
          */
         @JvmStatic // Convenience for Java developers.
-        fun <T : Any> from(data: List<T>) = PagingData(
+        public fun <T : Any> from(data: List<T>): PagingData<T> = PagingData(
             flow = flowOf(
                 PageEvent.Insert.Refresh(
                     pages = listOf(TransformablePage(originalPageOffset = 0, data = data)),
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt b/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
index fd24eba..7f6b49e 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
@@ -33,7 +33,7 @@
 
 /** @suppress */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-abstract class PagingDataDiffer<T : Any>(
+public abstract class PagingDataDiffer<T : Any>(
     private val differCallback: DifferCallback,
     private val mainDispatcher: CoroutineDispatcher = Dispatchers.Main
 ) {
@@ -112,7 +112,7 @@
      * result between [previousList] and [newList]. Null if [newList] or [previousList] lists are
      * empty, where it does not make sense to transform [lastAccessedIndex].
      */
-    abstract suspend fun presentNewList(
+    public abstract suspend fun presentNewList(
         previousList: NullPaddedList<T>,
         newList: NullPaddedList<T>,
         newCombinedLoadStates: CombinedLoadStates,
@@ -120,106 +120,112 @@
         onListPresentable: () -> Unit,
     ): Int?
 
-    open fun postEvents(): Boolean = false
+    public open fun postEvents(): Boolean = false
 
-    suspend fun collectFrom(pagingData: PagingData<T>) = collectFromRunner.runInIsolation {
-        receiver = pagingData.receiver
+    public suspend fun collectFrom(pagingData: PagingData<T>) {
+        collectFromRunner.runInIsolation {
+            receiver = pagingData.receiver
 
-        // TODO: Validate only empty pages between separator pages and its dependent pages.
-        pagingData.flow.collect { event ->
-            withContext<Unit>(mainDispatcher) {
-                if (event is PageEvent.Insert && event.loadType == REFRESH) {
-                    lastAccessedIndexUnfulfilled = false
-
-                    val newPresenter = PagePresenter(event)
-                    var >
-                    val transformedLastAccessedIndex = presentNewList(
-                        previousList = presenter,
-                        newList = newPresenter,
-                        newCombinedLoadStates = event.combinedLoadStates,
-                        lastAccessedIndex = lastAccessedIndex,
-                        >
-                            presenter = newPresenter
-                            >
-                        }
-                    )
-                    check(onListPresentableCalled) {
-                        "Missing call to onListPresentable after new list was presented. If you " +
-                            "are seeing this exception, it is generally an indication of an " +
-                            "issue with Paging. Please file a bug so we can fix it at: " +
-                            "https://issuetracker.google.com/issues/new?component=413106"
-                    }
-
-                    // Dispatch LoadState updates as soon as we are done diffing, but after setting
-                    // presenter.
-                    dispatchLoadStates(event.combinedLoadStates)
-
-                    if (transformedLastAccessedIndex == null) {
-                        // Send an initialize hint in case the new list is empty, which would
-                        // prevent a ViewportHint.Access from ever getting sent since there are
-                        // no items to bind from initial load.
-                        receiver?.accessHint(newPresenter.initializeHint())
-                    } else {
-                        // Transform the last loadAround index from the old list to the new list
-                        // by passing it through the DiffResult, and pass it forward as a
-                        // ViewportHint within the new list to the next generation of Pager.
-                        // This ensures prefetch distance for the last ViewportHint from the old
-                        // list is respected in the new list, even if invalidation interrupts
-                        // the prepend / append load that would have fulfilled it in the old
-                        // list.
-                        lastAccessedIndex = transformedLastAccessedIndex
-                        receiver?.accessHint(
-                            newPresenter.accessHintForPresenterIndex(transformedLastAccessedIndex)
-                        )
-                    }
-                } else {
-                    if (postEvents()) {
-                        yield()
-                    }
-
-                    // Send event to presenter to be shown to the UI.
-                    presenter.processEvent(event, processPageEventCallback)
-
-                    // Reset lastAccessedIndexUnfulfilled if a page is dropped, to avoid infinite
-                    // loops when maxSize is insufficiently large.
-                    if (event is PageEvent.Drop) {
+            // TODO: Validate only empty pages between separator pages and its dependent pages.
+            pagingData.flow.collect { event ->
+                withContext<Unit>(mainDispatcher) {
+                    if (event is PageEvent.Insert && event.loadType == REFRESH) {
                         lastAccessedIndexUnfulfilled = false
-                    }
 
-                    // If index points to a placeholder after transformations, resend it unless
-                    // there are no more items to load.
-                    if (event is PageEvent.Insert) {
-                        val prependDone =
-                            event.combinedLoadStates.prepend.endOfPaginationReached
-                        val appendDone = event.combinedLoadStates.append.endOfPaginationReached
-                        val canContinueLoading = !(event.loadType == PREPEND && prependDone) &&
-                            !(event.loadType == APPEND && appendDone)
+                        val newPresenter = PagePresenter(event)
+                        var >
+                        val transformedLastAccessedIndex = presentNewList(
+                            previousList = presenter,
+                            newList = newPresenter,
+                            newCombinedLoadStates = event.combinedLoadStates,
+                            lastAccessedIndex = lastAccessedIndex,
+                            >
+                                presenter = newPresenter
+                                >
+                            }
+                        )
+                        check(onListPresentableCalled) {
+                            "Missing call to onListPresentable after new list was presented. If " +
+                                "you are seeing this exception, it is generally an indication of " +
+                                "an issue with Paging. Please file a bug so we can fix it at: " +
+                                "https://issuetracker.google.com/issues/new?component=413106"
+                        }
 
-                        /**
-                         *  If the insert is empty due to aggressive filtering, another hint must be
-                         *  sent to fetcher-side to notify that PagingDataDiffer received the page,
-                         *  since fetcher estimates prefetchDistance based on page indices presented
-                         *  by PagingDataDiffer and we cannot rely on a new item being bound to
-                         *  trigger another hint since the presented page is empty.
-                         */
-                        val emptyInsert = event.pages.all { it.data.isEmpty() }
-                        if (!canContinueLoading) {
-                            // Reset lastAccessedIndexUnfulfilled since endOfPaginationReached
-                            // means there are no more pages to load that could fulfill this index.
-                            lastAccessedIndexUnfulfilled = false
-                        } else if (lastAccessedIndexUnfulfilled || emptyInsert) {
-                            val shouldResendHint = emptyInsert ||
-                                lastAccessedIndex < presenter.placeholdersBefore ||
-                                lastAccessedIndex > presenter.placeholdersBefore +
-                                presenter.storageCount
+                        // Dispatch LoadState updates as soon as we are done diffing, but after
+                        // setting presenter.
+                        dispatchLoadStates(event.combinedLoadStates)
 
-                            if (shouldResendHint) {
-                                receiver?.accessHint(
-                                    presenter.accessHintForPresenterIndex(lastAccessedIndex)
+                        if (transformedLastAccessedIndex == null) {
+                            // Send an initialize hint in case the new list is empty, which would
+                            // prevent a ViewportHint.Access from ever getting sent since there are
+                            // no items to bind from initial load.
+                            receiver?.accessHint(newPresenter.initializeHint())
+                        } else {
+                            // Transform the last loadAround index from the old list to the new list
+                            // by passing it through the DiffResult, and pass it forward as a
+                            // ViewportHint within the new list to the next generation of Pager.
+                            // This ensures prefetch distance for the last ViewportHint from the old
+                            // list is respected in the new list, even if invalidation interrupts
+                            // the prepend / append load that would have fulfilled it in the old
+                            // list.
+                            lastAccessedIndex = transformedLastAccessedIndex
+                            receiver?.accessHint(
+                                newPresenter.accessHintForPresenterIndex(
+                                    transformedLastAccessedIndex
                                 )
-                            } else {
-                                // lastIndex fulfilled, so reset lastAccessedIndexUnfulfilled.
+                            )
+                        }
+                    } else {
+                        if (postEvents()) {
+                            yield()
+                        }
+
+                        // Send event to presenter to be shown to the UI.
+                        presenter.processEvent(event, processPageEventCallback)
+
+                        // Reset lastAccessedIndexUnfulfilled if a page is dropped, to avoid
+                        // infinite loops when maxSize is insufficiently large.
+                        if (event is PageEvent.Drop) {
+                            lastAccessedIndexUnfulfilled = false
+                        }
+
+                        // If index points to a placeholder after transformations, resend it unless
+                        // there are no more items to load.
+                        if (event is PageEvent.Insert) {
+                            val prependDone =
+                                event.combinedLoadStates.prepend.endOfPaginationReached
+                            val appendDone = event.combinedLoadStates.append.endOfPaginationReached
+                            val canContinueLoading = !(event.loadType == PREPEND && prependDone) &&
+                                !(event.loadType == APPEND && appendDone)
+
+                            /**
+                             *  If the insert is empty due to aggressive filtering, another hint
+                             *  must be sent to fetcher-side to notify that PagingDataDiffer
+                             *  received the page, since fetcher estimates prefetchDistance based on
+                             *  page indices presented by PagingDataDiffer and we cannot rely on a
+                             *  new item being bound to trigger another hint since the presented
+                             *  page is empty.
+                             */
+                            val emptyInsert = event.pages.all { it.data.isEmpty() }
+                            if (!canContinueLoading) {
+                                // Reset lastAccessedIndexUnfulfilled since endOfPaginationReached
+                                // means there are no more pages to load that could fulfill this
+                                // index.
                                 lastAccessedIndexUnfulfilled = false
+                            } else if (lastAccessedIndexUnfulfilled || emptyInsert) {
+                                val shouldResendHint = emptyInsert ||
+                                    lastAccessedIndex < presenter.placeholdersBefore ||
+                                    lastAccessedIndex > presenter.placeholdersBefore +
+                                    presenter.storageCount
+
+                                if (shouldResendHint) {
+                                    receiver?.accessHint(
+                                        presenter.accessHintForPresenterIndex(lastAccessedIndex)
+                                    )
+                                } else {
+                                    // lastIndex fulfilled, so reset lastAccessedIndexUnfulfilled.
+                                    lastAccessedIndexUnfulfilled = false
+                                }
                             }
                         }
                     }
@@ -235,7 +241,7 @@
      * @param index Index of the presented item to return, including placeholders.
      * @return The presented item at position [index], `null` if it is a placeholder.
      */
-    operator fun get(@IntRange(from = 0) index: Int): T? {
+    public operator fun get(@IntRange(from = 0) index: Int): T? {
         lastAccessedIndexUnfulfilled = true
         lastAccessedIndex = index
 
@@ -250,7 +256,7 @@
      * @param index Index of the presented item to return, including placeholders.
      * @return The presented item at position [index], `null` if it is a placeholder
      */
-    fun peek(@IntRange(from = 0) index: Int): T? {
+    public fun peek(@IntRange(from = 0) index: Int): T? {
         return presenter.get(index)
     }
 
@@ -258,7 +264,7 @@
      * Returns a new [ItemSnapshotList] representing the currently presented items, including any
      * placeholders if they are enabled.
      */
-    fun snapshot(): ItemSnapshotList<T> = presenter.snapshot()
+    public fun snapshot(): ItemSnapshotList<T> = presenter.snapshot()
 
     /**
      * Retry any failed load requests that would result in a [LoadState.Error] update to this
@@ -271,7 +277,7 @@
      *  * [PagingSource.load] returning [PagingSource.LoadResult.Error]
      *  * [RemoteMediator.load] returning [RemoteMediator.MediatorResult.Error]
      */
-    fun retry() {
+    public fun retry() {
         receiver?.retry()
     }
 
@@ -291,14 +297,14 @@
      *
      * @sample androidx.paging.samples.refreshSample
      */
-    fun refresh() {
+    public fun refresh() {
         receiver?.refresh()
     }
 
     /**
      * @return Total number of presented items, including placeholders.
      */
-    val size: Int
+    public val size: Int
         get() = presenter.size
 
     private val _combinedLoadState = MutableStateFlow(combinedLoadStates.snapshot())
@@ -312,7 +318,7 @@
      *
      * @sample androidx.paging.samples.loadStateFlowSample
      */
-    val loadStateFlow: Flow<CombinedLoadStates>
+    public val loadStateFlow: Flow<CombinedLoadStates>
         get() = _combinedLoadState
 
     init {
@@ -333,7 +339,7 @@
      *
      * @sample androidx.paging.samples.addLoadStateListenerSample
      */
-    fun addLoadStateListener(listener: (CombinedLoadStates) -> Unit) {
+    public fun addLoadStateListener(listener: (CombinedLoadStates) -> Unit) {
         // Note: Important to add the listener first before sending off events, in case the
         // callback triggers removal, which could lead to a leak if the listener is added
         // afterwards.
@@ -347,7 +353,7 @@
      * @param listener Previously registered listener.
      * @see addLoadStateListener
      */
-    fun removeLoadStateListener(listener: (CombinedLoadStates) -> Unit) {
+    public fun removeLoadStateListener(listener: (CombinedLoadStates) -> Unit) {
         loadStateListeners.remove(listener)
     }
 }
@@ -362,8 +368,8 @@
  * @suppress
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-interface DifferCallback {
-    fun onChanged(position: Int, count: Int)
-    fun onInserted(position: Int, count: Int)
-    fun onRemoved(position: Int, count: Int)
+public interface DifferCallback {
+    public fun onChanged(position: Int, count: Int)
+    public fun onInserted(position: Int, count: Int)
+    public fun onRemoved(position: Int, count: Int)
 }
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingDataTransforms.kt b/paging/common/src/main/kotlin/androidx/paging/PagingDataTransforms.kt
index 836b86a..c867379 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingDataTransforms.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingDataTransforms.kt
@@ -38,7 +38,7 @@
  */
 @CheckResult
 @JvmSynthetic
-fun <T : Any, R : Any> PagingData<T>.map(
+public fun <T : Any, R : Any> PagingData<T>.map(
     transform: suspend (T) -> R
 ): PagingData<R> = transform { it.map(transform) }
 
@@ -49,7 +49,7 @@
  * @see PagingData.map
  */
 @CheckResult
-fun <T : Any, R : Any> PagingData<T>.map(
+public fun <T : Any, R : Any> PagingData<T>.map(
     executor: Executor,
     transform: (T) -> R,
 ): PagingData<R> = transform { event ->
@@ -64,7 +64,7 @@
  */
 @CheckResult
 @JvmSynthetic
-fun <T : Any, R : Any> PagingData<T>.flatMap(
+public fun <T : Any, R : Any> PagingData<T>.flatMap(
     transform: suspend (T) -> Iterable<R>
 ): PagingData<R> = transform { it.flatMap(transform) }
 
@@ -75,7 +75,7 @@
  * @see flatMap
  */
 @CheckResult
-fun <T : Any, R : Any> PagingData<T>.flatMap(
+public fun <T : Any, R : Any> PagingData<T>.flatMap(
     executor: Executor,
     transform: (T) -> Iterable<R>
 ): PagingData<R> = transform { event ->
@@ -89,7 +89,7 @@
  */
 @CheckResult
 @JvmSynthetic
-fun <T : Any> PagingData<T>.filter(
+public fun <T : Any> PagingData<T>.filter(
     predicate: suspend (T) -> Boolean
 ): PagingData<T> = transform { it.filter(predicate) }
 
@@ -100,7 +100,7 @@
  */
 @CheckResult
 @JvmName("filter")
-fun <T : Any> PagingData<T>.filter(
+public fun <T : Any> PagingData<T>.filter(
     executor: Executor,
     predicate: (T) -> Boolean
 ): PagingData<T> = transform { event ->
@@ -130,7 +130,7 @@
  */
 @CheckResult
 @JvmSynthetic
-fun <T : R, R : Any> PagingData<T>.insertSeparators(
+public fun <T : R, R : Any> PagingData<T>.insertSeparators(
     terminalSeparatorType: TerminalSeparatorType = FULLY_COMPLETE,
     generator: suspend (T?, T?) -> R?,
 ): PagingData<R> {
@@ -252,7 +252,7 @@
  */
 @CheckResult
 @JvmOverloads
-fun <R : Any, T : R> PagingData<T>.insertSeparators(
+public fun <R : Any, T : R> PagingData<T>.insertSeparators(
     terminalSeparatorType: TerminalSeparatorType = FULLY_COMPLETE,
     executor: Executor,
     generator: (T?, T?) -> R?,
@@ -287,10 +287,10 @@
  */
 @CheckResult
 @JvmOverloads
-fun <T : Any> PagingData<T>.insertHeaderItem(
+public fun <T : Any> PagingData<T>.insertHeaderItem(
     terminalSeparatorType: TerminalSeparatorType = FULLY_COMPLETE,
     item: T,
-) = insertSeparators(terminalSeparatorType) { before, _ ->
+): PagingData<T> = insertSeparators(terminalSeparatorType) { before, _ ->
     if (before == null) item else null
 }
 
@@ -317,9 +317,9 @@
  */
 @CheckResult
 @JvmOverloads
-fun <T : Any> PagingData<T>.insertFooterItem(
+public fun <T : Any> PagingData<T>.insertFooterItem(
     terminalSeparatorType: TerminalSeparatorType = FULLY_COMPLETE,
     item: T,
-) = insertSeparators(terminalSeparatorType) { _, after ->
+): PagingData<T> = insertSeparators(terminalSeparatorType) { _, after ->
     if (after == null) item else null
 }
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingSource.kt b/paging/common/src/main/kotlin/androidx/paging/PagingSource.kt
index bfeaf97..05aa243 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingSource.kt
@@ -23,17 +23,16 @@
 import java.util.concurrent.CopyOnWriteArrayList
 import java.util.concurrent.atomic.AtomicBoolean
 
-/**
- * @suppress
- */
+/** @suppress */
 @Suppress("DEPRECATION")
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-fun <Key : Any> PagedList.Config.toRefreshLoadParams(key: Key?): PagingSource.LoadParams<Key> =
-    PagingSource.LoadParams.Refresh(
-        key,
-        initialLoadSizeHint,
-        enablePlaceholders,
-    )
+public fun <Key : Any> PagedList.Config.toRefreshLoadParams(
+    key: Key?
+): PagingSource.LoadParams<Key> = PagingSource.LoadParams.Refresh(
+    key,
+    initialLoadSizeHint,
+    enablePlaceholders,
+)
 
 /**
  * Base class for an abstraction of pageable static data from some source, where loading pages
@@ -81,25 +80,25 @@
  *
  * @see Pager
  */
-abstract class PagingSource<Key : Any, Value : Any> {
+public abstract class PagingSource<Key : Any, Value : Any> {
 
     /**
      * Params for a load request on a [PagingSource] from [PagingSource.load].
      */
-    sealed class LoadParams<Key : Any> constructor(
+    public sealed class LoadParams<Key : Any> constructor(
         /**
          * Requested number of items to load.
          *
          * Note: It is valid for [PagingSource.load] to return a [LoadResult] that has a different
          * number of items than the requested load size.
          */
-        val loadSize: Int,
+        public val loadSize: Int,
         /**
          * From [PagingConfig.enablePlaceholders], true if placeholders are enabled and the load
          * request for this [LoadParams] should populate [LoadResult.Page.itemsBefore] and
          * [LoadResult.Page.itemsAfter] if possible.
          */
-        val placeholdersEnabled: Boolean,
+        public val placeholdersEnabled: Boolean,
     ) {
         /**
          * Key for the page to be loaded.
@@ -116,13 +115,13 @@
          *  * [Prepend] - [LoadResult.Page.prevKey] of the loaded page at the front of the list.
          *  * [Append] - [LoadResult.Page.nextKey] of the loaded page at the end of the list.
          */
-        abstract val key: Key?
+        public abstract val key: Key?
 
         /**
          * Params for an initial load request on a [PagingSource] from [PagingSource.load] or a
          * refresh triggered by [invalidate].
          */
-        class Refresh<Key : Any> constructor(
+        public class Refresh<Key : Any> constructor(
             override val key: Key?,
             loadSize: Int,
             placeholdersEnabled: Boolean,
@@ -135,7 +134,7 @@
          * Params to load a page of data from a [PagingSource] via [PagingSource.load] to be
          * appended to the end of the list.
          */
-        class Append<Key : Any> constructor(
+        public class Append<Key : Any> constructor(
             override val key: Key,
             loadSize: Int,
             placeholdersEnabled: Boolean,
@@ -148,7 +147,7 @@
          * Params to load a page of data from a [PagingSource] via [PagingSource.load] to be
          * prepended to the start of the list.
          */
-        class Prepend<Key : Any> constructor(
+        public class Prepend<Key : Any> constructor(
             override val key: Key,
             loadSize: Int,
             placeholdersEnabled: Boolean,
@@ -190,7 +189,7 @@
     /**
      * Result of a load request from [PagingSource.load].
      */
-    sealed class LoadResult<Key : Any, Value : Any> {
+    public sealed class LoadResult<Key : Any, Value : Any> {
         /**
          * Error result object for [PagingSource.load].
          *
@@ -200,7 +199,7 @@
          *
          * @sample androidx.paging.samples.pageKeyedPagingSourceSample
          */
-        data class Error<Key : Any, Value : Any>(
+        public data class Error<Key : Any, Value : Any>(
             val throwable: Throwable
         ) : LoadResult<Key, Value>()
 
@@ -210,7 +209,7 @@
          * @sample androidx.paging.samples.pageKeyedPage
          * @sample androidx.paging.samples.pageIndexedPage
          */
-        data class Page<Key : Any, Value : Any> constructor(
+        public data class Page<Key : Any, Value : Any> constructor(
             /**
              * Loaded data
              */
@@ -245,7 +244,7 @@
              * @param nextKey [Key] for next page if more data can be loaded in that direction,
              * `null` otherwise.
              */
-            constructor(
+            public constructor(
                 data: List<Value>,
                 prevKey: Key?,
                 nextKey: Key?
@@ -261,8 +260,8 @@
                 }
             }
 
-            companion object {
-                const val COUNT_UNDEFINED = Int.MIN_VALUE
+            public companion object {
+                public const val COUNT_UNDEFINED: Int = Int.MIN_VALUE
 
                 @Suppress("MemberVisibilityCanBePrivate") // Prevent synthetic accessor generation.
                 internal val EMPTY = Page(emptyList(), null, null, 0, 0)
@@ -286,14 +285,14 @@
      *
      * @see [PagingConfig.jumpThreshold]
      */
-    open val jumpingSupported: Boolean
+    public open val jumpingSupported: Boolean
         get() = false
 
     /**
      * `true` if this [PagingSource] expects to re-use keys to load distinct pages
      * without a call to [invalidate], `false` otherwise.
      */
-    open val keyReuseSupported: Boolean
+    public open val keyReuseSupported: Boolean
         get() = false
 
     @VisibleForTesting
@@ -305,7 +304,7 @@
      * Whether this [PagingSource] has been invalidated, which should happen when the data this
      * [PagingSource] represents changes since it was first instantiated.
      */
-    val invalid: Boolean
+    public val invalid: Boolean
         get() = _invalid.get()
 
     /**
@@ -314,7 +313,7 @@
      * This method is idempotent. i.e., If [invalidate] has already been called, subsequent calls to
      * this method should have no effect.
      */
-    fun invalidate() {
+    public fun invalidate() {
         if (_invalid.compareAndSet(false, true)) {
             onInvalidatedCallbacks.forEach { it.invoke() }
         }
@@ -331,7 +330,7 @@
      * @param onInvalidatedCallback The callback that will be invoked on thread that invalidates the
      * [PagingSource].
      */
-    fun registerInvalidatedCallback(onInvalidatedCallback: () -> Unit) {
+    public fun registerInvalidatedCallback(onInvalidatedCallback: () -> Unit) {
         onInvalidatedCallbacks.add(onInvalidatedCallback)
     }
 
@@ -340,7 +339,7 @@
      *
      * @param onInvalidatedCallback The previously added callback.
      */
-    fun unregisterInvalidatedCallback(onInvalidatedCallback: () -> Unit) {
+    public fun unregisterInvalidatedCallback(onInvalidatedCallback: () -> Unit) {
         onInvalidatedCallbacks.remove(onInvalidatedCallback)
     }
 
@@ -349,7 +348,7 @@
      *
      * Implement this method to trigger your async load (e.g. from database or network).
      */
-    abstract suspend fun load(params: LoadParams<Key>): LoadResult<Key, Value>
+    public abstract suspend fun load(params: LoadParams<Key>): LoadResult<Key, Value>
 
     /**
      * Provide a [Key] used for the initial [load] for the next [PagingSource] due to invalidation
@@ -376,5 +375,5 @@
      * user's current viewport. If the correct [Key] cannot be determined, `null` can be returned
      * to allow [load] decide what default key to use.
      */
-    abstract fun getRefreshKey(state: PagingState<Key, Value>): Key?
+    public abstract fun getRefreshKey(state: PagingState<Key, Value>): Key?
 }
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingState.kt b/paging/common/src/main/kotlin/androidx/paging/PagingState.kt
index df2899d..f599b9bf 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingState.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingState.kt
@@ -23,22 +23,22 @@
  * Snapshot state of Paging system including the loaded [pages], the last accessed [anchorPosition],
  * and the [config] used.
  */
-class PagingState<Key : Any, Value : Any> constructor(
+public class PagingState<Key : Any, Value : Any> constructor(
     /**
      * Loaded pages of data in the list.
      */
-    val pages: List<Page<Key, Value>>,
+    public val pages: List<Page<Key, Value>>,
     /**
      * Most recently accessed index in the list, including placeholders.
      *
      * `null` if no access in the [PagingData] has been made yet. E.g., if this snapshot was
      * generated before or during the first load.
      */
-    val anchorPosition: Int?,
+    public val anchorPosition: Int?,
     /**
      * [PagingConfig] that was given when initializing the [PagingData] stream.
      */
-    val config: PagingConfig,
+    public val config: PagingConfig,
     /**
      * Number of placeholders before the first loaded item if placeholders are enabled, otherwise 0.
      */
@@ -70,7 +70,7 @@
      * @return The closest loaded [Value] in [pages] to the provided [anchorPosition]. `null` if
      * all loaded [pages] are empty.
      */
-    fun closestItemToPosition(anchorPosition: Int): Value? {
+    public fun closestItemToPosition(anchorPosition: Int): Value? {
         if (pages.all { it.data.isEmpty() }) return null
 
         anchorPositionToPagedIndices(anchorPosition) { pageIndex, index ->
@@ -97,7 +97,7 @@
      * @return The closest loaded [Value] in [pages] to the provided [anchorPosition]. `null` if
      * all loaded [pages] are empty.
      */
-    fun closestPageToPosition(anchorPosition: Int): Page<Key, Value>? {
+    public fun closestPageToPosition(anchorPosition: Int): Page<Key, Value>? {
         if (pages.all { it.data.isEmpty() }) return null
 
         anchorPositionToPagedIndices(anchorPosition) { pageIndex, index ->
@@ -112,19 +112,23 @@
      * @return `true` if all loaded pages are empty or no pages were loaded when this [PagingState]
      * was created, `false` otherwise.
      */
-    fun isEmpty() = pages.all { it.data.isEmpty() }
+    public fun isEmpty(): Boolean = pages.all { it.data.isEmpty() }
 
     /**
      * @return The first loaded item in the list or `null` if all loaded pages are empty or no pages
      * were loaded when this [PagingState] was created.
      */
-    fun firstItemOrNull(): Value? = pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
+    public fun firstItemOrNull(): Value? {
+        return pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
+    }
 
     /**
      * @return The last loaded item in the list or `null` if all loaded pages are empty or no pages
      * were loaded when this [PagingState] was created.
      */
-    fun lastItemOrNull(): Value? = pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
+    public fun lastItemOrNull(): Value? {
+        return pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
+    }
 
     override fun toString(): String {
         return "PagingState(pages=$pages, anchorPosition=$anchorPosition, config=$config, " +
diff --git a/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt
index f8ee981..3e36a62 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt
@@ -53,12 +53,12 @@
         "androidx.paging.PagingSource"
     )
 )
-abstract class PositionalDataSource<T : Any> : DataSource<Int, T>(POSITIONAL) {
+public abstract class PositionalDataSource<T : Any> : DataSource<Int, T>(POSITIONAL) {
 
     /**
      * Holder object for inputs to [loadInitial].
      */
-    open class LoadInitialParams(
+    public open class LoadInitialParams(
         /**
          * Initial load position requested.
          *
@@ -66,27 +66,27 @@
          * before you execute your load.
          */
         @JvmField
-        val requestedStartPosition: Int,
+        public val requestedStartPosition: Int,
         /**
          * Requested number of items to load.
          *
          * Note that this may be larger than available data.
          */
         @JvmField
-        val requestedLoadSize: Int,
+        public val requestedLoadSize: Int,
         /**
          * Defines page size acceptable for return values.
          *
          * List of items passed to the callback must be an integer multiple of page size.
          */
         @JvmField
-        val pageSize: Int,
+        public val pageSize: Int,
         /**
          * Defines whether placeholders are enabled, and whether the loaded total count will be
          * ignored.
          */
         @JvmField
-        val placeholdersEnabled: Boolean
+        public val placeholdersEnabled: Boolean
     ) {
         init {
             check(requestedStartPosition >= 0) {
@@ -104,21 +104,21 @@
     /**
      * Holder object for inputs to [loadRange].
      */
-    open class LoadRangeParams(
+    public open class LoadRangeParams(
         /**
          * START position of data to load.
          *
          * Returned data must start at this position.
          */
         @JvmField
-        val startPosition: Int,
+        public val startPosition: Int,
         /**
          * Number of items to load.
          *
          * Returned data must be of this size, unless at end of the list.
          */
         @JvmField
-        val loadSize: Int
+        public val loadSize: Int
     )
 
     /**
@@ -132,7 +132,7 @@
      *
      * @param T Type of items being loaded.
      */
-    abstract class LoadInitialCallback<T> {
+    public abstract class LoadInitialCallback<T> {
         /**
          * Called to pass initial load state from a DataSource.
          *
@@ -150,7 +150,7 @@
          * Includes the number in the initial [data] parameter as well as any items that can be
          * loaded in front or behind of [data].
          */
-        abstract fun onResult(data: List<T>, position: Int, totalCount: Int)
+        public abstract fun onResult(data: List<T>, position: Int, totalCount: Int)
 
         /**
          * Called to pass initial load state from a DataSource without total count, when
@@ -168,7 +168,7 @@
          * @param position Position of the item at the front of the list. If there are N items
          * before the items in data that can be provided by this [DataSource], pass N.
          */
-        abstract fun onResult(data: List<T>, position: Int)
+        public abstract fun onResult(data: List<T>, position: Int)
     }
 
     /**
@@ -182,21 +182,21 @@
      *
      * @param T Type of items being loaded.
      */
-    abstract class LoadRangeCallback<T> {
+    public abstract class LoadRangeCallback<T> {
         /**
          * Called to pass loaded data from [loadRange].
          *
          * @param data List of items loaded from the [DataSource]. Must be same size as requested,
          * unless at end of list.
          */
-        abstract fun onResult(data: List<T>)
+        public abstract fun onResult(data: List<T>)
     }
 
     /**
      * @suppress
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
-    companion object {
+    public companion object {
         /**
          * Helper for computing an initial position in [loadInitial] when total data set size can be
          * computed ahead of loading.
@@ -240,7 +240,7 @@
          * @see [computeInitialLoadSize]
          */
         @JvmStatic
-        fun computeInitialLoadPosition(params: LoadInitialParams, totalCount: Int): Int {
+        public fun computeInitialLoadPosition(params: LoadInitialParams, totalCount: Int): Int {
             val position = params.requestedStartPosition
             val initialLoadSize = params.requestedLoadSize
             val pageSize = params.pageSize
@@ -302,11 +302,11 @@
          * @see [computeInitialLoadPosition]
          */
         @JvmStatic
-        fun computeInitialLoadSize(
+        public fun computeInitialLoadSize(
             params: LoadInitialParams,
             initialLoadPosition: Int,
             totalCount: Int
-        ) = minOf(totalCount - initialLoadPosition, params.requestedLoadSize)
+        ): Int = minOf(totalCount - initialLoadPosition, params.requestedLoadSize)
     }
 
     @Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
@@ -462,7 +462,7 @@
      * set size.
      */
     @WorkerThread
-    abstract fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>)
+    public abstract fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>)
 
     /**
      * Called to load a range of data from the DataSource.
@@ -477,7 +477,7 @@
      * @param callback Callback that receives loaded data.
      */
     @WorkerThread
-    abstract fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>)
+    public abstract fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>)
 
     @Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
     internal override val isContiguous = false
diff --git a/paging/common/src/main/kotlin/androidx/paging/RemoteMediator.kt b/paging/common/src/main/kotlin/androidx/paging/RemoteMediator.kt
index 706f33e..492b88c 100644
--- a/paging/common/src/main/kotlin/androidx/paging/RemoteMediator.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/RemoteMediator.kt
@@ -40,7 +40,7 @@
  * @sample androidx.paging.samples.remoteMediatorPageKeyedSample
  */
 @ExperimentalPagingApi
-abstract class RemoteMediator<Key : Any, Value : Any> {
+public abstract class RemoteMediator<Key : Any, Value : Any> {
     /**
      * Callback triggered when Paging needs to request more data from a remote source due to any of
      * the following events:
@@ -85,7 +85,10 @@
      * @return [MediatorResult] signifying what [LoadState] to be passed to the UI, and whether
      * there's more data available.
      */
-    abstract suspend fun load(loadType: LoadType, state: PagingState<Key, Value>): MediatorResult
+    public abstract suspend fun load(
+        loadType: LoadType,
+        state: PagingState<Key, Value>
+    ): MediatorResult
 
     /**
      * Callback fired during initialization of a [PagingData] stream, before initial load.
@@ -101,16 +104,16 @@
      *  * [SKIP_INITIAL_REFRESH] to wait for a refresh request from the UI before dispatching [load]
      *  asynchronously with load type [REFRESH].
      */
-    open suspend fun initialize(): InitializeAction = LAUNCH_INITIAL_REFRESH
+    public open suspend fun initialize(): InitializeAction = LAUNCH_INITIAL_REFRESH
 
     /**
      * Return type of [load], which determines [LoadState].
      */
-    sealed class MediatorResult {
+    public sealed class MediatorResult {
         /**
          * Recoverable error that can be retried, sets the [LoadState] to [LoadState.Error].
          */
-        class Error(val throwable: Throwable) : MediatorResult()
+        public class Error(public val throwable: Throwable) : MediatorResult()
 
         /**
          * Success signaling that [LoadState] should be set to [LoadState.NotLoading] if
@@ -121,15 +124,15 @@
          * [PagingSource.invalidate] to allow [androidx.paging.PagingDataAdapter] to pick up new
          * items found by [load].
          */
-        class Success(
-            @get:JvmName("endOfPaginationReached") val endOfPaginationReached: Boolean
+        public class Success(
+            @get:JvmName("endOfPaginationReached") public val endOfPaginationReached: Boolean
         ) : MediatorResult()
     }
 
     /**
      * Return type of [initialize], which signals the action to take after [initialize] completes.
      */
-    enum class InitializeAction {
+    public enum class InitializeAction {
         /**
          * Immediately dispatch a [load] asynchronously with load type [REFRESH], to update
          * paginated content when the stream is initialized.
diff --git a/paging/common/src/main/kotlin/androidx/paging/Separators.kt b/paging/common/src/main/kotlin/androidx/paging/Separators.kt
index 87440c4..c9c3731 100644
--- a/paging/common/src/main/kotlin/androidx/paging/Separators.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/Separators.kt
@@ -32,7 +32,7 @@
  * Mode for configuring when terminal separators (header and footer) would be displayed by the
  * [insertSeparators], [insertHeaderItem] or [insertFooterItem] operators on [PagingData].
  */
-enum class TerminalSeparatorType {
+public enum class TerminalSeparatorType {
     /**
      * Show terminal separators (header and footer) when both [PagingSource] and [RemoteMediator]
      * reaches the end of pagination.
diff --git a/paging/common/src/test/kotlin/androidx/paging/PageFetcherSnapshotTest.kt b/paging/common/src/test/kotlin/androidx/paging/PageFetcherSnapshotTest.kt
index 1696612..16dd201 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PageFetcherSnapshotTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PageFetcherSnapshotTest.kt
@@ -29,6 +29,7 @@
 import androidx.paging.PageEvent.Insert.Companion.Refresh
 import androidx.paging.PageEvent.LoadStateUpdate
 import androidx.paging.PagingSource.LoadResult.Page
+import androidx.paging.RemoteMediatorMock.LoadEvent
 import androidx.paging.TestPagingSource.Companion.LOAD_ERROR
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CompletableDeferred
@@ -62,7 +63,6 @@
 import kotlin.test.assertFailsWith
 import kotlin.test.assertFalse
 import kotlin.test.assertNotNull
-import kotlin.test.assertNull
 import kotlin.test.assertTrue
 import kotlin.test.fail
 
@@ -1870,14 +1870,7 @@
     }
 
     @Test
-    fun refreshKeyInfo_nullHint() = testScope.runBlockingTest {
-        val pagingSource = pagingSourceFactory()
-        val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
-        assertNull(pager.refreshKeyInfo())
-    }
-
-    @Test
-    fun refreshKeyInfo_pagesEmpty() = testScope.runBlockingTest {
+    fun currentPagingState_pagesEmptyWithHint() = testScope.runBlockingTest {
         pauseDispatcher {
             val pagingSource = pagingSourceFactory()
             val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
@@ -1891,12 +1884,53 @@
                     originalPageOffsetLast = 0
                 )
             )
-            assertNull(pager.refreshKeyInfo())
+            assertThat(pager.currentPagingState()).isEqualTo(
+                PagingState<Int, Int>(
+                    pages = listOf(),
+                    anchorPosition = 0,
+                    config = config,
+                    leadingPlaceholderCount = 0
+                )
+            )
         }
     }
 
+    /**
+     * Verify we re-use previous PagingState for remote refresh if there are no pages loaded.
+     */
     @Test
-    fun refreshKeyInfo_loadedIndex() = testScope.runBlockingTest {
+    fun currentPagingState_ignoredOnEmptyPages() = testScope.runBlockingTest {
+        val remoteMediator = RemoteMediatorMock()
+        val pagingSource = pagingSourceFactory()
+        val pager = PageFetcherSnapshot(
+            initialKey = 50,
+            pagingSource = pagingSource,
+            config = config,
+            retryFlow = retryBus.flow,
+            remoteMediatorConnection = RemoteMediatorAccessor(testScope, remoteMediator)
+        )
+        pager.accessHint(
+            ViewportHint.Access(
+                pageOffset = 0,
+                indexInPage = 0,
+                presentedItemsBefore = 0,
+                presentedItemsAfter = 1,
+                originalPageOffsetFirst = 0,
+                originalPageOffsetLast = 0
+            )
+        )
+        assertThat(pager.currentPagingState()).isEqualTo(
+            PagingState<Int, Int>(
+                pages = listOf(),
+                anchorPosition = 0,
+                config = config,
+                leadingPlaceholderCount = 0
+            )
+        )
+    }
+
+    @Test
+    fun currentPagingState_loadedIndex() = testScope.runBlockingTest {
         pauseDispatcher {
             val pagingSource = pagingSourceFactory()
             val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
@@ -1915,17 +1949,17 @@
                     )
                 )
 
-                val refreshKeyInfo = pager.refreshKeyInfo()
-                assertNotNull(refreshKeyInfo)
-                assertEquals(51, refreshKeyInfo.anchorPosition)
+                val pagingState = pager.currentPagingState()
+                assertNotNull(pagingState)
+                assertEquals(51, pagingState.anchorPosition)
 
                 // Assert from anchorPosition in placeholdersBefore
-                assertEquals(50, refreshKeyInfo.closestItemToPosition(10))
+                assertEquals(50, pagingState.closestItemToPosition(10))
                 // Assert from anchorPosition in loaded indices
-                assertEquals(50, refreshKeyInfo.closestItemToPosition(50))
-                assertEquals(51, refreshKeyInfo.closestItemToPosition(51))
+                assertEquals(50, pagingState.closestItemToPosition(50))
+                assertEquals(51, pagingState.closestItemToPosition(51))
                 // Assert from anchorPosition in placeholdersAfter
-                assertEquals(51, refreshKeyInfo.closestItemToPosition(90))
+                assertEquals(51, pagingState.closestItemToPosition(90))
 
                 val loadedPage = Page(
                     data = listOf(50, 51),
@@ -1934,20 +1968,20 @@
                     itemsBefore = 50,
                     itemsAfter = 48
                 )
-                assertEquals(listOf(loadedPage), refreshKeyInfo.pages)
+                assertEquals(listOf(loadedPage), pagingState.pages)
                 // Assert from anchorPosition in placeholdersBefore
-                assertEquals(loadedPage, refreshKeyInfo.closestPageToPosition(10))
+                assertEquals(loadedPage, pagingState.closestPageToPosition(10))
                 // Assert from anchorPosition in loaded indices
-                assertEquals(loadedPage, refreshKeyInfo.closestPageToPosition(50))
-                assertEquals(loadedPage, refreshKeyInfo.closestPageToPosition(51))
+                assertEquals(loadedPage, pagingState.closestPageToPosition(50))
+                assertEquals(loadedPage, pagingState.closestPageToPosition(51))
                 // Assert from anchorPosition in placeholdersAfter
-                assertEquals(loadedPage, refreshKeyInfo.closestPageToPosition(90))
+                assertEquals(loadedPage, pagingState.closestPageToPosition(90))
             }
         }
     }
 
     @Test
-    fun refreshKeyInfo_placeholdersBefore() = testScope.runBlockingTest {
+    fun currentPagingState_placeholdersBefore() = testScope.runBlockingTest {
         pauseDispatcher {
             val pagingSource = pagingSourceFactory()
             val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
@@ -1966,9 +2000,9 @@
                     )
                 )
 
-                val refreshKeyInfo = pager.refreshKeyInfo()
-                assertNotNull(refreshKeyInfo)
-                assertEquals(10, refreshKeyInfo.anchorPosition)
+                val pagingState = pager.currentPagingState()
+                assertNotNull(pagingState)
+                assertEquals(10, pagingState.anchorPosition)
                 assertEquals(
                     listOf(
                         Page(
@@ -1979,16 +2013,16 @@
                             itemsAfter = 48
                         )
                     ),
-                    refreshKeyInfo.pages
+                    pagingState.pages
                 )
 
                 // Assert from anchorPosition in placeholdersBefore
-                assertEquals(50, refreshKeyInfo.closestItemToPosition(10))
+                assertEquals(50, pagingState.closestItemToPosition(10))
                 // Assert from anchorPosition in loaded indices
-                assertEquals(50, refreshKeyInfo.closestItemToPosition(50))
-                assertEquals(51, refreshKeyInfo.closestItemToPosition(51))
+                assertEquals(50, pagingState.closestItemToPosition(50))
+                assertEquals(51, pagingState.closestItemToPosition(51))
                 // Assert from anchorPosition in placeholdersAfter
-                assertEquals(51, refreshKeyInfo.closestItemToPosition(90))
+                assertEquals(51, pagingState.closestItemToPosition(90))
 
                 val loadedPage = Page(
                     data = listOf(50, 51),
@@ -1998,18 +2032,18 @@
                     itemsAfter = 48
                 )
                 // Assert from anchorPosition in placeholdersBefore
-                assertEquals(loadedPage, refreshKeyInfo.closestPageToPosition(10))
+                assertEquals(loadedPage, pagingState.closestPageToPosition(10))
                 // Assert from anchorPosition in loaded indices
-                assertEquals(loadedPage, refreshKeyInfo.closestPageToPosition(50))
-                assertEquals(loadedPage, refreshKeyInfo.closestPageToPosition(51))
+                assertEquals(loadedPage, pagingState.closestPageToPosition(50))
+                assertEquals(loadedPage, pagingState.closestPageToPosition(51))
                 // Assert from anchorPosition in placeholdersAfter
-                assertEquals(loadedPage, refreshKeyInfo.closestPageToPosition(90))
+                assertEquals(loadedPage, pagingState.closestPageToPosition(90))
             }
         }
     }
 
     @Test
-    fun pageFetcherSnapshot_currentPagingState() = testScope.runBlockingTest {
+    fun currentPagingState_noHint() = testScope.runBlockingTest {
         val pager = PageFetcherSnapshot(
             initialKey = 50,
             pagingSource = TestPagingSource(loadDelay = 100),
@@ -2017,7 +2051,14 @@
             retryFlow = retryBus.flow
         )
 
-        assertEquals(null, pager.refreshKeyInfo())
+        assertThat(pager.currentPagingState()).isEqualTo(
+            PagingState<Int, Int>(
+                pages = listOf(),
+                anchorPosition = null,
+                config = config,
+                leadingPlaceholderCount = 0,
+            )
+        )
     }
 
     @Test
@@ -2238,6 +2279,71 @@
     }
 
     @Test
+    fun remoteMediator_remoteRefreshCachesPreviousPagingState() = testScope.runBlockingTest {
+        @OptIn(ExperimentalPagingApi::class)
+        val remoteMediator = RemoteMediatorMock().apply {
+            initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
+            loadCallback = { _, _ -> RemoteMediator.MediatorResult.Success(true) }
+        }
+
+        val config = PagingConfig(
+            pageSize = 1,
+            prefetchDistance = 2,
+            enablePlaceholders = true,
+            initialLoadSize = 1,
+            maxSize = 5
+        )
+        val pager = PageFetcher(
+            initialKey = 0,
+            pagingSourceFactory = { TestPagingSource(items = listOf(0)) },
+            config = config,
+            remoteMediator = remoteMediator
+        )
+
+        val state = collectFetcherState(pager)
+
+        // Let the initial page load; loaded data should be [0]
+        advanceUntilIdle()
+        assertThat(remoteMediator.newLoadEvents).containsExactly(
+            LoadEvent<Int, Int>(
+                loadType = REFRESH,
+                state = PagingState<Int, Int>(
+                    pages = listOf(),
+                    anchorPosition = null,
+                    config = config,
+                    leadingPlaceholderCount = 0,
+                ),
+            )
+        )
+
+        // Explicit call to refresh, which should trigger remote refresh with cached PagingState.
+        pager.refresh()
+        advanceUntilIdle()
+
+        assertThat(remoteMediator.newLoadEvents).containsExactly(
+            LoadEvent<Int, Int>(
+                loadType = REFRESH,
+                state = PagingState<Int, Int>(
+                    pages = listOf(
+                        Page(
+                            data = listOf(0),
+                            prevKey = null,
+                            nextKey = null,
+                            itemsBefore = 0,
+                            itemsAfter = 0,
+                        ),
+                    ),
+                    anchorPosition = null,
+                    config = config,
+                    leadingPlaceholderCount = 0,
+                ),
+            )
+        )
+
+        state.job.cancel()
+    }
+
+    @Test
     fun remoteMediator_remoteRefreshEndOfPaginationReached() = testScope.runBlockingTest {
         @OptIn(ExperimentalPagingApi::class)
         val remoteMediator = RemoteMediatorMock().apply {
@@ -2963,10 +3069,12 @@
                 jumpThreshold = 10
             )
             var didJump = false
-            val pager = PageFetcherSnapshot(
+            val pager = PageFetcherSnapshot<Int, Int>(
                 initialKey = 50,
                 pagingSource = pagingSourceFactory(),
-                config = config, retryFlow = retryBus.flow
+                config = config,
+                retryFlow = retryBus.flow,
+                previousPagingState = null,
             ) {
                 didJump = true
             }
diff --git a/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt b/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt
index bad2b72..b095750 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt
@@ -504,7 +504,6 @@
         }
     }
 
-    @ExperimentalStdlibApi
     @Test
     fun pagingSourceInvalidBeforeCallbackAdded() = testScope.runBlockingTest {
         var invalidatesFromAdapter = 0
@@ -523,11 +522,11 @@
             }
         }
 
+        @OptIn(ExperimentalStdlibApi::class)
         val job = launch {
             pager.flow.collectLatest { pagingData ->
-                TestPagingDataDiffer<Int>(
-                    testScope.coroutineContext[CoroutineDispatcher.Key]!!
-                ).collectFrom(pagingData)
+                TestPagingDataDiffer<Int>(testScope.coroutineContext[CoroutineDispatcher]!!)
+                    .collectFrom(pagingData)
             }
         }
 
@@ -539,6 +538,208 @@
         job.cancel()
     }
 
+    @OptIn(ExperimentalPagingApi::class)
+    @Test
+    fun cachesPreviousPagingStateOnEmptyPages() = testScope.runBlockingTest {
+        val config = PagingConfig(
+            pageSize = 1,
+            prefetchDistance = 1,
+            enablePlaceholders = true,
+            initialLoadSize = 3,
+        )
+
+        val remoteMediator = RemoteMediatorMock().apply {
+            initializeResult = LAUNCH_INITIAL_REFRESH
+        }
+        val pageFetcher = PageFetcher(
+            pagingSourceFactory = suspend {
+                TestPagingSource(loadDelay = 1000)
+            },
+            initialKey = 50,
+            config = config,
+            remoteMediator = remoteMediator,
+        )
+
+        var receiver: UiReceiver? = null
+        val job = launch() {
+            pageFetcher.flow.collectLatest {
+                receiver = it.receiver
+                it.flow.collect { }
+            }
+        }
+
+        // Allow initial load to finish, so PagingState has non-zero pages.
+        advanceUntilIdle()
+
+        // Verify remote refresh is called with initial empty case.
+        assertThat(remoteMediator.newLoadEvents).containsExactly(
+            RemoteMediatorMock.LoadEvent(
+                loadType = REFRESH,
+                state = PagingState(
+                    pages = listOf(),
+                    anchorPosition = null,
+                    config = config,
+                    leadingPlaceholderCount = 0,
+                ),
+            )
+        )
+
+        // Trigger refresh, instantiating second generation.
+        pageFetcher.refresh()
+
+        // Allow remote refresh to get triggered, but do not let paging source complete initial load
+        // for second generation.
+        advanceTimeBy(500)
+
+        // Verify remote refresh is called with PagingState from first generation.
+        val pagingState = PagingState(
+            pages = listOf(
+                PagingSource.LoadResult.Page(
+                    data = listOf(50, 51, 52),
+                    prevKey = 49,
+                    nextKey = 53,
+                    itemsBefore = 50,
+                    itemsAfter = 47,
+                )
+            ),
+            anchorPosition = null,
+            config = config,
+            leadingPlaceholderCount = 50,
+        )
+        assertThat(remoteMediator.newLoadEvents).containsExactly(
+            RemoteMediatorMock.LoadEvent(loadType = REFRESH, state = pagingState)
+        )
+
+        // Trigger a hint, which would normally populate anchorPosition. In real world scenario,
+        // this would happen as a result of UI still presenting first generation since second
+        // generation never finished loading yet.
+        receiver?.accessHint(
+            ViewportHint.Access(
+                pageOffset = 0,
+                indexInPage = 0,
+                presentedItemsBefore = 0,
+                presentedItemsAfter = 2,
+                originalPageOffsetFirst = 0,
+                originalPageOffsetLast = 0,
+            )
+        )
+
+        // Trigger refresh instantiating third generation before second has a chance to complete
+        // initial load.
+        pageFetcher.refresh()
+
+        // Wait for all non-canceled loads to complete.
+        advanceUntilIdle()
+
+        // Verify remote refresh is called with PagingState from first generation, since second
+        // generation never loaded any pages.
+        assertThat(remoteMediator.newLoadEvents).containsExactly(
+            RemoteMediatorMock.LoadEvent(loadType = REFRESH, state = pagingState)
+        )
+
+        job.cancel()
+    }
+
+    @OptIn(ExperimentalPagingApi::class)
+    @Test
+    fun cachesPreviousPagingStateOnNullHint() = testScope.runBlockingTest {
+        val config = PagingConfig(
+            pageSize = 1,
+            prefetchDistance = 1,
+            enablePlaceholders = true,
+            initialLoadSize = 3,
+        )
+
+        val remoteMediator = RemoteMediatorMock().apply {
+            initializeResult = LAUNCH_INITIAL_REFRESH
+        }
+        val pageFetcher = PageFetcher(
+            pagingSourceFactory = suspend {
+                TestPagingSource(loadDelay = 1000)
+            },
+            initialKey = 50,
+            config = config,
+            remoteMediator = remoteMediator,
+        )
+
+        var receiver: UiReceiver? = null
+        val job = launch() {
+            pageFetcher.flow.collectLatest {
+                receiver = it.receiver
+                it.flow.collect { }
+            }
+        }
+
+        // Allow initial load to finish, so PagingState has non-zero pages.
+        advanceUntilIdle()
+
+        // Trigger a hint to populate anchorPosition, this should cause PageFetcher to cache this
+        // PagingState and use it in next remoteRefresh
+        receiver?.accessHint(
+            ViewportHint.Access(
+                pageOffset = 0,
+                indexInPage = 0,
+                presentedItemsBefore = 0,
+                presentedItemsAfter = 2,
+                originalPageOffsetFirst = 0,
+                originalPageOffsetLast = 0,
+            )
+        )
+
+        // Verify remote refresh is called with initial empty case.
+        assertThat(remoteMediator.newLoadEvents).containsExactly(
+            RemoteMediatorMock.LoadEvent(
+                loadType = REFRESH,
+                state = PagingState(
+                    pages = listOf(),
+                    anchorPosition = null,
+                    config = config,
+                    leadingPlaceholderCount = 0,
+                ),
+            )
+        )
+
+        // Trigger refresh, instantiating second generation.
+        pageFetcher.refresh()
+
+        // Allow remote refresh to get triggered, and let paging source load finish.
+        advanceUntilIdle()
+
+        // Verify remote refresh is called with PagingState from first generation.
+        val pagingState = PagingState(
+            pages = listOf(
+                PagingSource.LoadResult.Page(
+                    data = listOf(50, 51, 52),
+                    prevKey = 49,
+                    nextKey = 53,
+                    itemsBefore = 50,
+                    itemsAfter = 47,
+                )
+            ),
+            anchorPosition = 50,
+            config = config,
+            leadingPlaceholderCount = 50,
+        )
+        assertThat(remoteMediator.newLoadEvents).containsExactly(
+            RemoteMediatorMock.LoadEvent(loadType = REFRESH, state = pagingState)
+        )
+
+        // Trigger refresh instantiating third generation before second has a chance to complete
+        // initial load.
+        pageFetcher.refresh()
+
+        // Wait for all non-canceled loads to complete.
+        advanceUntilIdle()
+
+        // Verify remote refresh is called with PagingState from first generation, since second
+        // generation never loaded any pages.
+        assertThat(remoteMediator.newLoadEvents).containsExactly(
+            RemoteMediatorMock.LoadEvent(loadType = REFRESH, state = pagingState)
+        )
+
+        job.cancel()
+    }
+
     @Test
     fun invalidateBeforeAccessPreservesPagingState() = testScope.runBlockingTest {
         pauseDispatcher {
diff --git a/room/common/api/2.3.0-beta03.txt b/room/common/api/2.3.0-beta03.txt
new file mode 100644
index 0000000..ab3d354
--- /dev/null
+++ b/room/common/api/2.3.0-beta03.txt
@@ -0,0 +1,206 @@
+// Signature format: 4.0
+package androidx.room {
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface ColumnInfo {
+    method @androidx.room.ColumnInfo.Collate public abstract int collate() default androidx.room.ColumnInfo.UNSPECIFIED;
+    method public abstract String defaultValue() default androidx.room.ColumnInfo.VALUE_UNSPECIFIED;
+    method public abstract boolean index() default false;
+    method public abstract String name() default androidx.room.ColumnInfo.INHERIT_FIELD_NAME;
+    method @androidx.room.ColumnInfo.SQLiteTypeAffinity public abstract int typeAffinity() default androidx.room.ColumnInfo.UNDEFINED;
+    field public static final int BINARY = 2; // 0x2
+    field public static final int BLOB = 5; // 0x5
+    field public static final String INHERIT_FIELD_NAME = "[field-name]";
+    field public static final int INTEGER = 3; // 0x3
+    field @RequiresApi(21) public static final int LOCALIZED = 5; // 0x5
+    field public static final int NOCASE = 3; // 0x3
+    field public static final int REAL = 4; // 0x4
+    field public static final int RTRIM = 4; // 0x4
+    field public static final int TEXT = 2; // 0x2
+    field public static final int UNDEFINED = 1; // 0x1
+    field @RequiresApi(21) public static final int UNICODE = 6; // 0x6
+    field public static final int UNSPECIFIED = 1; // 0x1
+    field public static final String VALUE_UNSPECIFIED = "[value-unspecified]";
+  }
+
+  @IntDef({androidx.room.ColumnInfo.UNSPECIFIED, androidx.room.ColumnInfo.BINARY, androidx.room.ColumnInfo.NOCASE, androidx.room.ColumnInfo.RTRIM, androidx.room.ColumnInfo.LOCALIZED, androidx.room.ColumnInfo.UNICODE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.Collate {
+  }
+
+  @IntDef({androidx.room.ColumnInfo.UNDEFINED, androidx.room.ColumnInfo.TEXT, androidx.room.ColumnInfo.INTEGER, androidx.room.ColumnInfo.REAL, androidx.room.ColumnInfo.BLOB}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.SQLiteTypeAffinity {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Dao {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Database {
+    method public abstract Class<?>[] entities();
+    method public abstract boolean exportSchema() default true;
+    method public abstract int version();
+    method public abstract Class<?>[] views() default {};
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface DatabaseView {
+    method public abstract String value() default "";
+    method public abstract String viewName() default "";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface Delete {
+    method public abstract Class<?> entity() default java.lang.Object.class;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface Embedded {
+    method public abstract String prefix() default "";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Entity {
+    method public abstract androidx.room.ForeignKey[] foreignKeys() default {};
+    method public abstract String[] ignoredColumns() default {};
+    method public abstract androidx.room.Index[] indices() default {};
+    method public abstract boolean inheritSuperIndices() default false;
+    method public abstract String[] primaryKeys() default {};
+    method public abstract String tableName() default "";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface ForeignKey {
+    method public abstract String[] childColumns();
+    method public abstract boolean deferred() default false;
+    method public abstract Class<?> entity();
+    method @androidx.room.ForeignKey.Action public abstract int onDelete() default androidx.room.ForeignKey.NO_ACTION;
+    method @androidx.room.ForeignKey.Action public abstract int onUpdate() default androidx.room.ForeignKey.NO_ACTION;
+    method public abstract String[] parentColumns();
+    field public static final int CASCADE = 5; // 0x5
+    field public static final int NO_ACTION = 1; // 0x1
+    field public static final int RESTRICT = 2; // 0x2
+    field public static final int SET_DEFAULT = 4; // 0x4
+    field public static final int SET_NULL = 3; // 0x3
+  }
+
+  @IntDef({androidx.room.ForeignKey.NO_ACTION, androidx.room.ForeignKey.RESTRICT, androidx.room.ForeignKey.SET_NULL, androidx.room.ForeignKey.SET_DEFAULT, androidx.room.ForeignKey.CASCADE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ForeignKey.Action {
+  }
+
+  @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts3 {
+    method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+    method public abstract String[] tokenizerArgs() default {};
+  }
+
+  @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts4 {
+    method public abstract Class<?> contentEntity() default java.lang.Object.class;
+    method public abstract String languageId() default "";
+    method public abstract androidx.room.FtsOptions.MatchInfo matchInfo() default androidx.room.FtsOptions.MatchInfo.FTS4;
+    method public abstract String[] notIndexed() default {};
+    method public abstract androidx.room.FtsOptions.Order order() default androidx.room.FtsOptions.Order.ASC;
+    method public abstract int[] prefix() default {};
+    method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+    method public abstract String[] tokenizerArgs() default {};
+  }
+
+  public class FtsOptions {
+    field public static final String TOKENIZER_ICU = "icu";
+    field public static final String TOKENIZER_PORTER = "porter";
+    field public static final String TOKENIZER_SIMPLE = "simple";
+    field @RequiresApi(21) public static final String TOKENIZER_UNICODE61 = "unicode61";
+  }
+
+  public enum FtsOptions.MatchInfo {
+    enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS3;
+    enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS4;
+  }
+
+  public enum FtsOptions.Order {
+    enum_constant public static final androidx.room.FtsOptions.Order ASC;
+    enum_constant public static final androidx.room.FtsOptions.Order DESC;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.CONSTRUCTOR}) public @interface Ignore {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Index {
+    method public abstract String name() default "";
+    method public abstract boolean unique() default false;
+    method public abstract String[] value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Insert {
+    method public abstract Class<?> entity() default java.lang.Object.class;
+    method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Junction {
+    method public abstract String entityColumn() default "";
+    method public abstract String parentColumn() default "";
+    method public abstract Class<?> value();
+  }
+
+  @IntDef({androidx.room.OnConflictStrategy.REPLACE, androidx.room.OnConflictStrategy.ROLLBACK, androidx.room.OnConflictStrategy.ABORT, androidx.room.OnConflictStrategy.FAIL, androidx.room.OnConflictStrategy.IGNORE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface OnConflictStrategy {
+    field public static final int ABORT = 3; // 0x3
+    field @Deprecated public static final int FAIL = 4; // 0x4
+    field public static final int IGNORE = 5; // 0x5
+    field public static final int REPLACE = 1; // 0x1
+    field @Deprecated public static final int ROLLBACK = 2; // 0x2
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface PrimaryKey {
+    method public abstract boolean autoGenerate() default false;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedTypeConverter {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface Query {
+    method public abstract String value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface RawQuery {
+    method public abstract Class<?>[] observedEntities() default {};
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface Relation {
+    method public abstract androidx.room.Junction associateBy() default @androidx.room.Junction;
+    method public abstract Class<?> entity() default java.lang.Object.class;
+    method public abstract String entityColumn();
+    method public abstract String parentColumn();
+    method public abstract String[] projection() default {};
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) public @interface RewriteQueriesToDropUnusedColumns {
+  }
+
+  public class RoomWarnings {
+    ctor @Deprecated public RoomWarnings();
+    field public static final String CANNOT_CREATE_VERIFICATION_DATABASE = "ROOM_CANNOT_CREATE_VERIFICATION_DATABASE";
+    field public static final String CURSOR_MISMATCH = "ROOM_CURSOR_MISMATCH";
+    field public static final String DEFAULT_CONSTRUCTOR = "ROOM_DEFAULT_CONSTRUCTOR";
+    field public static final String INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED = "ROOM_EMBEDDED_ENTITY_INDEX_IS_DROPPED";
+    field public static final String INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED = "ROOM_EMBEDDED_INDEX_IS_DROPPED";
+    field public static final String INDEX_FROM_PARENT_FIELD_IS_DROPPED = "ROOM_PARENT_FIELD_INDEX_IS_DROPPED";
+    field public static final String INDEX_FROM_PARENT_IS_DROPPED = "ROOM_PARENT_INDEX_IS_DROPPED";
+    field public static final String MISMATCHED_GETTER = "ROOM_MISMATCHED_GETTER_TYPE";
+    field public static final String MISMATCHED_SETTER = "ROOM_MISMATCHED_SETTER_TYPE";
+    field public static final String MISSING_INDEX_ON_FOREIGN_KEY_CHILD = "ROOM_MISSING_FOREIGN_KEY_CHILD_INDEX";
+    field public static final String MISSING_INDEX_ON_JUNCTION = "MISSING_INDEX_ON_JUNCTION";
+    field public static final String MISSING_JAVA_TMP_DIR = "ROOM_MISSING_JAVA_TMP_DIR";
+    field public static final String MISSING_SCHEMA_LOCATION = "ROOM_MISSING_SCHEMA_LOCATION";
+    field public static final String PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED = "ROOM_EMBEDDED_PRIMARY_KEY_IS_DROPPED";
+    field public static final String RELATION_QUERY_WITHOUT_TRANSACTION = "ROOM_RELATION_QUERY_WITHOUT_TRANSACTION";
+    field public static final String RELATION_TYPE_MISMATCH = "ROOM_RELATION_TYPE_MISMATCH";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) public @interface SkipQueryVerification {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Transaction {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface TypeConverter {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD}) public @interface TypeConverters {
+    method public abstract Class<?>[] value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Update {
+    method public abstract Class<?> entity() default java.lang.Object.class;
+    method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+  }
+
+}
+
diff --git a/room/common/api/public_plus_experimental_2.3.0-beta03.txt b/room/common/api/public_plus_experimental_2.3.0-beta03.txt
new file mode 100644
index 0000000..ab3d354
--- /dev/null
+++ b/room/common/api/public_plus_experimental_2.3.0-beta03.txt
@@ -0,0 +1,206 @@
+// Signature format: 4.0
+package androidx.room {
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface ColumnInfo {
+    method @androidx.room.ColumnInfo.Collate public abstract int collate() default androidx.room.ColumnInfo.UNSPECIFIED;
+    method public abstract String defaultValue() default androidx.room.ColumnInfo.VALUE_UNSPECIFIED;
+    method public abstract boolean index() default false;
+    method public abstract String name() default androidx.room.ColumnInfo.INHERIT_FIELD_NAME;
+    method @androidx.room.ColumnInfo.SQLiteTypeAffinity public abstract int typeAffinity() default androidx.room.ColumnInfo.UNDEFINED;
+    field public static final int BINARY = 2; // 0x2
+    field public static final int BLOB = 5; // 0x5
+    field public static final String INHERIT_FIELD_NAME = "[field-name]";
+    field public static final int INTEGER = 3; // 0x3
+    field @RequiresApi(21) public static final int LOCALIZED = 5; // 0x5
+    field public static final int NOCASE = 3; // 0x3
+    field public static final int REAL = 4; // 0x4
+    field public static final int RTRIM = 4; // 0x4
+    field public static final int TEXT = 2; // 0x2
+    field public static final int UNDEFINED = 1; // 0x1
+    field @RequiresApi(21) public static final int UNICODE = 6; // 0x6
+    field public static final int UNSPECIFIED = 1; // 0x1
+    field public static final String VALUE_UNSPECIFIED = "[value-unspecified]";
+  }
+
+  @IntDef({androidx.room.ColumnInfo.UNSPECIFIED, androidx.room.ColumnInfo.BINARY, androidx.room.ColumnInfo.NOCASE, androidx.room.ColumnInfo.RTRIM, androidx.room.ColumnInfo.LOCALIZED, androidx.room.ColumnInfo.UNICODE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.Collate {
+  }
+
+  @IntDef({androidx.room.ColumnInfo.UNDEFINED, androidx.room.ColumnInfo.TEXT, androidx.room.ColumnInfo.INTEGER, androidx.room.ColumnInfo.REAL, androidx.room.ColumnInfo.BLOB}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.SQLiteTypeAffinity {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Dao {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Database {
+    method public abstract Class<?>[] entities();
+    method public abstract boolean exportSchema() default true;
+    method public abstract int version();
+    method public abstract Class<?>[] views() default {};
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface DatabaseView {
+    method public abstract String value() default "";
+    method public abstract String viewName() default "";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface Delete {
+    method public abstract Class<?> entity() default java.lang.Object.class;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface Embedded {
+    method public abstract String prefix() default "";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Entity {
+    method public abstract androidx.room.ForeignKey[] foreignKeys() default {};
+    method public abstract String[] ignoredColumns() default {};
+    method public abstract androidx.room.Index[] indices() default {};
+    method public abstract boolean inheritSuperIndices() default false;
+    method public abstract String[] primaryKeys() default {};
+    method public abstract String tableName() default "";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface ForeignKey {
+    method public abstract String[] childColumns();
+    method public abstract boolean deferred() default false;
+    method public abstract Class<?> entity();
+    method @androidx.room.ForeignKey.Action public abstract int onDelete() default androidx.room.ForeignKey.NO_ACTION;
+    method @androidx.room.ForeignKey.Action public abstract int onUpdate() default androidx.room.ForeignKey.NO_ACTION;
+    method public abstract String[] parentColumns();
+    field public static final int CASCADE = 5; // 0x5
+    field public static final int NO_ACTION = 1; // 0x1
+    field public static final int RESTRICT = 2; // 0x2
+    field public static final int SET_DEFAULT = 4; // 0x4
+    field public static final int SET_NULL = 3; // 0x3
+  }
+
+  @IntDef({androidx.room.ForeignKey.NO_ACTION, androidx.room.ForeignKey.RESTRICT, androidx.room.ForeignKey.SET_NULL, androidx.room.ForeignKey.SET_DEFAULT, androidx.room.ForeignKey.CASCADE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ForeignKey.Action {
+  }
+
+  @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts3 {
+    method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+    method public abstract String[] tokenizerArgs() default {};
+  }
+
+  @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts4 {
+    method public abstract Class<?> contentEntity() default java.lang.Object.class;
+    method public abstract String languageId() default "";
+    method public abstract androidx.room.FtsOptions.MatchInfo matchInfo() default androidx.room.FtsOptions.MatchInfo.FTS4;
+    method public abstract String[] notIndexed() default {};
+    method public abstract androidx.room.FtsOptions.Order order() default androidx.room.FtsOptions.Order.ASC;
+    method public abstract int[] prefix() default {};
+    method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+    method public abstract String[] tokenizerArgs() default {};
+  }
+
+  public class FtsOptions {
+    field public static final String TOKENIZER_ICU = "icu";
+    field public static final String TOKENIZER_PORTER = "porter";
+    field public static final String TOKENIZER_SIMPLE = "simple";
+    field @RequiresApi(21) public static final String TOKENIZER_UNICODE61 = "unicode61";
+  }
+
+  public enum FtsOptions.MatchInfo {
+    enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS3;
+    enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS4;
+  }
+
+  public enum FtsOptions.Order {
+    enum_constant public static final androidx.room.FtsOptions.Order ASC;
+    enum_constant public static final androidx.room.FtsOptions.Order DESC;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.CONSTRUCTOR}) public @interface Ignore {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Index {
+    method public abstract String name() default "";
+    method public abstract boolean unique() default false;
+    method public abstract String[] value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Insert {
+    method public abstract Class<?> entity() default java.lang.Object.class;
+    method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Junction {
+    method public abstract String entityColumn() default "";
+    method public abstract String parentColumn() default "";
+    method public abstract Class<?> value();
+  }
+
+  @IntDef({androidx.room.OnConflictStrategy.REPLACE, androidx.room.OnConflictStrategy.ROLLBACK, androidx.room.OnConflictStrategy.ABORT, androidx.room.OnConflictStrategy.FAIL, androidx.room.OnConflictStrategy.IGNORE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface OnConflictStrategy {
+    field public static final int ABORT = 3; // 0x3
+    field @Deprecated public static final int FAIL = 4; // 0x4
+    field public static final int IGNORE = 5; // 0x5
+    field public static final int REPLACE = 1; // 0x1
+    field @Deprecated public static final int ROLLBACK = 2; // 0x2
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface PrimaryKey {
+    method public abstract boolean autoGenerate() default false;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedTypeConverter {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface Query {
+    method public abstract String value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface RawQuery {
+    method public abstract Class<?>[] observedEntities() default {};
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface Relation {
+    method public abstract androidx.room.Junction associateBy() default @androidx.room.Junction;
+    method public abstract Class<?> entity() default java.lang.Object.class;
+    method public abstract String entityColumn();
+    method public abstract String parentColumn();
+    method public abstract String[] projection() default {};
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) public @interface RewriteQueriesToDropUnusedColumns {
+  }
+
+  public class RoomWarnings {
+    ctor @Deprecated public RoomWarnings();
+    field public static final String CANNOT_CREATE_VERIFICATION_DATABASE = "ROOM_CANNOT_CREATE_VERIFICATION_DATABASE";
+    field public static final String CURSOR_MISMATCH = "ROOM_CURSOR_MISMATCH";
+    field public static final String DEFAULT_CONSTRUCTOR = "ROOM_DEFAULT_CONSTRUCTOR";
+    field public static final String INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED = "ROOM_EMBEDDED_ENTITY_INDEX_IS_DROPPED";
+    field public static final String INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED = "ROOM_EMBEDDED_INDEX_IS_DROPPED";
+    field public static final String INDEX_FROM_PARENT_FIELD_IS_DROPPED = "ROOM_PARENT_FIELD_INDEX_IS_DROPPED";
+    field public static final String INDEX_FROM_PARENT_IS_DROPPED = "ROOM_PARENT_INDEX_IS_DROPPED";
+    field public static final String MISMATCHED_GETTER = "ROOM_MISMATCHED_GETTER_TYPE";
+    field public static final String MISMATCHED_SETTER = "ROOM_MISMATCHED_SETTER_TYPE";
+    field public static final String MISSING_INDEX_ON_FOREIGN_KEY_CHILD = "ROOM_MISSING_FOREIGN_KEY_CHILD_INDEX";
+    field public static final String MISSING_INDEX_ON_JUNCTION = "MISSING_INDEX_ON_JUNCTION";
+    field public static final String MISSING_JAVA_TMP_DIR = "ROOM_MISSING_JAVA_TMP_DIR";
+    field public static final String MISSING_SCHEMA_LOCATION = "ROOM_MISSING_SCHEMA_LOCATION";
+    field public static final String PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED = "ROOM_EMBEDDED_PRIMARY_KEY_IS_DROPPED";
+    field public static final String RELATION_QUERY_WITHOUT_TRANSACTION = "ROOM_RELATION_QUERY_WITHOUT_TRANSACTION";
+    field public static final String RELATION_TYPE_MISMATCH = "ROOM_RELATION_TYPE_MISMATCH";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) public @interface SkipQueryVerification {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Transaction {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface TypeConverter {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD}) public @interface TypeConverters {
+    method public abstract Class<?>[] value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Update {
+    method public abstract Class<?> entity() default java.lang.Object.class;
+    method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+  }
+
+}
+
diff --git a/room/common/api/restricted_2.3.0-beta03.txt b/room/common/api/restricted_2.3.0-beta03.txt
new file mode 100644
index 0000000..c88fdd3
--- /dev/null
+++ b/room/common/api/restricted_2.3.0-beta03.txt
@@ -0,0 +1,215 @@
+// Signature format: 4.0
+package androidx.room {
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface ColumnInfo {
+    method @androidx.room.ColumnInfo.Collate public abstract int collate() default androidx.room.ColumnInfo.UNSPECIFIED;
+    method public abstract String defaultValue() default androidx.room.ColumnInfo.VALUE_UNSPECIFIED;
+    method public abstract boolean index() default false;
+    method public abstract String name() default androidx.room.ColumnInfo.INHERIT_FIELD_NAME;
+    method @androidx.room.ColumnInfo.SQLiteTypeAffinity public abstract int typeAffinity() default androidx.room.ColumnInfo.UNDEFINED;
+    field public static final int BINARY = 2; // 0x2
+    field public static final int BLOB = 5; // 0x5
+    field public static final String INHERIT_FIELD_NAME = "[field-name]";
+    field public static final int INTEGER = 3; // 0x3
+    field @RequiresApi(21) public static final int LOCALIZED = 5; // 0x5
+    field public static final int NOCASE = 3; // 0x3
+    field public static final int REAL = 4; // 0x4
+    field public static final int RTRIM = 4; // 0x4
+    field public static final int TEXT = 2; // 0x2
+    field public static final int UNDEFINED = 1; // 0x1
+    field @RequiresApi(21) public static final int UNICODE = 6; // 0x6
+    field public static final int UNSPECIFIED = 1; // 0x1
+    field public static final String VALUE_UNSPECIFIED = "[value-unspecified]";
+  }
+
+  @IntDef({androidx.room.ColumnInfo.UNSPECIFIED, androidx.room.ColumnInfo.BINARY, androidx.room.ColumnInfo.NOCASE, androidx.room.ColumnInfo.RTRIM, androidx.room.ColumnInfo.LOCALIZED, androidx.room.ColumnInfo.UNICODE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.Collate {
+  }
+
+  @IntDef({androidx.room.ColumnInfo.UNDEFINED, androidx.room.ColumnInfo.TEXT, androidx.room.ColumnInfo.INTEGER, androidx.room.ColumnInfo.REAL, androidx.room.ColumnInfo.BLOB}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.SQLiteTypeAffinity {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Dao {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Database {
+    method public abstract Class<?>[] entities();
+    method public abstract boolean exportSchema() default true;
+    method public abstract int version();
+    method public abstract Class<?>[] views() default {};
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface DatabaseView {
+    method public abstract String value() default "";
+    method public abstract String viewName() default "";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface Delete {
+    method public abstract Class<?> entity() default java.lang.Object.class;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface Embedded {
+    method public abstract String prefix() default "";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Entity {
+    method public abstract androidx.room.ForeignKey[] foreignKeys() default {};
+    method public abstract String[] ignoredColumns() default {};
+    method public abstract androidx.room.Index[] indices() default {};
+    method public abstract boolean inheritSuperIndices() default false;
+    method public abstract String[] primaryKeys() default {};
+    method public abstract String tableName() default "";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface ForeignKey {
+    method public abstract String[] childColumns();
+    method public abstract boolean deferred() default false;
+    method public abstract Class<?> entity();
+    method @androidx.room.ForeignKey.Action public abstract int onDelete() default androidx.room.ForeignKey.NO_ACTION;
+    method @androidx.room.ForeignKey.Action public abstract int onUpdate() default androidx.room.ForeignKey.NO_ACTION;
+    method public abstract String[] parentColumns();
+    field public static final int CASCADE = 5; // 0x5
+    field public static final int NO_ACTION = 1; // 0x1
+    field public static final int RESTRICT = 2; // 0x2
+    field public static final int SET_DEFAULT = 4; // 0x4
+    field public static final int SET_NULL = 3; // 0x3
+  }
+
+  @IntDef({androidx.room.ForeignKey.NO_ACTION, androidx.room.ForeignKey.RESTRICT, androidx.room.ForeignKey.SET_NULL, androidx.room.ForeignKey.SET_DEFAULT, androidx.room.ForeignKey.CASCADE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ForeignKey.Action {
+  }
+
+  @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts3 {
+    method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+    method public abstract String[] tokenizerArgs() default {};
+  }
+
+  @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts4 {
+    method public abstract Class<?> contentEntity() default java.lang.Object.class;
+    method public abstract String languageId() default "";
+    method public abstract androidx.room.FtsOptions.MatchInfo matchInfo() default androidx.room.FtsOptions.MatchInfo.FTS4;
+    method public abstract String[] notIndexed() default {};
+    method public abstract androidx.room.FtsOptions.Order order() default androidx.room.FtsOptions.Order.ASC;
+    method public abstract int[] prefix() default {};
+    method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+    method public abstract String[] tokenizerArgs() default {};
+  }
+
+  public class FtsOptions {
+    field public static final String TOKENIZER_ICU = "icu";
+    field public static final String TOKENIZER_PORTER = "porter";
+    field public static final String TOKENIZER_SIMPLE = "simple";
+    field @RequiresApi(21) public static final String TOKENIZER_UNICODE61 = "unicode61";
+  }
+
+  public enum FtsOptions.MatchInfo {
+    enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS3;
+    enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS4;
+  }
+
+  public enum FtsOptions.Order {
+    enum_constant public static final androidx.room.FtsOptions.Order ASC;
+    enum_constant public static final androidx.room.FtsOptions.Order DESC;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.CONSTRUCTOR}) public @interface Ignore {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Index {
+    method public abstract String name() default "";
+    method public abstract boolean unique() default false;
+    method public abstract String[] value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Insert {
+    method public abstract Class<?> entity() default java.lang.Object.class;
+    method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Junction {
+    method public abstract String entityColumn() default "";
+    method public abstract String parentColumn() default "";
+    method public abstract Class<?> value();
+  }
+
+  @IntDef({androidx.room.OnConflictStrategy.REPLACE, androidx.room.OnConflictStrategy.ROLLBACK, androidx.room.OnConflictStrategy.ABORT, androidx.room.OnConflictStrategy.FAIL, androidx.room.OnConflictStrategy.IGNORE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface OnConflictStrategy {
+    field public static final int ABORT = 3; // 0x3
+    field @Deprecated public static final int FAIL = 4; // 0x4
+    field public static final int IGNORE = 5; // 0x5
+    field public static final int REPLACE = 1; // 0x1
+    field @Deprecated public static final int ROLLBACK = 2; // 0x2
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface PrimaryKey {
+    method public abstract boolean autoGenerate() default false;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedTypeConverter {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface Query {
+    method public abstract String value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface RawQuery {
+    method public abstract Class<?>[] observedEntities() default {};
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface Relation {
+    method public abstract androidx.room.Junction associateBy() default @androidx.room.Junction;
+    method public abstract Class<?> entity() default java.lang.Object.class;
+    method public abstract String entityColumn();
+    method public abstract String parentColumn();
+    method public abstract String[] projection() default {};
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) public @interface RewriteQueriesToDropUnusedColumns {
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class RoomMasterTable {
+    method public static String! createInsertQuery(String!);
+    field public static final String CREATE_QUERY = "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)";
+    field public static final String DEFAULT_ID = "42";
+    field public static final String NAME = "room_master_table";
+    field public static final String READ_QUERY = "SELECT identity_hash FROM room_master_table WHERE id = 42 LIMIT 1";
+    field public static final String TABLE_NAME = "room_master_table";
+  }
+
+  public class RoomWarnings {
+    ctor @Deprecated public RoomWarnings();
+    field public static final String CANNOT_CREATE_VERIFICATION_DATABASE = "ROOM_CANNOT_CREATE_VERIFICATION_DATABASE";
+    field public static final String CURSOR_MISMATCH = "ROOM_CURSOR_MISMATCH";
+    field public static final String DEFAULT_CONSTRUCTOR = "ROOM_DEFAULT_CONSTRUCTOR";
+    field public static final String INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED = "ROOM_EMBEDDED_ENTITY_INDEX_IS_DROPPED";
+    field public static final String INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED = "ROOM_EMBEDDED_INDEX_IS_DROPPED";
+    field public static final String INDEX_FROM_PARENT_FIELD_IS_DROPPED = "ROOM_PARENT_FIELD_INDEX_IS_DROPPED";
+    field public static final String INDEX_FROM_PARENT_IS_DROPPED = "ROOM_PARENT_INDEX_IS_DROPPED";
+    field public static final String MISMATCHED_GETTER = "ROOM_MISMATCHED_GETTER_TYPE";
+    field public static final String MISMATCHED_SETTER = "ROOM_MISMATCHED_SETTER_TYPE";
+    field public static final String MISSING_INDEX_ON_FOREIGN_KEY_CHILD = "ROOM_MISSING_FOREIGN_KEY_CHILD_INDEX";
+    field public static final String MISSING_INDEX_ON_JUNCTION = "MISSING_INDEX_ON_JUNCTION";
+    field public static final String MISSING_JAVA_TMP_DIR = "ROOM_MISSING_JAVA_TMP_DIR";
+    field public static final String MISSING_SCHEMA_LOCATION = "ROOM_MISSING_SCHEMA_LOCATION";
+    field public static final String PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED = "ROOM_EMBEDDED_PRIMARY_KEY_IS_DROPPED";
+    field public static final String RELATION_QUERY_WITHOUT_TRANSACTION = "ROOM_RELATION_QUERY_WITHOUT_TRANSACTION";
+    field public static final String RELATION_TYPE_MISMATCH = "ROOM_RELATION_TYPE_MISMATCH";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) public @interface SkipQueryVerification {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Transaction {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface TypeConverter {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD}) public @interface TypeConverters {
+    method public abstract Class<?>[] value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Update {
+    method public abstract Class<?> entity() default java.lang.Object.class;
+    method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+  }
+
+}
+
diff --git a/room/guava/api/2.3.0-beta03.txt b/room/guava/api/2.3.0-beta03.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/room/guava/api/2.3.0-beta03.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/room/guava/api/public_plus_experimental_2.3.0-beta03.txt b/room/guava/api/public_plus_experimental_2.3.0-beta03.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/room/guava/api/public_plus_experimental_2.3.0-beta03.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/room/guava/api/res-2.3.0-beta03.txt b/room/guava/api/res-2.3.0-beta03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/guava/api/res-2.3.0-beta03.txt
diff --git a/room/guava/api/restricted_2.3.0-beta03.txt b/room/guava/api/restricted_2.3.0-beta03.txt
new file mode 100644
index 0000000..1c04602
--- /dev/null
+++ b/room/guava/api/restricted_2.3.0-beta03.txt
@@ -0,0 +1,14 @@
+// Signature format: 4.0
+package androidx.room.guava {
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class GuavaRoom {
+    method @Deprecated public static <T> com.google.common.util.concurrent.ListenableFuture<T!>! createListenableFuture(java.util.concurrent.Callable<T!>!, androidx.room.RoomSQLiteQuery!, boolean);
+    method @Deprecated public static <T> com.google.common.util.concurrent.ListenableFuture<T!>! createListenableFuture(androidx.room.RoomDatabase!, java.util.concurrent.Callable<T!>!, androidx.room.RoomSQLiteQuery!, boolean);
+    method public static <T> com.google.common.util.concurrent.ListenableFuture<T!>! createListenableFuture(androidx.room.RoomDatabase!, boolean, java.util.concurrent.Callable<T!>!, androidx.room.RoomSQLiteQuery!, boolean);
+    method public static <T> com.google.common.util.concurrent.ListenableFuture<T!> createListenableFuture(androidx.room.RoomDatabase, boolean, java.util.concurrent.Callable<T!>, androidx.room.RoomSQLiteQuery, boolean, android.os.CancellationSignal?);
+    method @Deprecated public static <T> com.google.common.util.concurrent.ListenableFuture<T!> createListenableFuture(androidx.room.RoomDatabase, java.util.concurrent.Callable<T!>);
+    method public static <T> com.google.common.util.concurrent.ListenableFuture<T!> createListenableFuture(androidx.room.RoomDatabase, boolean, java.util.concurrent.Callable<T!>);
+  }
+
+}
+
diff --git a/room/ktx/api/2.3.0-beta03.txt b/room/ktx/api/2.3.0-beta03.txt
new file mode 100644
index 0000000..83dcd49
--- /dev/null
+++ b/room/ktx/api/2.3.0-beta03.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package androidx.room {
+
+  public final class CoroutinesRoomKt {
+  }
+
+  public final class RoomDatabaseKt {
+    method public static suspend <R> Object? withTransaction(androidx.room.RoomDatabase, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R> p);
+  }
+
+}
+
diff --git a/room/ktx/api/public_plus_experimental_2.3.0-beta03.txt b/room/ktx/api/public_plus_experimental_2.3.0-beta03.txt
new file mode 100644
index 0000000..83dcd49
--- /dev/null
+++ b/room/ktx/api/public_plus_experimental_2.3.0-beta03.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package androidx.room {
+
+  public final class CoroutinesRoomKt {
+  }
+
+  public final class RoomDatabaseKt {
+    method public static suspend <R> Object? withTransaction(androidx.room.RoomDatabase, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R> p);
+  }
+
+}
+
diff --git a/room/ktx/api/res-2.3.0-beta03.txt b/room/ktx/api/res-2.3.0-beta03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/ktx/api/res-2.3.0-beta03.txt
diff --git a/room/ktx/api/restricted_2.3.0-beta03.txt b/room/ktx/api/restricted_2.3.0-beta03.txt
new file mode 100644
index 0000000..3047be8
--- /dev/null
+++ b/room/ktx/api/restricted_2.3.0-beta03.txt
@@ -0,0 +1,25 @@
+// Signature format: 4.0
+package androidx.room {
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class CoroutinesRoom {
+    method public static <R> kotlinx.coroutines.flow.Flow<R> createFlow(androidx.room.RoomDatabase db, boolean inTransaction, String![] tableNames, java.util.concurrent.Callable<R> callable);
+    method public static suspend <R> Object? execute(androidx.room.RoomDatabase p, boolean db, java.util.concurrent.Callable<R> inTransaction, kotlin.coroutines.Continuation<? super R> callable);
+    method public static suspend <R> Object? execute(androidx.room.RoomDatabase p, boolean db, android.os.CancellationSignal inTransaction, java.util.concurrent.Callable<R> cancellationSignal, kotlin.coroutines.Continuation<? super R> callable);
+    field public static final androidx.room.CoroutinesRoom.Companion Companion;
+  }
+
+  public static final class CoroutinesRoom.Companion {
+    method public <R> kotlinx.coroutines.flow.Flow<R> createFlow(androidx.room.RoomDatabase db, boolean inTransaction, String![] tableNames, java.util.concurrent.Callable<R> callable);
+    method public suspend <R> Object? execute(androidx.room.RoomDatabase db, boolean inTransaction, java.util.concurrent.Callable<R> callable, kotlin.coroutines.Continuation<? super R> p);
+    method public suspend <R> Object? execute(androidx.room.RoomDatabase db, boolean inTransaction, android.os.CancellationSignal cancellationSignal, java.util.concurrent.Callable<R> callable, kotlin.coroutines.Continuation<? super R> p);
+  }
+
+  public final class CoroutinesRoomKt {
+  }
+
+  public final class RoomDatabaseKt {
+    method public static suspend <R> Object? withTransaction(androidx.room.RoomDatabase, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R> p);
+  }
+
+}
+
diff --git a/room/migration/api/2.3.0-beta03.txt b/room/migration/api/2.3.0-beta03.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/room/migration/api/2.3.0-beta03.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/room/migration/api/public_plus_experimental_2.3.0-beta03.txt b/room/migration/api/public_plus_experimental_2.3.0-beta03.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/room/migration/api/public_plus_experimental_2.3.0-beta03.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/room/migration/api/restricted_2.3.0-beta03.txt b/room/migration/api/restricted_2.3.0-beta03.txt
new file mode 100644
index 0000000..fe4aad7
--- /dev/null
+++ b/room/migration/api/restricted_2.3.0-beta03.txt
@@ -0,0 +1,107 @@
+// Signature format: 4.0
+package androidx.room.migration.bundle {
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class BundleUtil {
+    field public static final String TABLE_NAME_PLACEHOLDER = "${TABLE_NAME}";
+    field public static final String VIEW_NAME_PLACEHOLDER = "${VIEW_NAME}";
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DatabaseBundle {
+    ctor public DatabaseBundle(int, String!, java.util.List<androidx.room.migration.bundle.EntityBundle!>!, java.util.List<androidx.room.migration.bundle.DatabaseViewBundle!>!, java.util.List<java.lang.String!>!);
+    ctor public DatabaseBundle();
+    method public java.util.List<java.lang.String!>! buildCreateQueries();
+    method public java.util.List<androidx.room.migration.bundle.EntityBundle!>! getEntities();
+    method public java.util.Map<java.lang.String!,androidx.room.migration.bundle.EntityBundle!>! getEntitiesByTableName();
+    method public String! getIdentityHash();
+    method public int getVersion();
+    method public java.util.List<androidx.room.migration.bundle.DatabaseViewBundle!>! getViews();
+    method public boolean isSchemaEqual(androidx.room.migration.bundle.DatabaseBundle!);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DatabaseViewBundle {
+    ctor public DatabaseViewBundle(String!, String!);
+    method public String! createView();
+    method public String! getCreateSql();
+    method public String! getViewName();
+    method public boolean isSchemaEqual(androidx.room.migration.bundle.DatabaseViewBundle!);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class EntityBundle {
+    ctor public EntityBundle(String!, String!, java.util.List<androidx.room.migration.bundle.FieldBundle!>!, androidx.room.migration.bundle.PrimaryKeyBundle!, java.util.List<androidx.room.migration.bundle.IndexBundle!>!, java.util.List<androidx.room.migration.bundle.ForeignKeyBundle!>!);
+    method public java.util.Collection<java.lang.String!>! buildCreateQueries();
+    method public String! createNewTable();
+    method public String! createTable();
+    method public String! getCreateSql();
+    method public java.util.List<androidx.room.migration.bundle.FieldBundle!>! getFields();
+    method public java.util.Map<java.lang.String!,androidx.room.migration.bundle.FieldBundle!>! getFieldsByColumnName();
+    method public java.util.List<androidx.room.migration.bundle.ForeignKeyBundle!>! getForeignKeys();
+    method public java.util.List<androidx.room.migration.bundle.IndexBundle!>! getIndices();
+    method public String! getNewTableName();
+    method public androidx.room.migration.bundle.PrimaryKeyBundle! getPrimaryKey();
+    method public String! getTableName();
+    method public boolean isSchemaEqual(androidx.room.migration.bundle.EntityBundle!);
+    method public String renameToOriginal();
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FieldBundle {
+    ctor @Deprecated public FieldBundle(String!, String!, String!, boolean);
+    ctor public FieldBundle(String!, String!, String!, boolean, String!);
+    method public String! getAffinity();
+    method public String! getColumnName();
+    method public String! getDefaultValue();
+    method public String! getFieldPath();
+    method public boolean isNonNull();
+    method public boolean isSchemaEqual(androidx.room.migration.bundle.FieldBundle!);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ForeignKeyBundle {
+    ctor public ForeignKeyBundle(String!, String!, String!, java.util.List<java.lang.String!>!, java.util.List<java.lang.String!>!);
+    method public java.util.List<java.lang.String!>! getColumns();
+    method public String! getOnDelete();
+    method public String! getOnUpdate();
+    method public java.util.List<java.lang.String!>! getReferencedColumns();
+    method public String! getTable();
+    method public boolean isSchemaEqual(androidx.room.migration.bundle.ForeignKeyBundle!);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FtsEntityBundle extends androidx.room.migration.bundle.EntityBundle {
+    ctor public FtsEntityBundle(String!, String!, java.util.List<androidx.room.migration.bundle.FieldBundle!>!, androidx.room.migration.bundle.PrimaryKeyBundle!, String!, androidx.room.migration.bundle.FtsOptionsBundle!, java.util.List<java.lang.String!>!);
+    method public androidx.room.migration.bundle.FtsOptionsBundle! getFtsOptions();
+    method public java.util.List<java.lang.String!>! getShadowTableNames();
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FtsOptionsBundle {
+    ctor public FtsOptionsBundle(String!, java.util.List<java.lang.String!>!, String!, String!, String!, java.util.List<java.lang.String!>!, java.util.List<java.lang.Integer!>!, String!);
+    method public String! getContentTable();
+    method public boolean isSchemaEqual(androidx.room.migration.bundle.FtsOptionsBundle!);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class IndexBundle {
+    ctor public IndexBundle(String!, boolean, java.util.List<java.lang.String!>!, String!);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public String! create(String!);
+    method public java.util.List<java.lang.String!>! getColumnNames();
+    method public String! getName();
+    method public boolean isSchemaEqual(androidx.room.migration.bundle.IndexBundle!);
+    method public boolean isUnique();
+    field public static final String DEFAULT_PREFIX = "index_";
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class PrimaryKeyBundle {
+    ctor public PrimaryKeyBundle(boolean, java.util.List<java.lang.String!>!);
+    method public java.util.List<java.lang.String!>! getColumnNames();
+    method public boolean isAutoGenerate();
+    method public boolean isSchemaEqual(androidx.room.migration.bundle.PrimaryKeyBundle!);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SchemaBundle {
+    ctor public SchemaBundle(int, androidx.room.migration.bundle.DatabaseBundle!);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.room.migration.bundle.SchemaBundle! deserialize(java.io.InputStream!) throws java.io.UnsupportedEncodingException;
+    method public androidx.room.migration.bundle.DatabaseBundle! getDatabase();
+    method public int getFormatVersion();
+    method public boolean isSchemaEqual(androidx.room.migration.bundle.SchemaBundle!);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void serialize(androidx.room.migration.bundle.SchemaBundle!, java.io.File!) throws java.io.IOException;
+    field public static final int LATEST_FORMAT = 1; // 0x1
+  }
+
+}
+
diff --git a/room/runtime/api/2.3.0-beta03.txt b/room/runtime/api/2.3.0-beta03.txt
new file mode 100644
index 0000000..95f23cc
--- /dev/null
+++ b/room/runtime/api/2.3.0-beta03.txt
@@ -0,0 +1,139 @@
+// Signature format: 4.0
+package androidx.room {
+
+  public class DatabaseConfiguration {
+    method public boolean isMigrationRequired(int, int);
+    method @Deprecated public boolean isMigrationRequiredFrom(int);
+    field public final boolean allowDestructiveMigrationOnDowngrade;
+    field public final boolean allowMainThreadQueries;
+    field public final java.util.List<androidx.room.RoomDatabase.Callback!>? callbacks;
+    field public final android.content.Context context;
+    field public final String? copyFromAssetPath;
+    field public final java.io.File? copyFromFile;
+    field public final java.util.concurrent.Callable<java.io.InputStream!>? copyFromInputStream;
+    field public final androidx.room.RoomDatabase.JournalMode! journalMode;
+    field public final androidx.room.RoomDatabase.MigrationContainer migrationContainer;
+    field public final boolean multiInstanceInvalidation;
+    field public final String? name;
+    field public final androidx.room.RoomDatabase.PrepackagedDatabaseCallback? prepackagedDatabaseCallback;
+    field public final java.util.concurrent.Executor queryExecutor;
+    field public final boolean requireMigration;
+    field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
+    field public final java.util.concurrent.Executor transactionExecutor;
+    field public final java.util.List<java.lang.Object!> typeConverters;
+  }
+
+  @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface ExperimentalRoomApi {
+  }
+
+  public class InvalidationTracker {
+    method @WorkerThread public void addObserver(androidx.room.InvalidationTracker.Observer);
+    method public void refreshVersionsAsync();
+    method @WorkerThread public void removeObserver(androidx.room.InvalidationTracker.Observer);
+  }
+
+  public abstract static class InvalidationTracker.Observer {
+    ctor protected InvalidationTracker.Observer(String, java.lang.String!...);
+    ctor public InvalidationTracker.Observer(String![]);
+    method public abstract void onInvalidated(java.util.Set<java.lang.String!>);
+  }
+
+  public class Room {
+    ctor @Deprecated public Room();
+    method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T!> databaseBuilder(android.content.Context, Class<T!>, String);
+    method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T!> inMemoryDatabaseBuilder(android.content.Context, Class<T!>);
+    field public static final String MASTER_TABLE_NAME = "room_master_table";
+  }
+
+  public abstract class RoomDatabase {
+    ctor public RoomDatabase();
+    method @Deprecated public void beginTransaction();
+    method @WorkerThread public abstract void clearAllTables();
+    method public void close();
+    method public androidx.sqlite.db.SupportSQLiteStatement! compileStatement(String);
+    method protected abstract androidx.room.InvalidationTracker createInvalidationTracker();
+    method protected abstract androidx.sqlite.db.SupportSQLiteOpenHelper createOpenHelper(androidx.room.DatabaseConfiguration!);
+    method @Deprecated public void endTransaction();
+    method public androidx.room.InvalidationTracker getInvalidationTracker();
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper getOpenHelper();
+    method public java.util.concurrent.Executor getQueryExecutor();
+    method public java.util.concurrent.Executor getTransactionExecutor();
+    method public <T> T? getTypeConverter(Class<T!>);
+    method public boolean inTransaction();
+    method @CallSuper public void init(androidx.room.DatabaseConfiguration);
+    method protected void internalInitInvalidationTracker(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public boolean isOpen();
+    method public android.database.Cursor query(String, Object![]?);
+    method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery);
+    method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery, android.os.CancellationSignal?);
+    method public void runInTransaction(Runnable);
+    method public <V> V! runInTransaction(java.util.concurrent.Callable<V!>);
+    method @Deprecated public void setTransactionSuccessful();
+    field @Deprecated protected volatile androidx.sqlite.db.SupportSQLiteDatabase! mDatabase;
+  }
+
+  public static class RoomDatabase.Builder<T extends androidx.room.RoomDatabase> {
+    method public androidx.room.RoomDatabase.Builder<T!> addCallback(androidx.room.RoomDatabase.Callback);
+    method public androidx.room.RoomDatabase.Builder<T!> addMigrations(androidx.room.migration.Migration!...);
+    method public androidx.room.RoomDatabase.Builder<T!> addTypeConverter(Object);
+    method public androidx.room.RoomDatabase.Builder<T!> allowMainThreadQueries();
+    method public T build();
+    method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+    method public androidx.room.RoomDatabase.Builder<T!> enableMultiInstanceInvalidation();
+    method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigration();
+    method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationFrom(int...);
+    method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationOnDowngrade();
+    method public androidx.room.RoomDatabase.Builder<T!> openHelperFactory(androidx.sqlite.db.SupportSQLiteOpenHelper.Factory?);
+    method @androidx.room.ExperimentalRoomApi public androidx.room.RoomDatabase.Builder<T!> setAutoCloseTimeout(@IntRange(from=0) long, java.util.concurrent.TimeUnit);
+    method public androidx.room.RoomDatabase.Builder<T!> setJournalMode(androidx.room.RoomDatabase.JournalMode);
+    method public androidx.room.RoomDatabase.Builder<T!> setQueryCallback(androidx.room.RoomDatabase.QueryCallback, java.util.concurrent.Executor);
+    method public androidx.room.RoomDatabase.Builder<T!> setQueryExecutor(java.util.concurrent.Executor);
+    method public androidx.room.RoomDatabase.Builder<T!> setTransactionExecutor(java.util.concurrent.Executor);
+  }
+
+  public abstract static class RoomDatabase.Callback {
+    ctor public RoomDatabase.Callback();
+    method public void onCreate(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onDestructiveMigration(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onOpen(androidx.sqlite.db.SupportSQLiteDatabase);
+  }
+
+  public enum RoomDatabase.JournalMode {
+    enum_constant public static final androidx.room.RoomDatabase.JournalMode AUTOMATIC;
+    enum_constant public static final androidx.room.RoomDatabase.JournalMode TRUNCATE;
+    enum_constant @RequiresApi(android.os.Build.VERSION_CODES.JELLY_BEAN) public static final androidx.room.RoomDatabase.JournalMode WRITE_AHEAD_LOGGING;
+  }
+
+  public static class RoomDatabase.MigrationContainer {
+    ctor public RoomDatabase.MigrationContainer();
+    method public void addMigrations(androidx.room.migration.Migration!...);
+    method public java.util.List<androidx.room.migration.Migration!>? findMigrationPath(int, int);
+  }
+
+  public abstract static class RoomDatabase.PrepackagedDatabaseCallback {
+    ctor public RoomDatabase.PrepackagedDatabaseCallback();
+    method public void onOpenPrepackagedDatabase(androidx.sqlite.db.SupportSQLiteDatabase);
+  }
+
+  public static interface RoomDatabase.QueryCallback {
+    method public void onQuery(String, java.util.List<java.lang.Object!>);
+  }
+
+}
+
+package androidx.room.migration {
+
+  public abstract class Migration {
+    ctor public Migration(int, int);
+    method public abstract void migrate(androidx.sqlite.db.SupportSQLiteDatabase);
+    field public final int endVersion;
+    field public final int startVersion;
+  }
+
+}
+
diff --git a/room/runtime/api/current.txt b/room/runtime/api/current.txt
index d9b5519..95f23cc 100644
--- a/room/runtime/api/current.txt
+++ b/room/runtime/api/current.txt
@@ -23,6 +23,9 @@
     field public final java.util.List<java.lang.Object!> typeConverters;
   }
 
+  @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface ExperimentalRoomApi {
+  }
+
   public class InvalidationTracker {
     method @WorkerThread public void addObserver(androidx.room.InvalidationTracker.Observer);
     method public void refreshVersionsAsync();
@@ -86,7 +89,7 @@
     method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationFrom(int...);
     method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationOnDowngrade();
     method public androidx.room.RoomDatabase.Builder<T!> openHelperFactory(androidx.sqlite.db.SupportSQLiteOpenHelper.Factory?);
-    method public androidx.room.RoomDatabase.Builder<T!> setAutoCloseTimeout(@IntRange(from=0) long, java.util.concurrent.TimeUnit);
+    method @androidx.room.ExperimentalRoomApi public androidx.room.RoomDatabase.Builder<T!> setAutoCloseTimeout(@IntRange(from=0) long, java.util.concurrent.TimeUnit);
     method public androidx.room.RoomDatabase.Builder<T!> setJournalMode(androidx.room.RoomDatabase.JournalMode);
     method public androidx.room.RoomDatabase.Builder<T!> setQueryCallback(androidx.room.RoomDatabase.QueryCallback, java.util.concurrent.Executor);
     method public androidx.room.RoomDatabase.Builder<T!> setQueryExecutor(java.util.concurrent.Executor);
diff --git a/room/runtime/api/public_plus_experimental_2.3.0-beta03.txt b/room/runtime/api/public_plus_experimental_2.3.0-beta03.txt
new file mode 100644
index 0000000..b1f6259
--- /dev/null
+++ b/room/runtime/api/public_plus_experimental_2.3.0-beta03.txt
@@ -0,0 +1,140 @@
+// Signature format: 4.0
+package androidx.room {
+
+  public class DatabaseConfiguration {
+    method public boolean isMigrationRequired(int, int);
+    method @Deprecated public boolean isMigrationRequiredFrom(int);
+    field public final boolean allowDestructiveMigrationOnDowngrade;
+    field public final boolean allowMainThreadQueries;
+    field public final java.util.List<androidx.room.RoomDatabase.Callback!>? callbacks;
+    field public final android.content.Context context;
+    field public final String? copyFromAssetPath;
+    field public final java.io.File? copyFromFile;
+    field public final java.util.concurrent.Callable<java.io.InputStream!>? copyFromInputStream;
+    field public final androidx.room.RoomDatabase.JournalMode! journalMode;
+    field public final androidx.room.RoomDatabase.MigrationContainer migrationContainer;
+    field public final boolean multiInstanceInvalidation;
+    field public final String? name;
+    field public final androidx.room.RoomDatabase.PrepackagedDatabaseCallback? prepackagedDatabaseCallback;
+    field public final java.util.concurrent.Executor queryExecutor;
+    field public final boolean requireMigration;
+    field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
+    field public final java.util.concurrent.Executor transactionExecutor;
+    field public final java.util.List<java.lang.Object!> typeConverters;
+  }
+
+  @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface ExperimentalRoomApi {
+  }
+
+  public class InvalidationTracker {
+    method @WorkerThread public void addObserver(androidx.room.InvalidationTracker.Observer);
+    method public void refreshVersionsAsync();
+    method @WorkerThread public void removeObserver(androidx.room.InvalidationTracker.Observer);
+  }
+
+  public abstract static class InvalidationTracker.Observer {
+    ctor protected InvalidationTracker.Observer(String, java.lang.String!...);
+    ctor public InvalidationTracker.Observer(String![]);
+    method public abstract void onInvalidated(java.util.Set<java.lang.String!>);
+  }
+
+  public class Room {
+    ctor @Deprecated public Room();
+    method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T!> databaseBuilder(android.content.Context, Class<T!>, String);
+    method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T!> inMemoryDatabaseBuilder(android.content.Context, Class<T!>);
+    field public static final String MASTER_TABLE_NAME = "room_master_table";
+  }
+
+  public abstract class RoomDatabase {
+    ctor public RoomDatabase();
+    method @Deprecated public void beginTransaction();
+    method @WorkerThread public abstract void clearAllTables();
+    method public void close();
+    method public androidx.sqlite.db.SupportSQLiteStatement! compileStatement(String);
+    method protected abstract androidx.room.InvalidationTracker createInvalidationTracker();
+    method protected abstract androidx.sqlite.db.SupportSQLiteOpenHelper createOpenHelper(androidx.room.DatabaseConfiguration!);
+    method @Deprecated public void endTransaction();
+    method public androidx.room.InvalidationTracker getInvalidationTracker();
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper getOpenHelper();
+    method public java.util.concurrent.Executor getQueryExecutor();
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) protected java.util.Map<java.lang.Class<?>!,java.util.List<java.lang.Class<?>!>!> getRequiredTypeConverters();
+    method public java.util.concurrent.Executor getTransactionExecutor();
+    method public <T> T? getTypeConverter(Class<T!>);
+    method public boolean inTransaction();
+    method @CallSuper public void init(androidx.room.DatabaseConfiguration);
+    method protected void internalInitInvalidationTracker(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public boolean isOpen();
+    method public android.database.Cursor query(String, Object![]?);
+    method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery);
+    method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery, android.os.CancellationSignal?);
+    method public void runInTransaction(Runnable);
+    method public <V> V! runInTransaction(java.util.concurrent.Callable<V!>);
+    method @Deprecated public void setTransactionSuccessful();
+    field @Deprecated protected volatile androidx.sqlite.db.SupportSQLiteDatabase! mDatabase;
+  }
+
+  public static class RoomDatabase.Builder<T extends androidx.room.RoomDatabase> {
+    method public androidx.room.RoomDatabase.Builder<T!> addCallback(androidx.room.RoomDatabase.Callback);
+    method public androidx.room.RoomDatabase.Builder<T!> addMigrations(androidx.room.migration.Migration!...);
+    method public androidx.room.RoomDatabase.Builder<T!> addTypeConverter(Object);
+    method public androidx.room.RoomDatabase.Builder<T!> allowMainThreadQueries();
+    method public T build();
+    method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+    method public androidx.room.RoomDatabase.Builder<T!> enableMultiInstanceInvalidation();
+    method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigration();
+    method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationFrom(int...);
+    method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationOnDowngrade();
+    method public androidx.room.RoomDatabase.Builder<T!> openHelperFactory(androidx.sqlite.db.SupportSQLiteOpenHelper.Factory?);
+    method @androidx.room.ExperimentalRoomApi public androidx.room.RoomDatabase.Builder<T!> setAutoCloseTimeout(@IntRange(from=0) long, java.util.concurrent.TimeUnit);
+    method public androidx.room.RoomDatabase.Builder<T!> setJournalMode(androidx.room.RoomDatabase.JournalMode);
+    method public androidx.room.RoomDatabase.Builder<T!> setQueryCallback(androidx.room.RoomDatabase.QueryCallback, java.util.concurrent.Executor);
+    method public androidx.room.RoomDatabase.Builder<T!> setQueryExecutor(java.util.concurrent.Executor);
+    method public androidx.room.RoomDatabase.Builder<T!> setTransactionExecutor(java.util.concurrent.Executor);
+  }
+
+  public abstract static class RoomDatabase.Callback {
+    ctor public RoomDatabase.Callback();
+    method public void onCreate(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onDestructiveMigration(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onOpen(androidx.sqlite.db.SupportSQLiteDatabase);
+  }
+
+  public enum RoomDatabase.JournalMode {
+    enum_constant public static final androidx.room.RoomDatabase.JournalMode AUTOMATIC;
+    enum_constant public static final androidx.room.RoomDatabase.JournalMode TRUNCATE;
+    enum_constant @RequiresApi(android.os.Build.VERSION_CODES.JELLY_BEAN) public static final androidx.room.RoomDatabase.JournalMode WRITE_AHEAD_LOGGING;
+  }
+
+  public static class RoomDatabase.MigrationContainer {
+    ctor public RoomDatabase.MigrationContainer();
+    method public void addMigrations(androidx.room.migration.Migration!...);
+    method public java.util.List<androidx.room.migration.Migration!>? findMigrationPath(int, int);
+  }
+
+  public abstract static class RoomDatabase.PrepackagedDatabaseCallback {
+    ctor public RoomDatabase.PrepackagedDatabaseCallback();
+    method public void onOpenPrepackagedDatabase(androidx.sqlite.db.SupportSQLiteDatabase);
+  }
+
+  public static interface RoomDatabase.QueryCallback {
+    method public void onQuery(String, java.util.List<java.lang.Object!>);
+  }
+
+}
+
+package androidx.room.migration {
+
+  public abstract class Migration {
+    ctor public Migration(int, int);
+    method public abstract void migrate(androidx.sqlite.db.SupportSQLiteDatabase);
+    field public final int endVersion;
+    field public final int startVersion;
+  }
+
+}
+
diff --git a/room/runtime/api/public_plus_experimental_current.txt b/room/runtime/api/public_plus_experimental_current.txt
index d0cfe70..b1f6259 100644
--- a/room/runtime/api/public_plus_experimental_current.txt
+++ b/room/runtime/api/public_plus_experimental_current.txt
@@ -23,6 +23,9 @@
     field public final java.util.List<java.lang.Object!> typeConverters;
   }
 
+  @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface ExperimentalRoomApi {
+  }
+
   public class InvalidationTracker {
     method @WorkerThread public void addObserver(androidx.room.InvalidationTracker.Observer);
     method public void refreshVersionsAsync();
@@ -87,7 +90,7 @@
     method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationFrom(int...);
     method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationOnDowngrade();
     method public androidx.room.RoomDatabase.Builder<T!> openHelperFactory(androidx.sqlite.db.SupportSQLiteOpenHelper.Factory?);
-    method public androidx.room.RoomDatabase.Builder<T!> setAutoCloseTimeout(@IntRange(from=0) long, java.util.concurrent.TimeUnit);
+    method @androidx.room.ExperimentalRoomApi public androidx.room.RoomDatabase.Builder<T!> setAutoCloseTimeout(@IntRange(from=0) long, java.util.concurrent.TimeUnit);
     method public androidx.room.RoomDatabase.Builder<T!> setJournalMode(androidx.room.RoomDatabase.JournalMode);
     method public androidx.room.RoomDatabase.Builder<T!> setQueryCallback(androidx.room.RoomDatabase.QueryCallback, java.util.concurrent.Executor);
     method public androidx.room.RoomDatabase.Builder<T!> setQueryExecutor(java.util.concurrent.Executor);
diff --git a/room/runtime/api/res-2.3.0-beta03.txt b/room/runtime/api/res-2.3.0-beta03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/runtime/api/res-2.3.0-beta03.txt
diff --git a/room/runtime/api/restricted_2.3.0-beta03.txt b/room/runtime/api/restricted_2.3.0-beta03.txt
new file mode 100644
index 0000000..52fb0a1
--- /dev/null
+++ b/room/runtime/api/restricted_2.3.0-beta03.txt
@@ -0,0 +1,339 @@
+// Signature format: 4.0
+package androidx.room {
+
+  public class DatabaseConfiguration {
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode!, java.util.concurrent.Executor, boolean, java.util.Set<java.lang.Integer!>?);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode!, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode!, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?, androidx.room.RoomDatabase.PrepackagedDatabaseCallback?);
+    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?, androidx.room.RoomDatabase.PrepackagedDatabaseCallback?, java.util.List<java.lang.Object!>?);
+    method public boolean isMigrationRequired(int, int);
+    method @Deprecated public boolean isMigrationRequiredFrom(int);
+    field public final boolean allowDestructiveMigrationOnDowngrade;
+    field public final boolean allowMainThreadQueries;
+    field public final java.util.List<androidx.room.RoomDatabase.Callback!>? callbacks;
+    field public final android.content.Context context;
+    field public final String? copyFromAssetPath;
+    field public final java.io.File? copyFromFile;
+    field public final java.util.concurrent.Callable<java.io.InputStream!>? copyFromInputStream;
+    field public final androidx.room.RoomDatabase.JournalMode! journalMode;
+    field public final androidx.room.RoomDatabase.MigrationContainer migrationContainer;
+    field public final boolean multiInstanceInvalidation;
+    field public final String? name;
+    field public final androidx.room.RoomDatabase.PrepackagedDatabaseCallback? prepackagedDatabaseCallback;
+    field public final java.util.concurrent.Executor queryExecutor;
+    field public final boolean requireMigration;
+    field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
+    field public final java.util.concurrent.Executor transactionExecutor;
+    field public final java.util.List<java.lang.Object!> typeConverters;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class EntityDeletionOrUpdateAdapter<T> extends androidx.room.SharedSQLiteStatement {
+    ctor public EntityDeletionOrUpdateAdapter(androidx.room.RoomDatabase!);
+    method protected abstract void bind(androidx.sqlite.db.SupportSQLiteStatement!, T!);
+    method public final int handle(T!);
+    method public final int handleMultiple(Iterable<? extends T>!);
+    method public final int handleMultiple(T![]!);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class EntityInsertionAdapter<T> extends androidx.room.SharedSQLiteStatement {
+    ctor public EntityInsertionAdapter(androidx.room.RoomDatabase!);
+    method protected abstract void bind(androidx.sqlite.db.SupportSQLiteStatement!, T!);
+    method public final void insert(T!);
+    method public final void insert(T![]!);
+    method public final void insert(Iterable<? extends T>!);
+    method public final long insertAndReturnId(T!);
+    method public final long[]! insertAndReturnIdsArray(java.util.Collection<? extends T>!);
+    method public final long[]! insertAndReturnIdsArray(T![]!);
+    method public final Long![]! insertAndReturnIdsArrayBox(java.util.Collection<? extends T>!);
+    method public final Long![]! insertAndReturnIdsArrayBox(T![]!);
+    method public final java.util.List<java.lang.Long!>! insertAndReturnIdsList(T![]!);
+    method public final java.util.List<java.lang.Long!>! insertAndReturnIdsList(java.util.Collection<? extends T>!);
+  }
+
+  @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface ExperimentalRoomApi {
+  }
+
+  public class InvalidationTracker {
+    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public InvalidationTracker(androidx.room.RoomDatabase!, java.lang.String!...);
+    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public InvalidationTracker(androidx.room.RoomDatabase!, java.util.Map<java.lang.String!,java.lang.String!>!, java.util.Map<java.lang.String!,java.util.Set<java.lang.String!>!>!, java.lang.String!...);
+    method @WorkerThread public void addObserver(androidx.room.InvalidationTracker.Observer);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void addWeakObserver(androidx.room.InvalidationTracker.Observer!);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T!>! createLiveData(String![]!, java.util.concurrent.Callable<T!>!);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T!>! createLiveData(String![]!, boolean, java.util.concurrent.Callable<T!>!);
+    method public void refreshVersionsAsync();
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @WorkerThread public void refreshVersionsSync();
+    method @WorkerThread public void removeObserver(androidx.room.InvalidationTracker.Observer);
+  }
+
+  public abstract static class InvalidationTracker.Observer {
+    ctor protected InvalidationTracker.Observer(String, java.lang.String!...);
+    ctor public InvalidationTracker.Observer(String![]);
+    method public abstract void onInvalidated(java.util.Set<java.lang.String!>);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class MultiInstanceInvalidationService extends android.app.Service {
+    ctor public MultiInstanceInvalidationService();
+    method public android.os.IBinder? onBind(android.content.Intent!);
+  }
+
+  public class Room {
+    ctor @Deprecated public Room();
+    method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T!> databaseBuilder(android.content.Context, Class<T!>, String);
+    method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T!> inMemoryDatabaseBuilder(android.content.Context, Class<T!>);
+    field public static final String MASTER_TABLE_NAME = "room_master_table";
+  }
+
+  public abstract class RoomDatabase {
+    ctor public RoomDatabase();
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void assertNotMainThread();
+    method @Deprecated public void beginTransaction();
+    method @WorkerThread public abstract void clearAllTables();
+    method public void close();
+    method public androidx.sqlite.db.SupportSQLiteStatement! compileStatement(String);
+    method protected abstract androidx.room.InvalidationTracker createInvalidationTracker();
+    method protected abstract androidx.sqlite.db.SupportSQLiteOpenHelper createOpenHelper(androidx.room.DatabaseConfiguration!);
+    method @Deprecated public void endTransaction();
+    method public androidx.room.InvalidationTracker getInvalidationTracker();
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper getOpenHelper();
+    method public java.util.concurrent.Executor getQueryExecutor();
+    method public java.util.concurrent.Executor getTransactionExecutor();
+    method public <T> T? getTypeConverter(Class<T!>);
+    method public boolean inTransaction();
+    method @CallSuper public void init(androidx.room.DatabaseConfiguration);
+    method protected void internalInitInvalidationTracker(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public boolean isOpen();
+    method public android.database.Cursor query(String, Object![]?);
+    method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery);
+    method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery, android.os.CancellationSignal?);
+    method public void runInTransaction(Runnable);
+    method public <V> V! runInTransaction(java.util.concurrent.Callable<V!>);
+    method @Deprecated public void setTransactionSuccessful();
+    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MAX_BIND_PARAMETER_CNT = 999; // 0x3e7
+    field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected java.util.List<androidx.room.RoomDatabase.Callback!>? mCallbacks;
+    field @Deprecated protected volatile androidx.sqlite.db.SupportSQLiteDatabase! mDatabase;
+  }
+
+  public static class RoomDatabase.Builder<T extends androidx.room.RoomDatabase> {
+    method public androidx.room.RoomDatabase.Builder<T!> addCallback(androidx.room.RoomDatabase.Callback);
+    method public androidx.room.RoomDatabase.Builder<T!> addMigrations(androidx.room.migration.Migration!...);
+    method public androidx.room.RoomDatabase.Builder<T!> addTypeConverter(Object);
+    method public androidx.room.RoomDatabase.Builder<T!> allowMainThreadQueries();
+    method public T build();
+    method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+    method public androidx.room.RoomDatabase.Builder<T!> enableMultiInstanceInvalidation();
+    method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigration();
+    method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationFrom(int...);
+    method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationOnDowngrade();
+    method public androidx.room.RoomDatabase.Builder<T!> openHelperFactory(androidx.sqlite.db.SupportSQLiteOpenHelper.Factory?);
+    method @androidx.room.ExperimentalRoomApi public androidx.room.RoomDatabase.Builder<T!> setAutoCloseTimeout(@IntRange(from=0) long, java.util.concurrent.TimeUnit);
+    method public androidx.room.RoomDatabase.Builder<T!> setJournalMode(androidx.room.RoomDatabase.JournalMode);
+    method public androidx.room.RoomDatabase.Builder<T!> setQueryCallback(androidx.room.RoomDatabase.QueryCallback, java.util.concurrent.Executor);
+    method public androidx.room.RoomDatabase.Builder<T!> setQueryExecutor(java.util.concurrent.Executor);
+    method public androidx.room.RoomDatabase.Builder<T!> setTransactionExecutor(java.util.concurrent.Executor);
+  }
+
+  public abstract static class RoomDatabase.Callback {
+    ctor public RoomDatabase.Callback();
+    method public void onCreate(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onDestructiveMigration(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onOpen(androidx.sqlite.db.SupportSQLiteDatabase);
+  }
+
+  public enum RoomDatabase.JournalMode {
+    enum_constant public static final androidx.room.RoomDatabase.JournalMode AUTOMATIC;
+    enum_constant public static final androidx.room.RoomDatabase.JournalMode TRUNCATE;
+    enum_constant @RequiresApi(android.os.Build.VERSION_CODES.JELLY_BEAN) public static final androidx.room.RoomDatabase.JournalMode WRITE_AHEAD_LOGGING;
+  }
+
+  public static class RoomDatabase.MigrationContainer {
+    ctor public RoomDatabase.MigrationContainer();
+    method public void addMigrations(androidx.room.migration.Migration!...);
+    method public java.util.List<androidx.room.migration.Migration!>? findMigrationPath(int, int);
+  }
+
+  public abstract static class RoomDatabase.PrepackagedDatabaseCallback {
+    ctor public RoomDatabase.PrepackagedDatabaseCallback();
+    method public void onOpenPrepackagedDatabase(androidx.sqlite.db.SupportSQLiteDatabase);
+  }
+
+  public static interface RoomDatabase.QueryCallback {
+    method public void onQuery(String, java.util.List<java.lang.Object!>);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class RoomOpenHelper extends androidx.sqlite.db.SupportSQLiteOpenHelper.Callback {
+    ctor public RoomOpenHelper(androidx.room.DatabaseConfiguration, androidx.room.RoomOpenHelper.Delegate, String, String);
+    ctor public RoomOpenHelper(androidx.room.DatabaseConfiguration, androidx.room.RoomOpenHelper.Delegate, String);
+    method public void onCreate(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method public void onUpgrade(androidx.sqlite.db.SupportSQLiteDatabase!, int, int);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract static class RoomOpenHelper.Delegate {
+    ctor public RoomOpenHelper.Delegate(int);
+    method protected abstract void createAllTables(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method protected abstract void dropAllTables(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method protected abstract void onCreate(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method protected abstract void onOpen(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method protected void onPostMigrate(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method protected void onPreMigrate(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method protected androidx.room.RoomOpenHelper.ValidationResult onValidateSchema(androidx.sqlite.db.SupportSQLiteDatabase);
+    method @Deprecated protected void validateMigration(androidx.sqlite.db.SupportSQLiteDatabase!);
+    field public final int version;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class RoomOpenHelper.ValidationResult {
+    ctor public RoomOpenHelper.ValidationResult(boolean, String?);
+    field public final String? expectedFoundMsg;
+    field public final boolean isValid;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class RoomSQLiteQuery implements androidx.sqlite.db.SupportSQLiteProgram androidx.sqlite.db.SupportSQLiteQuery {
+    method public static androidx.room.RoomSQLiteQuery! acquire(String!, int);
+    method public void bindBlob(int, byte[]!);
+    method public void bindDouble(int, double);
+    method public void bindLong(int, long);
+    method public void bindNull(int);
+    method public void bindString(int, String!);
+    method public void bindTo(androidx.sqlite.db.SupportSQLiteProgram!);
+    method public void clearBindings();
+    method public void close();
+    method public void copyArgumentsFrom(androidx.room.RoomSQLiteQuery!);
+    method public static androidx.room.RoomSQLiteQuery! copyFrom(androidx.sqlite.db.SupportSQLiteQuery!);
+    method public int getArgCount();
+    method public String! getSql();
+    method public void release();
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class SharedSQLiteStatement {
+    ctor public SharedSQLiteStatement(androidx.room.RoomDatabase!);
+    method public androidx.sqlite.db.SupportSQLiteStatement! acquire();
+    method protected void assertNotMainThread();
+    method protected abstract String! createQuery();
+    method public void release(androidx.sqlite.db.SupportSQLiteStatement!);
+  }
+
+}
+
+package androidx.room.migration {
+
+  public abstract class Migration {
+    ctor public Migration(int, int);
+    method public abstract void migrate(androidx.sqlite.db.SupportSQLiteDatabase);
+    field public final int endVersion;
+    field public final int startVersion;
+  }
+
+}
+
+package androidx.room.paging {
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class LimitOffsetDataSource<T> extends androidx.paging.PositionalDataSource<T> {
+    ctor protected LimitOffsetDataSource(androidx.room.RoomDatabase!, androidx.sqlite.db.SupportSQLiteQuery!, boolean, java.lang.String!...);
+    ctor protected LimitOffsetDataSource(androidx.room.RoomDatabase!, androidx.room.RoomSQLiteQuery!, boolean, java.lang.String!...);
+    method protected abstract java.util.List<T!>! convertRows(android.database.Cursor!);
+    method public void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams, androidx.paging.PositionalDataSource.LoadInitialCallback<T!>);
+    method public void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams, androidx.paging.PositionalDataSource.LoadRangeCallback<T!>);
+  }
+
+}
+
+package androidx.room.util {
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class CopyLock {
+    ctor public CopyLock(String, java.io.File, boolean);
+    method public void lock();
+    method public void unlock();
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class CursorUtil {
+    method public static android.database.Cursor copyAndClose(android.database.Cursor);
+    method public static int getColumnIndex(android.database.Cursor, String);
+    method public static int getColumnIndexOrThrow(android.database.Cursor, String);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DBUtil {
+    method public static android.os.CancellationSignal? createCancellationSignal();
+    method public static void dropFtsSyncTriggers(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method @Deprecated public static android.database.Cursor query(androidx.room.RoomDatabase!, androidx.sqlite.db.SupportSQLiteQuery!, boolean);
+    method public static android.database.Cursor query(androidx.room.RoomDatabase, androidx.sqlite.db.SupportSQLiteQuery, boolean, android.os.CancellationSignal?);
+    method public static int readVersion(java.io.File) throws java.io.IOException;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FileUtil {
+    method public static void copy(java.nio.channels.ReadableByteChannel, java.nio.channels.FileChannel) throws java.io.IOException;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class FtsTableInfo {
+    ctor public FtsTableInfo(String!, java.util.Set<java.lang.String!>!, java.util.Set<java.lang.String!>!);
+    ctor public FtsTableInfo(String!, java.util.Set<java.lang.String!>!, String!);
+    method public static androidx.room.util.FtsTableInfo! read(androidx.sqlite.db.SupportSQLiteDatabase!, String!);
+    field public final java.util.Set<java.lang.String!>! columns;
+    field public final String! name;
+    field public final java.util.Set<java.lang.String!>! options;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class StringUtil {
+    method public static void appendPlaceholders(StringBuilder!, int);
+    method public static String? joinIntoString(java.util.List<java.lang.Integer!>?);
+    method public static StringBuilder! newStringBuilder();
+    method public static java.util.List<java.lang.Integer!>? splitToIntList(String?);
+    field public static final String![]! EMPTY_STRING_ARRAY;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class TableInfo {
+    ctor public TableInfo(String!, java.util.Map<java.lang.String!,androidx.room.util.TableInfo.Column!>!, java.util.Set<androidx.room.util.TableInfo.ForeignKey!>!, java.util.Set<androidx.room.util.TableInfo.Index!>!);
+    ctor public TableInfo(String!, java.util.Map<java.lang.String!,androidx.room.util.TableInfo.Column!>!, java.util.Set<androidx.room.util.TableInfo.ForeignKey!>!);
+    method public static androidx.room.util.TableInfo! read(androidx.sqlite.db.SupportSQLiteDatabase!, String!);
+    field public static final int CREATED_FROM_DATABASE = 2; // 0x2
+    field public static final int CREATED_FROM_ENTITY = 1; // 0x1
+    field public static final int CREATED_FROM_UNKNOWN = 0; // 0x0
+    field public final java.util.Map<java.lang.String!,androidx.room.util.TableInfo.Column!>! columns;
+    field public final java.util.Set<androidx.room.util.TableInfo.ForeignKey!>! foreignKeys;
+    field public final java.util.Set<androidx.room.util.TableInfo.Index!>? indices;
+    field public final String! name;
+  }
+
+  public static final class TableInfo.Column {
+    ctor @Deprecated public TableInfo.Column(String!, String!, boolean, int);
+    ctor public TableInfo.Column(String!, String!, boolean, int, String!, int);
+    method public boolean isPrimaryKey();
+    field @androidx.room.ColumnInfo.SQLiteTypeAffinity public final int affinity;
+    field public final String! defaultValue;
+    field public final String! name;
+    field public final boolean notNull;
+    field public final int primaryKeyPosition;
+    field public final String! type;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final class TableInfo.ForeignKey {
+    ctor public TableInfo.ForeignKey(String, String, String, java.util.List<java.lang.String!>, java.util.List<java.lang.String!>);
+    field public final java.util.List<java.lang.String!> columnNames;
+    field public final String onDelete;
+    field public final String onUpdate;
+    field public final java.util.List<java.lang.String!> referenceColumnNames;
+    field public final String referenceTable;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final class TableInfo.Index {
+    ctor public TableInfo.Index(String!, boolean, java.util.List<java.lang.String!>!);
+    field public static final String DEFAULT_PREFIX = "index_";
+    field public final java.util.List<java.lang.String!>! columns;
+    field public final String! name;
+    field public final boolean unique;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class ViewInfo {
+    ctor public ViewInfo(String!, String!);
+    method public static androidx.room.util.ViewInfo! read(androidx.sqlite.db.SupportSQLiteDatabase!, String!);
+    field public final String! name;
+    field public final String! sql;
+  }
+
+}
+
diff --git a/room/runtime/api/restricted_current.txt b/room/runtime/api/restricted_current.txt
index 0db4e83..52fb0a1 100644
--- a/room/runtime/api/restricted_current.txt
+++ b/room/runtime/api/restricted_current.txt
@@ -52,6 +52,9 @@
     method public final java.util.List<java.lang.Long!>! insertAndReturnIdsList(java.util.Collection<? extends T>!);
   }
 
+  @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface ExperimentalRoomApi {
+  }
+
   public class InvalidationTracker {
     ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public InvalidationTracker(androidx.room.RoomDatabase!, java.lang.String!...);
     ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public InvalidationTracker(androidx.room.RoomDatabase!, java.util.Map<java.lang.String!,java.lang.String!>!, java.util.Map<java.lang.String!,java.util.Set<java.lang.String!>!>!, java.lang.String!...);
@@ -129,7 +132,7 @@
     method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationFrom(int...);
     method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationOnDowngrade();
     method public androidx.room.RoomDatabase.Builder<T!> openHelperFactory(androidx.sqlite.db.SupportSQLiteOpenHelper.Factory?);
-    method public androidx.room.RoomDatabase.Builder<T!> setAutoCloseTimeout(@IntRange(from=0) long, java.util.concurrent.TimeUnit);
+    method @androidx.room.ExperimentalRoomApi public androidx.room.RoomDatabase.Builder<T!> setAutoCloseTimeout(@IntRange(from=0) long, java.util.concurrent.TimeUnit);
     method public androidx.room.RoomDatabase.Builder<T!> setJournalMode(androidx.room.RoomDatabase.JournalMode);
     method public androidx.room.RoomDatabase.Builder<T!> setQueryCallback(androidx.room.RoomDatabase.QueryCallback, java.util.concurrent.Executor);
     method public androidx.room.RoomDatabase.Builder<T!> setQueryExecutor(java.util.concurrent.Executor);
diff --git a/room/runtime/build.gradle b/room/runtime/build.gradle
index 651e93f..31b6246 100644
--- a/room/runtime/build.gradle
+++ b/room/runtime/build.gradle
@@ -42,7 +42,7 @@
     implementation("androidx.arch.core:core-runtime:2.0.1")
     compileOnly("androidx.paging:paging-common:2.0.0")
     compileOnly("androidx.lifecycle:lifecycle-livedata-core:2.0.0")
-    implementation(project(":annotation:annotation-experimental"))
+    implementation("androidx.annotation:annotation-experimental:1.1.0-beta01")
     compileOnly KOTLIN_STDLIB // Due to :annotation-experimental
 
     testImplementation("androidx.arch.core:core-testing:2.0.1")
diff --git a/room/runtime/src/main/java/androidx/room/ExperimentalRoomApi.java b/room/runtime/src/main/java/androidx/room/ExperimentalRoomApi.java
index 7396429..729a4a9 100644
--- a/room/runtime/src/main/java/androidx/room/ExperimentalRoomApi.java
+++ b/room/runtime/src/main/java/androidx/room/ExperimentalRoomApi.java
@@ -26,4 +26,4 @@
  */
 @Target({ElementType.METHOD})
 @RequiresOptIn()
-@interface ExperimentalRoomApi {}
+public @interface ExperimentalRoomApi {}
diff --git a/room/rxjava2/api/2.3.0-beta03.txt b/room/rxjava2/api/2.3.0-beta03.txt
new file mode 100644
index 0000000..64b6fe4
--- /dev/null
+++ b/room/rxjava2/api/2.3.0-beta03.txt
@@ -0,0 +1,16 @@
+// Signature format: 4.0
+package androidx.room {
+
+  public class EmptyResultSetException extends java.lang.RuntimeException {
+    ctor public EmptyResultSetException(String!);
+  }
+
+  public class RxRoom {
+    ctor @Deprecated public RxRoom();
+    method public static io.reactivex.Flowable<java.lang.Object!>! createFlowable(androidx.room.RoomDatabase!, java.lang.String!...);
+    method public static io.reactivex.Observable<java.lang.Object!>! createObservable(androidx.room.RoomDatabase!, java.lang.String!...);
+    field public static final Object! NOTHING;
+  }
+
+}
+
diff --git a/room/rxjava2/api/public_plus_experimental_2.3.0-beta03.txt b/room/rxjava2/api/public_plus_experimental_2.3.0-beta03.txt
new file mode 100644
index 0000000..64b6fe4
--- /dev/null
+++ b/room/rxjava2/api/public_plus_experimental_2.3.0-beta03.txt
@@ -0,0 +1,16 @@
+// Signature format: 4.0
+package androidx.room {
+
+  public class EmptyResultSetException extends java.lang.RuntimeException {
+    ctor public EmptyResultSetException(String!);
+  }
+
+  public class RxRoom {
+    ctor @Deprecated public RxRoom();
+    method public static io.reactivex.Flowable<java.lang.Object!>! createFlowable(androidx.room.RoomDatabase!, java.lang.String!...);
+    method public static io.reactivex.Observable<java.lang.Object!>! createObservable(androidx.room.RoomDatabase!, java.lang.String!...);
+    field public static final Object! NOTHING;
+  }
+
+}
+
diff --git a/room/rxjava2/api/res-2.3.0-beta03.txt b/room/rxjava2/api/res-2.3.0-beta03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/rxjava2/api/res-2.3.0-beta03.txt
diff --git a/room/rxjava2/api/restricted_2.3.0-beta03.txt b/room/rxjava2/api/restricted_2.3.0-beta03.txt
new file mode 100644
index 0000000..5505f93
--- /dev/null
+++ b/room/rxjava2/api/restricted_2.3.0-beta03.txt
@@ -0,0 +1,21 @@
+// Signature format: 4.0
+package androidx.room {
+
+  public class EmptyResultSetException extends java.lang.RuntimeException {
+    ctor public EmptyResultSetException(String!);
+  }
+
+  public class RxRoom {
+    ctor @Deprecated public RxRoom();
+    method public static io.reactivex.Flowable<java.lang.Object!>! createFlowable(androidx.room.RoomDatabase!, java.lang.String!...);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T!>! createFlowable(androidx.room.RoomDatabase!, String![]!, java.util.concurrent.Callable<T!>!);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T!>! createFlowable(androidx.room.RoomDatabase!, boolean, String![]!, java.util.concurrent.Callable<T!>!);
+    method public static io.reactivex.Observable<java.lang.Object!>! createObservable(androidx.room.RoomDatabase!, java.lang.String!...);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T!>! createObservable(androidx.room.RoomDatabase!, String![]!, java.util.concurrent.Callable<T!>!);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T!>! createObservable(androidx.room.RoomDatabase!, boolean, String![]!, java.util.concurrent.Callable<T!>!);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Single<T!>! createSingle(java.util.concurrent.Callable<T!>!);
+    field public static final Object! NOTHING;
+  }
+
+}
+
diff --git a/room/rxjava3/api/2.3.0-beta03.txt b/room/rxjava3/api/2.3.0-beta03.txt
new file mode 100644
index 0000000..6b78281
--- /dev/null
+++ b/room/rxjava3/api/2.3.0-beta03.txt
@@ -0,0 +1,15 @@
+// Signature format: 4.0
+package androidx.room.rxjava3 {
+
+  public final class EmptyResultSetException extends java.lang.RuntimeException {
+    ctor public EmptyResultSetException(String);
+  }
+
+  public final class RxRoom {
+    method public static io.reactivex.rxjava3.core.Flowable<java.lang.Object!> createFlowable(androidx.room.RoomDatabase, java.lang.String!...);
+    method public static io.reactivex.rxjava3.core.Observable<java.lang.Object!> createObservable(androidx.room.RoomDatabase, java.lang.String!...);
+    field public static final Object NOTHING;
+  }
+
+}
+
diff --git a/room/rxjava3/api/public_plus_experimental_2.3.0-beta03.txt b/room/rxjava3/api/public_plus_experimental_2.3.0-beta03.txt
new file mode 100644
index 0000000..6b78281
--- /dev/null
+++ b/room/rxjava3/api/public_plus_experimental_2.3.0-beta03.txt
@@ -0,0 +1,15 @@
+// Signature format: 4.0
+package androidx.room.rxjava3 {
+
+  public final class EmptyResultSetException extends java.lang.RuntimeException {
+    ctor public EmptyResultSetException(String);
+  }
+
+  public final class RxRoom {
+    method public static io.reactivex.rxjava3.core.Flowable<java.lang.Object!> createFlowable(androidx.room.RoomDatabase, java.lang.String!...);
+    method public static io.reactivex.rxjava3.core.Observable<java.lang.Object!> createObservable(androidx.room.RoomDatabase, java.lang.String!...);
+    field public static final Object NOTHING;
+  }
+
+}
+
diff --git a/room/rxjava3/api/res-2.3.0-beta03.txt b/room/rxjava3/api/res-2.3.0-beta03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/rxjava3/api/res-2.3.0-beta03.txt
diff --git a/room/rxjava3/api/restricted_2.3.0-beta03.txt b/room/rxjava3/api/restricted_2.3.0-beta03.txt
new file mode 100644
index 0000000..0680710
--- /dev/null
+++ b/room/rxjava3/api/restricted_2.3.0-beta03.txt
@@ -0,0 +1,18 @@
+// Signature format: 4.0
+package androidx.room.rxjava3 {
+
+  public final class EmptyResultSetException extends java.lang.RuntimeException {
+    ctor public EmptyResultSetException(String);
+  }
+
+  public final class RxRoom {
+    method public static io.reactivex.rxjava3.core.Flowable<java.lang.Object!> createFlowable(androidx.room.RoomDatabase, java.lang.String!...);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Flowable<T!> createFlowable(androidx.room.RoomDatabase, boolean, String![], java.util.concurrent.Callable<T!>);
+    method public static io.reactivex.rxjava3.core.Observable<java.lang.Object!> createObservable(androidx.room.RoomDatabase, java.lang.String!...);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Observable<T!> createObservable(androidx.room.RoomDatabase, boolean, String![], java.util.concurrent.Callable<T!>);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Single<T!> createSingle(java.util.concurrent.Callable<T!>);
+    field public static final Object NOTHING;
+  }
+
+}
+
diff --git a/room/testing/api/2.3.0-beta03.txt b/room/testing/api/2.3.0-beta03.txt
new file mode 100644
index 0000000..891c1b7
--- /dev/null
+++ b/room/testing/api/2.3.0-beta03.txt
@@ -0,0 +1,14 @@
+// Signature format: 4.0
+package androidx.room.testing {
+
+  public class MigrationTestHelper extends org.junit.rules.TestWatcher {
+    ctor public MigrationTestHelper(android.app.Instrumentation!, String!);
+    ctor public MigrationTestHelper(android.app.Instrumentation!, String!, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory!);
+    method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method public void closeWhenFinished(androidx.room.RoomDatabase!);
+    method public androidx.sqlite.db.SupportSQLiteDatabase! createDatabase(String!, int) throws java.io.IOException;
+    method public androidx.sqlite.db.SupportSQLiteDatabase! runMigrationsAndValidate(String!, int, boolean, androidx.room.migration.Migration!...) throws java.io.IOException;
+  }
+
+}
+
diff --git a/room/testing/api/public_plus_experimental_2.3.0-beta03.txt b/room/testing/api/public_plus_experimental_2.3.0-beta03.txt
new file mode 100644
index 0000000..891c1b7
--- /dev/null
+++ b/room/testing/api/public_plus_experimental_2.3.0-beta03.txt
@@ -0,0 +1,14 @@
+// Signature format: 4.0
+package androidx.room.testing {
+
+  public class MigrationTestHelper extends org.junit.rules.TestWatcher {
+    ctor public MigrationTestHelper(android.app.Instrumentation!, String!);
+    ctor public MigrationTestHelper(android.app.Instrumentation!, String!, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory!);
+    method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method public void closeWhenFinished(androidx.room.RoomDatabase!);
+    method public androidx.sqlite.db.SupportSQLiteDatabase! createDatabase(String!, int) throws java.io.IOException;
+    method public androidx.sqlite.db.SupportSQLiteDatabase! runMigrationsAndValidate(String!, int, boolean, androidx.room.migration.Migration!...) throws java.io.IOException;
+  }
+
+}
+
diff --git a/room/testing/api/res-2.3.0-beta03.txt b/room/testing/api/res-2.3.0-beta03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/testing/api/res-2.3.0-beta03.txt
diff --git a/room/testing/api/restricted_2.3.0-beta03.txt b/room/testing/api/restricted_2.3.0-beta03.txt
new file mode 100644
index 0000000..891c1b7
--- /dev/null
+++ b/room/testing/api/restricted_2.3.0-beta03.txt
@@ -0,0 +1,14 @@
+// Signature format: 4.0
+package androidx.room.testing {
+
+  public class MigrationTestHelper extends org.junit.rules.TestWatcher {
+    ctor public MigrationTestHelper(android.app.Instrumentation!, String!);
+    ctor public MigrationTestHelper(android.app.Instrumentation!, String!, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory!);
+    method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method public void closeWhenFinished(androidx.room.RoomDatabase!);
+    method public androidx.sqlite.db.SupportSQLiteDatabase! createDatabase(String!, int) throws java.io.IOException;
+    method public androidx.sqlite.db.SupportSQLiteDatabase! runMigrationsAndValidate(String!, int, boolean, androidx.room.migration.Migration!...) throws java.io.IOException;
+  }
+
+}
+
diff --git a/wear/wear-phone-interactions/api/current.txt b/wear/wear-phone-interactions/api/current.txt
index e73768b..0863cad 100644
--- a/wear/wear-phone-interactions/api/current.txt
+++ b/wear/wear-phone-interactions/api/current.txt
@@ -16,6 +16,90 @@
 
 }
 
+package androidx.wear.phone.interactions.authentication {
+
+  @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CodeChallenge {
+    ctor public CodeChallenge(androidx.wear.phone.interactions.authentication.CodeVerifier codeVerifier);
+    method public String getValue();
+  }
+
+  @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CodeVerifier {
+    ctor public CodeVerifier(optional int byteLength);
+    ctor public CodeVerifier(String value);
+    method public String getValue();
+  }
+
+  public final class OAuthRequest {
+    method public String getPackageName();
+    method public android.net.Uri getRequestUrl();
+    field public static final androidx.wear.phone.interactions.authentication.OAuthRequest.Companion Companion;
+    field public static final String WEAR_REDIRECT_URL_PREFIX = "https://wear.googleapis.com/3p_auth/";
+  }
+
+  public static final class OAuthRequest.Builder {
+    ctor public OAuthRequest.Builder(String packageName);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.wear.phone.interactions.authentication.OAuthRequest build();
+    method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setAuthProviderUrl(android.net.Uri authProviderUrl);
+    method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setClientId(String clientId);
+    method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setCodeChallenge(androidx.wear.phone.interactions.authentication.CodeChallenge codeChallenge);
+    method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setRedirectUrl(android.net.Uri redirectUrl);
+  }
+
+  public static final class OAuthRequest.Companion {
+  }
+
+  public final class OAuthResponse {
+    method public int getErrorCode();
+    method public android.net.Uri? getResponseUrl();
+  }
+
+  public static final class OAuthResponse.Builder {
+    ctor public OAuthResponse.Builder();
+    method public androidx.wear.phone.interactions.authentication.OAuthResponse build();
+    method public androidx.wear.phone.interactions.authentication.OAuthResponse.Builder setErrorCode(int errorCode);
+    method public androidx.wear.phone.interactions.authentication.OAuthResponse.Builder setResponseUrl(android.net.Uri responseUrl);
+  }
+
+  public final class RemoteAuthClient implements java.lang.AutoCloseable {
+    method @UiThread public void close();
+    method public static androidx.wear.phone.interactions.authentication.RemoteAuthClient create(android.content.Context context);
+    method protected void finalize();
+    method @UiThread public void sendAuthorizationRequest(androidx.wear.phone.interactions.authentication.OAuthRequest request, androidx.wear.phone.interactions.authentication.RemoteAuthClient.Callback clientCallback);
+    field public static final androidx.wear.phone.interactions.authentication.RemoteAuthClient.Companion Companion;
+    field public static final int ERROR_PHONE_UNAVAILABLE = 2; // 0x2
+    field public static final int ERROR_UNSUPPORTED = 1; // 0x1
+    field public static final int NO_ERROR = 0; // 0x0
+  }
+
+  public abstract static class RemoteAuthClient.Callback {
+    ctor public RemoteAuthClient.Callback();
+    method @UiThread public abstract void onAuthorizationError(int errorCode);
+    method @UiThread public abstract void onAuthorizationResponse(androidx.wear.phone.interactions.authentication.OAuthRequest request, androidx.wear.phone.interactions.authentication.OAuthResponse response);
+  }
+
+  public static final class RemoteAuthClient.Companion {
+    method public androidx.wear.phone.interactions.authentication.RemoteAuthClient create(android.content.Context context);
+  }
+
+  public interface RemoteAuthRequestHandler {
+    method public boolean isAuthSupported();
+    method public void sendAuthRequest(androidx.wear.phone.interactions.authentication.OAuthRequest request, kotlin.Pair<java.lang.String,java.lang.Integer> packageNameAndRequestId);
+  }
+
+  public abstract class RemoteAuthService extends android.app.Service {
+    ctor public RemoteAuthService();
+    method protected final android.os.IBinder onBind(android.content.Intent intent, androidx.wear.phone.interactions.authentication.RemoteAuthRequestHandler remoteAuthRequestHandler);
+    method public static final void sendResponseToCallback(androidx.wear.phone.interactions.authentication.OAuthResponse response, kotlin.Pair<java.lang.String,java.lang.Integer> packageNameAndRequestId);
+    method protected boolean verifyPackageName(android.content.Context context, String? requestPackageName);
+    field public static final androidx.wear.phone.interactions.authentication.RemoteAuthService.Companion Companion;
+  }
+
+  public static final class RemoteAuthService.Companion {
+    method public void sendResponseToCallback(androidx.wear.phone.interactions.authentication.OAuthResponse response, kotlin.Pair<java.lang.String,java.lang.Integer> packageNameAndRequestId);
+  }
+
+}
+
 package androidx.wear.phone.interactions.notifications {
 
   public final class BridgingConfig {
diff --git a/wear/wear-phone-interactions/api/public_plus_experimental_current.txt b/wear/wear-phone-interactions/api/public_plus_experimental_current.txt
index e73768b..0863cad 100644
--- a/wear/wear-phone-interactions/api/public_plus_experimental_current.txt
+++ b/wear/wear-phone-interactions/api/public_plus_experimental_current.txt
@@ -16,6 +16,90 @@
 
 }
 
+package androidx.wear.phone.interactions.authentication {
+
+  @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CodeChallenge {
+    ctor public CodeChallenge(androidx.wear.phone.interactions.authentication.CodeVerifier codeVerifier);
+    method public String getValue();
+  }
+
+  @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CodeVerifier {
+    ctor public CodeVerifier(optional int byteLength);
+    ctor public CodeVerifier(String value);
+    method public String getValue();
+  }
+
+  public final class OAuthRequest {
+    method public String getPackageName();
+    method public android.net.Uri getRequestUrl();
+    field public static final androidx.wear.phone.interactions.authentication.OAuthRequest.Companion Companion;
+    field public static final String WEAR_REDIRECT_URL_PREFIX = "https://wear.googleapis.com/3p_auth/";
+  }
+
+  public static final class OAuthRequest.Builder {
+    ctor public OAuthRequest.Builder(String packageName);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.wear.phone.interactions.authentication.OAuthRequest build();
+    method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setAuthProviderUrl(android.net.Uri authProviderUrl);
+    method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setClientId(String clientId);
+    method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setCodeChallenge(androidx.wear.phone.interactions.authentication.CodeChallenge codeChallenge);
+    method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setRedirectUrl(android.net.Uri redirectUrl);
+  }
+
+  public static final class OAuthRequest.Companion {
+  }
+
+  public final class OAuthResponse {
+    method public int getErrorCode();
+    method public android.net.Uri? getResponseUrl();
+  }
+
+  public static final class OAuthResponse.Builder {
+    ctor public OAuthResponse.Builder();
+    method public androidx.wear.phone.interactions.authentication.OAuthResponse build();
+    method public androidx.wear.phone.interactions.authentication.OAuthResponse.Builder setErrorCode(int errorCode);
+    method public androidx.wear.phone.interactions.authentication.OAuthResponse.Builder setResponseUrl(android.net.Uri responseUrl);
+  }
+
+  public final class RemoteAuthClient implements java.lang.AutoCloseable {
+    method @UiThread public void close();
+    method public static androidx.wear.phone.interactions.authentication.RemoteAuthClient create(android.content.Context context);
+    method protected void finalize();
+    method @UiThread public void sendAuthorizationRequest(androidx.wear.phone.interactions.authentication.OAuthRequest request, androidx.wear.phone.interactions.authentication.RemoteAuthClient.Callback clientCallback);
+    field public static final androidx.wear.phone.interactions.authentication.RemoteAuthClient.Companion Companion;
+    field public static final int ERROR_PHONE_UNAVAILABLE = 2; // 0x2
+    field public static final int ERROR_UNSUPPORTED = 1; // 0x1
+    field public static final int NO_ERROR = 0; // 0x0
+  }
+
+  public abstract static class RemoteAuthClient.Callback {
+    ctor public RemoteAuthClient.Callback();
+    method @UiThread public abstract void onAuthorizationError(int errorCode);
+    method @UiThread public abstract void onAuthorizationResponse(androidx.wear.phone.interactions.authentication.OAuthRequest request, androidx.wear.phone.interactions.authentication.OAuthResponse response);
+  }
+
+  public static final class RemoteAuthClient.Companion {
+    method public androidx.wear.phone.interactions.authentication.RemoteAuthClient create(android.content.Context context);
+  }
+
+  public interface RemoteAuthRequestHandler {
+    method public boolean isAuthSupported();
+    method public void sendAuthRequest(androidx.wear.phone.interactions.authentication.OAuthRequest request, kotlin.Pair<java.lang.String,java.lang.Integer> packageNameAndRequestId);
+  }
+
+  public abstract class RemoteAuthService extends android.app.Service {
+    ctor public RemoteAuthService();
+    method protected final android.os.IBinder onBind(android.content.Intent intent, androidx.wear.phone.interactions.authentication.RemoteAuthRequestHandler remoteAuthRequestHandler);
+    method public static final void sendResponseToCallback(androidx.wear.phone.interactions.authentication.OAuthResponse response, kotlin.Pair<java.lang.String,java.lang.Integer> packageNameAndRequestId);
+    method protected boolean verifyPackageName(android.content.Context context, String? requestPackageName);
+    field public static final androidx.wear.phone.interactions.authentication.RemoteAuthService.Companion Companion;
+  }
+
+  public static final class RemoteAuthService.Companion {
+    method public void sendResponseToCallback(androidx.wear.phone.interactions.authentication.OAuthResponse response, kotlin.Pair<java.lang.String,java.lang.Integer> packageNameAndRequestId);
+  }
+
+}
+
 package androidx.wear.phone.interactions.notifications {
 
   public final class BridgingConfig {
diff --git a/wear/wear-phone-interactions/api/restricted_current.txt b/wear/wear-phone-interactions/api/restricted_current.txt
index e73768b..0863cad 100644
--- a/wear/wear-phone-interactions/api/restricted_current.txt
+++ b/wear/wear-phone-interactions/api/restricted_current.txt
@@ -16,6 +16,90 @@
 
 }
 
+package androidx.wear.phone.interactions.authentication {
+
+  @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CodeChallenge {
+    ctor public CodeChallenge(androidx.wear.phone.interactions.authentication.CodeVerifier codeVerifier);
+    method public String getValue();
+  }
+
+  @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CodeVerifier {
+    ctor public CodeVerifier(optional int byteLength);
+    ctor public CodeVerifier(String value);
+    method public String getValue();
+  }
+
+  public final class OAuthRequest {
+    method public String getPackageName();
+    method public android.net.Uri getRequestUrl();
+    field public static final androidx.wear.phone.interactions.authentication.OAuthRequest.Companion Companion;
+    field public static final String WEAR_REDIRECT_URL_PREFIX = "https://wear.googleapis.com/3p_auth/";
+  }
+
+  public static final class OAuthRequest.Builder {
+    ctor public OAuthRequest.Builder(String packageName);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.wear.phone.interactions.authentication.OAuthRequest build();
+    method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setAuthProviderUrl(android.net.Uri authProviderUrl);
+    method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setClientId(String clientId);
+    method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setCodeChallenge(androidx.wear.phone.interactions.authentication.CodeChallenge codeChallenge);
+    method public androidx.wear.phone.interactions.authentication.OAuthRequest.Builder setRedirectUrl(android.net.Uri redirectUrl);
+  }
+
+  public static final class OAuthRequest.Companion {
+  }
+
+  public final class OAuthResponse {
+    method public int getErrorCode();
+    method public android.net.Uri? getResponseUrl();
+  }
+
+  public static final class OAuthResponse.Builder {
+    ctor public OAuthResponse.Builder();
+    method public androidx.wear.phone.interactions.authentication.OAuthResponse build();
+    method public androidx.wear.phone.interactions.authentication.OAuthResponse.Builder setErrorCode(int errorCode);
+    method public androidx.wear.phone.interactions.authentication.OAuthResponse.Builder setResponseUrl(android.net.Uri responseUrl);
+  }
+
+  public final class RemoteAuthClient implements java.lang.AutoCloseable {
+    method @UiThread public void close();
+    method public static androidx.wear.phone.interactions.authentication.RemoteAuthClient create(android.content.Context context);
+    method protected void finalize();
+    method @UiThread public void sendAuthorizationRequest(androidx.wear.phone.interactions.authentication.OAuthRequest request, androidx.wear.phone.interactions.authentication.RemoteAuthClient.Callback clientCallback);
+    field public static final androidx.wear.phone.interactions.authentication.RemoteAuthClient.Companion Companion;
+    field public static final int ERROR_PHONE_UNAVAILABLE = 2; // 0x2
+    field public static final int ERROR_UNSUPPORTED = 1; // 0x1
+    field public static final int NO_ERROR = 0; // 0x0
+  }
+
+  public abstract static class RemoteAuthClient.Callback {
+    ctor public RemoteAuthClient.Callback();
+    method @UiThread public abstract void onAuthorizationError(int errorCode);
+    method @UiThread public abstract void onAuthorizationResponse(androidx.wear.phone.interactions.authentication.OAuthRequest request, androidx.wear.phone.interactions.authentication.OAuthResponse response);
+  }
+
+  public static final class RemoteAuthClient.Companion {
+    method public androidx.wear.phone.interactions.authentication.RemoteAuthClient create(android.content.Context context);
+  }
+
+  public interface RemoteAuthRequestHandler {
+    method public boolean isAuthSupported();
+    method public void sendAuthRequest(androidx.wear.phone.interactions.authentication.OAuthRequest request, kotlin.Pair<java.lang.String,java.lang.Integer> packageNameAndRequestId);
+  }
+
+  public abstract class RemoteAuthService extends android.app.Service {
+    ctor public RemoteAuthService();
+    method protected final android.os.IBinder onBind(android.content.Intent intent, androidx.wear.phone.interactions.authentication.RemoteAuthRequestHandler remoteAuthRequestHandler);
+    method public static final void sendResponseToCallback(androidx.wear.phone.interactions.authentication.OAuthResponse response, kotlin.Pair<java.lang.String,java.lang.Integer> packageNameAndRequestId);
+    method protected boolean verifyPackageName(android.content.Context context, String? requestPackageName);
+    field public static final androidx.wear.phone.interactions.authentication.RemoteAuthService.Companion Companion;
+  }
+
+  public static final class RemoteAuthService.Companion {
+    method public void sendResponseToCallback(androidx.wear.phone.interactions.authentication.OAuthResponse response, kotlin.Pair<java.lang.String,java.lang.Integer> packageNameAndRequestId);
+  }
+
+}
+
 package androidx.wear.phone.interactions.notifications {
 
   public final class BridgingConfig {
diff --git a/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/authentication/IAuthenticationRequestCallback.aidl b/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/authentication/IAuthenticationRequestCallback.aidl
new file mode 100644
index 0000000..c9f49542
--- /dev/null
+++ b/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/authentication/IAuthenticationRequestCallback.aidl
@@ -0,0 +1,37 @@
+package android.support.wearable.authentication;
+
+/**
+ * Interface for defining the callback to be notified when an aync remote authentication request
+ * completes.
+ *
+ * @hide
+ */
+interface IAuthenticationRequestCallback {
+  // IMPORTANT NOTE: All methods must be given an explicit transcation id that must never change
+  // in the future to remain binary backwards compatible
+  // Next Id: 2
+
+  /**
+   * API version number. This should be incremented every time a new method is added.
+   */
+  const int API_VERSION = 1;
+
+  /**
+   * Called when an aync authentication request is completed.
+   *
+   * Bundle contents:
+   * <ul><li>"responseUrl": the response URL from the remote auth request (Uri)
+   * <ul><li>"error": an error code explaining the request result status (int)
+   *
+   * @since API version 0
+   */
+  void onResult(in Bundle result) = 0;
+
+  /**
+   * Return the version number of this interface which the client can use to determine which
+   * methods are available.
+   *
+   * @since API version 1
+   */
+  int getApiVersion() = 1;
+}
diff --git a/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/authentication/IAuthenticationRequestService.aidl b/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/authentication/IAuthenticationRequestService.aidl
new file mode 100644
index 0000000..7e3d3a1
--- /dev/null
+++ b/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/authentication/IAuthenticationRequestService.aidl
@@ -0,0 +1,39 @@
+package android.support.wearable.authentication;
+
+import android.support.wearable.authentication.IAuthenticationRequestCallback;
+
+/**
+ * Interface of a service that supports an async remote authentication.
+ *
+ * @hide
+ */
+interface IAuthenticationRequestService {
+  // IMPORTANT NOTE: all methods must be given an explicit transaction id that must never change
+  // in the future to remain binary backwards compatible.
+  // Next Id: 2
+
+  /**
+   * API version number. This should be incremented every time a new method is added.
+   */
+  const int API_VERSION = 1;
+
+  /**
+   * Open the request Url, and send the respond result back to callback when authentication
+   * completed.
+   *
+   * Bundle contents:
+   * <ul><li>"requestUrl": the URL of the OAuth request (Uri)
+   * <ul><li>"packageName": the package name of the requester (String)
+   *
+   * @since API version 0
+   */
+  void openUrl(
+    in Bundle request,
+    in IAuthenticationRequestCallback authenticationRequestCallback) = 0;
+
+  /**
+   * Return the version number of this interface which the client can use to determine which
+   * methods are available
+   */
+  int getApiVersion() = 1;
+}
diff --git a/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/notifications/IBridgingManagerService.aidl b/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/notifications/IBridgingManagerService.aidl
index d887837..4141777 100644
--- a/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/notifications/IBridgingManagerService.aidl
+++ b/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/notifications/IBridgingManagerService.aidl
@@ -34,7 +34,7 @@
     /**
      * Sets the bridging configuration
      *
-     * @since API version 1
+     * @since API version 0
      */
     void setBridgingConfig(in Bundle bridgingConfig) = 0;
 
diff --git a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/CodeChallenge.kt b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/CodeChallenge.kt
new file mode 100644
index 0000000..3b10dcf
--- /dev/null
+++ b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/CodeChallenge.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.authentication
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import java.nio.charset.StandardCharsets
+import java.security.MessageDigest
+import java.util.Base64
+
+/**
+ * Authorization code challenge
+ *
+ * * Related specifications:
+ *      Proof Key for Code Exchange by OAuth Public Clients (RFC 7636).
+ *      https://tools.ietf.org/html/rfc7636
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+public class CodeChallenge constructor(
+    codeVerifier: CodeVerifier
+) {
+
+    /**
+     * The challenge value.
+     */
+    private var value: String? = null
+
+    /**
+     * Computes the code challenge value using the specified verifier with SHA-256.
+     */
+    init {
+        val md = MessageDigest.getInstance("SHA-256")
+        val hash: ByteArray = md.digest(codeVerifier.getValueBytes())
+        value = Base64.getUrlEncoder().withoutPadding().encodeToString(hash)
+    }
+
+    public fun getValue(): String {
+        return value!!
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (other is CodeChallenge) {
+            return other.getValue() == value
+        }
+        return false
+    }
+
+    override fun hashCode(): Int {
+        return value!!.toByteArray(StandardCharsets.UTF_8).contentHashCode()
+    }
+}
\ No newline at end of file
diff --git a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/CodeVerifier.kt b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/CodeVerifier.kt
new file mode 100644
index 0000000..6dacb90
--- /dev/null
+++ b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/CodeVerifier.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.authentication
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import java.nio.charset.StandardCharsets
+import java.security.SecureRandom
+import java.util.Base64
+
+/**
+ * Authorisation code verifier.
+ *
+ * * Related specifications:
+ *      Proof Key for Code Exchange by OAuth Public Clients (RFC 7636).
+ *      https://tools.ietf.org/html/rfc7636
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+public class CodeVerifier {
+    private companion object {
+        /**
+         * The minimum byte length of a code verifier.
+         */
+        private const val MIN_LENGTH_BYTE = 32
+
+        /**
+         * The maximum character length of a code verifier.
+         */
+        private const val MAX_LENGTH_BYTE = 96
+
+        /**
+         * The minimum character length of a code verifier with base64url-encoded.
+         */
+        private const val MIN_LENGTH_BASE64URL = 43
+
+        /**
+         * The maximum character length of a code verifier with base64url-encoded.
+         */
+        private const val MAX_LENGTH_BASE64URL = 128
+
+        /**
+         * The secure random generator.
+         */
+        private val SECURE_RANDOM: SecureRandom = SecureRandom()
+    }
+
+    /**
+     * The verifier value.
+     */
+    private var value: String? = null
+
+    @JvmOverloads
+    public constructor(
+        /**
+         * It is RECOMMENDED that the output of a suitable random number generator be used to create a
+         * 32-octet sequence. The octet sequence is then base64url-encoded to produce a 43-octet URL
+         * safe string to use as the code verifier.
+         */
+        byteLength: Int = 32
+    ) {
+        /**
+         * Generates a new code verifier with a cryptographic random value of the specified byte
+         * length, Base64URL-encoded
+         */
+        require((byteLength >= MIN_LENGTH_BYTE) and (byteLength <= MAX_LENGTH_BYTE)) {
+            "code verifier for PKCE must has a minimum length of $MIN_LENGTH_BASE64URL characters" +
+                " and a maximum length of $MAX_LENGTH_BASE64URL characters, please generate " +
+                "the code verifier with byte length between $MIN_LENGTH_BYTE and $MAX_LENGTH_BYTE"
+        }
+
+        val randomBytes = ByteArray(byteLength)
+
+        SECURE_RANDOM.nextBytes(randomBytes)
+
+        value = Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes)
+    }
+
+    public constructor(value: String) {
+        this.value = value
+    }
+
+    public fun getValue(): String {
+        return value!!
+    }
+
+    internal fun getValueBytes(): ByteArray {
+        return value!!.toByteArray(StandardCharsets.UTF_8)
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (other is CodeVerifier) {
+            return other.getValue() == value
+        }
+        return false
+    }
+
+    override fun hashCode(): Int {
+        return getValueBytes().contentHashCode()
+    }
+}
\ No newline at end of file
diff --git a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/OAuthRequest.kt b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/OAuthRequest.kt
new file mode 100644
index 0000000..89402f1
--- /dev/null
+++ b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/OAuthRequest.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.authentication
+
+import android.annotation.SuppressLint
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import androidx.annotation.RequiresApi
+
+/**
+ * The OAuth request to be sent to the server to start the OAuth 2 authentication flow
+ */
+public class OAuthRequest internal constructor(
+    private var packageName: String,
+    private val requestUrl: Uri
+) {
+    public companion object {
+        /**
+         * The default google-specific custom URL to route the response from the auth
+         * server back to the 1P companion app, which then forwards it to the 3P app that made
+         * the request on the wear device.
+         *
+         * To deliver an Auth response to your Wear app, set the redirect_uri
+         * parameter on the Auth request, with your app's package name appended.
+         *
+         * For example, if your app's package name is com.package.name, with 1P companion app
+         * paired,  the redirect_uri query will be WEAR_REDIRECT_URL_PREFIX + "com.package.name".
+         */
+        public const val WEAR_REDIRECT_URL_PREFIX: String = "https://wear.googleapis.com/3p_auth/"
+    }
+
+    /**
+     * Builder for constructing new instance of OAuth request.
+     */
+    public class Builder(private val packageName: String) {
+        private var authProviderUrl: Uri? = null
+        private var codeChallenge: CodeChallenge? = null
+        private var clientId: String? = null
+        private var redirectUrl: Uri? = null
+        /**
+         * Set the url of the auth provider site.
+         * It provides the address pointing to the 3p/4p auth site. Appending query parameters in
+         * this uri is optional, it is recommended to let the builder append query parameters
+         * automatically through the use of setters (no setter is required for the builder to
+         * append the redirect_uri).
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public fun setAuthProviderUrl(authProviderUrl: Uri): Builder =
+            this.apply {
+                this.authProviderUrl = authProviderUrl
+            }
+
+        /**
+         * Set the code challenge for authentication with PKCE (proof key for code exchange).
+         * With this setter called, the builder appends the "code_challenge",
+         * "code_challenge_method" and "response_type" queries to the requestUrl.
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public fun setCodeChallenge(codeChallenge: CodeChallenge): Builder =
+            this.apply {
+                this.codeChallenge = codeChallenge
+            }
+
+        /**
+         * Set the client id of this OAuth request.
+         * With this setter called. the builder appends the "client_id" to the requestUrl.
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public fun setClientId(clientId: String): Builder =
+            this.apply {
+                this.clientId = clientId
+            }
+
+        /**
+         * Set the redirect url the companion app registered to, so that the response will be
+         * routed from the auth server back to the companion.
+         *
+         * Calling this method is optional. If the redirect URL is not specified, it will be
+         * automatically set to [WEAR_REDIRECT_URL_PREFIX]
+         *
+         * Note, the app package name should NOT be included here, it will be appended to the end
+         * of redirect_uri automatically in [Builder.build].
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public fun setRedirectUrl(redirectUrl: Uri): Builder =
+            this.apply {
+                this.redirectUrl = redirectUrl
+            }
+
+        @RequiresApi(Build.VERSION_CODES.O)
+        internal fun composeRequestUrl(): Uri {
+            require(authProviderUrl != null) {
+                "The request requires the auth provider url to be provided."
+            }
+            val requestUriBuilder = authProviderUrl!!.buildUpon()
+
+            clientId?.let {
+                appendQueryParameter(requestUriBuilder, "client_id", clientId!!)
+            }
+
+            /**
+             * Set the request url by redirecting the auth provider URL with the WearOS auth
+             * site [WEAR_REDIRECT_URL_PREFIX].
+             * The receiving app's package name is also required as the 3rd path component in the
+             * redirect_uri, this allows Wear to ensure other apps cannot reuse your redirect_uri
+             * to receive responses.
+             */
+            appendQueryParameter(
+                requestUriBuilder,
+                "redirect_uri",
+                Uri.withAppendedPath(
+                    if (redirectUrl == null) Uri.parse(WEAR_REDIRECT_URL_PREFIX) else redirectUrl,
+                    packageName
+                ).toString()
+            )
+
+            codeChallenge?.let {
+                appendQueryParameter(requestUriBuilder, "response_type", "code")
+                appendQueryParameter(
+                    requestUriBuilder,
+                    "code_challenge",
+                    codeChallenge!!.getValue()
+                )
+                appendQueryParameter(requestUriBuilder, "code_challenge_method", "S256")
+            }
+
+            return requestUriBuilder.build()
+        }
+
+        private fun appendQueryParameter(
+            requestUriBuilder: Uri.Builder,
+            queryKey: String,
+            expectedQueryParam: String
+        ) {
+            val currentQueryParam = authProviderUrl!!.getQueryParameter(queryKey)
+            currentQueryParam?.let {
+                require(expectedQueryParam == currentQueryParam) {
+                    "The '$queryKey' query param already exists in the authProviderUrl, " +
+                        "expect to have the value of '$expectedQueryParam', but " +
+                        "'$currentQueryParam' is given. Please correct it,  or leave it out " +
+                        "to allow the request builder to append it automatically."
+                }
+            } ?: run {
+                requestUriBuilder.appendQueryParameter(queryKey, expectedQueryParam)
+            }
+        }
+
+        /** Build the request instance specified by this builder */
+        @RequiresApi(Build.VERSION_CODES.O)
+        public fun build(): OAuthRequest {
+            val requestUrl = composeRequestUrl()
+            checkValidity(requestUrl)
+            return OAuthRequest(packageName, requestUrl)
+        }
+
+        // check the validity of the request for the OAuth2 flow with PKCE
+        private fun checkValidity(requestUrl: Uri) {
+            // check that client_id is provided in the request
+            queryParameterCheck(requestUrl, "client_id", null)
+            // check that code_challenge is provided in the request
+            queryParameterCheck(requestUrl, "code_challenge", null)
+            // check that code_challenge_mode is provided in the request, and with value 'S256'
+            queryParameterCheck(requestUrl, "code_challenge_method", "S256")
+            // check that response_type is provided in the request, and with value 'code'
+            queryParameterCheck(requestUrl, "response_type", "code")
+        }
+
+        private fun queryParameterCheck(
+            requestUrl: Uri,
+            queryKey: String,
+            expectedQueryParameter: String?
+        ) {
+            val queryParam = requestUrl.getQueryParameter(queryKey)
+            require(queryParam != null) {
+                "The use of Proof Key for Code Exchange is required for authentication, " +
+                    "please provide $queryKey in the request."
+            }
+            expectedQueryParameter?.let {
+                require(queryParam == expectedQueryParameter) {
+                    "The use of Proof Key for Code Exchange is required for authentication, " +
+                        "the query parameter '$queryKey' is expected to have value of " +
+                        "'$expectedQueryParameter', but '$queryParam' is set"
+                }
+            }
+        }
+    }
+
+    /** Get the package name of the app that send the auth request */
+    public fun getPackageName(): String = packageName
+
+    /**
+     * Get the Url of the auth request.
+     * The request is expected to craft a URL something like:
+     *     https://authorization-server.com/auth?client_id=XXXXX
+     *     &redirect_uri=https://wear.googleapis.com/3p_auth/mypackagename
+     *     &response_type=code
+     *     &code_challenge=XXXXX...XXX
+     *     &code_challenge_method=S256
+     */
+    public fun getRequestUrl(): Uri = requestUrl
+
+    internal fun toBundle(): Bundle = Bundle().apply {
+        putParcelable(RemoteAuthClient.KEY_REQUEST_URL, requestUrl)
+        putString(RemoteAuthClient.KEY_PACKAGE_NAME, packageName)
+    }
+}
diff --git a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/OAuthResponse.kt b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/OAuthResponse.kt
new file mode 100644
index 0000000..7f0e5b4
--- /dev/null
+++ b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/OAuthResponse.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.authentication
+
+import android.net.Uri
+
+/**
+ * The authentication response to be sent back to the client after completing the OAuth2 flow.
+ */
+public class OAuthResponse internal constructor(
+    @RemoteAuthClient.Companion.ErrorCode private val errorCode: Int = RemoteAuthClient.NO_ERROR,
+    private val responseUrl: Uri?
+) {
+    /**
+     * Builder for constructing new instance of authentication response.
+     */
+    public class Builder {
+        @RemoteAuthClient.Companion.ErrorCode
+        private var errorCode: Int = RemoteAuthClient.NO_ERROR
+        private var responseUrl: Uri? = null
+
+        /** Set the error code to indicate the request result status */
+        public fun setErrorCode(@RemoteAuthClient.Companion.ErrorCode errorCode: Int): Builder =
+            this.apply {
+                this.errorCode = errorCode
+            }
+
+        /** Set the Url of the auth response */
+        public fun setResponseUrl(responseUrl: Uri): Builder = this.apply {
+            this.responseUrl = responseUrl
+        }
+
+        /** Build the response instance specified by this builder*/
+        public fun build(): OAuthResponse = OAuthResponse(errorCode, responseUrl)
+    }
+
+    /** get the error code that indicated the request result status */
+    @RemoteAuthClient.Companion.ErrorCode
+    public fun getErrorCode(): Int = errorCode
+
+    /** get the Url of the auth response */
+    public fun getResponseUrl(): Uri? = responseUrl
+}
\ No newline at end of file
diff --git a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/RemoteAuthClient.kt b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/RemoteAuthClient.kt
new file mode 100644
index 0000000..e1c5d72
--- /dev/null
+++ b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/RemoteAuthClient.kt
@@ -0,0 +1,374 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.authentication
+
+import android.annotation.SuppressLint
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.net.Uri
+import android.os.Bundle
+import android.os.Handler
+import android.os.IBinder
+import android.support.wearable.authentication.IAuthenticationRequestCallback
+import android.support.wearable.authentication.IAuthenticationRequestService
+import androidx.annotation.IntDef
+import androidx.annotation.UiThread
+import java.util.ArrayDeque
+import java.util.Queue
+import java.util.concurrent.Executor
+
+/**
+ * Provides a client for supporting remote authentication on Wear. The authentication session
+ * will be opened on the user's paired phone.
+ *
+ * * The following example triggers an authorization session to open on the phone.
+ * ```
+ * // PKCE (Proof Key for Code Exchange) is required for the auth
+ * private var codeVerifier: CodeVerifier
+ * private var authClient: RemoteAuthClient
+ *
+ * override public fun onCreate(b: Bundle) {
+ *   super.onCreate(b);
+ *   authClient = RemoteAuthClient.create(this);
+ *   ...
+ * }
+ *
+ * override public fun onDestroy() {
+ *   authClient.close();
+ *   super.onDestroy();
+ * }
+ *
+ * public fun startAuthFlow() {
+ *    // PKCE (Proof Key for Code Exchange) is required, store this code verifier here .
+ *    // To access the resource later, both the auth token ans code verifier are needed.
+ *    codeVerifier = CodeVerifier()
+ *
+ *   // Construct your auth request.
+ *   authClient.sendAuthorizationRequest(
+ *      OAuthRequest.Builder(this.applicationContext.packageName)
+ *          .setAuthProviderUrl(Uri.parse("https://...."))
+ *          .setCodeChallenge(CodeChallenge(codeVerifier))
+ *          .build(),
+ *      new MyAuthCallback()
+ *   );
+ * }
+ *
+ * private  class MyAuthCallback: RemoteAuthClient.Callback {
+ *   override public fun onAuthorizationResponse(
+ *      request: OAuthRequest,
+ *      response: OAuthResponse
+ *    ) {
+ *     // Parse the result token out of the response and store it, e.g. in SharedPreferences,
+ *     // so you can use it later (Note, use together with code verifier from version R)
+ *     // You'll also want to display a success UI.
+ *     ...
+ *   }
+ *
+ *   override public fun onAuthorizationError(errorCode: int) {
+ *     // Compare against codes available in RemoteAuthClient.ErrorCode
+ *     // You'll also want to display an error UI.
+ *     ...
+ *   }
+ * }
+ * ```
+ */
+public class RemoteAuthClient internal constructor(
+    private val serviceBinder: ServiceBinder,
+    private val uiThreadExecutor: Executor,
+    private val packageName: String
+) : AutoCloseable {
+    public companion object {
+        /**
+         * The URL to be opened in a web browser on the companion.
+         * Value type: Uri
+         */
+        internal const val KEY_REQUEST_URL: String = "requestUrl"
+
+        /**
+         * The package name obtained from calling getPackageName() on the context passed into
+         * [.create].
+         * Value type: String
+         */
+        internal const val KEY_PACKAGE_NAME: String = "packageName"
+
+        /**
+         * The URL that the web browser is directed to that triggered the companion to open.
+         * Value type: Uri
+         */
+        internal const val KEY_RESPONSE_URL: String = "responseUrl"
+
+        /**
+         * The error code explaining why the request failed.
+         * Value type: [.ErrorCode]
+         */
+        internal const val KEY_ERROR_CODE: String = "errorCode"
+
+        /**
+         * Package name for the service provider on Wearable.
+         * Home app for Wear 2, and Wear Core Service for wear 3
+         */
+        internal const val WEARABLE_PACKAGE_NAME: String = "com.google.android.wearable.app"
+
+        /**
+         * Triggering a service that will prompt a user for authorization credential on the phone
+         * For backwards compatibility, leave this action name as "OAUTH", so 3p app using this new
+         * androidx class can still send request to the service in clockwork home with WSL.
+         */
+        internal const val ACTION_AUTH: String =
+            "android.support.wearable.authentication.action.OAUTH"
+
+        /** Indicates 3p authentication is finished without error  */
+        public const val NO_ERROR: Int = 0
+
+        /** Indicates 3p authentication isn't supported by Wear OS  */
+        public const val ERROR_UNSUPPORTED: Int = 1
+
+        /** Indicates no phone is connected, or the phone connected doesn't support 3p auth */
+        public const val ERROR_PHONE_UNAVAILABLE: Int = 2
+
+        /** Errors returned in [.Callback.onAuthorizationError].  */
+        @Retention(AnnotationRetention.SOURCE)
+        @IntDef(NO_ERROR, ERROR_UNSUPPORTED, ERROR_PHONE_UNAVAILABLE)
+        internal annotation class ErrorCode
+
+        /** service connection status */
+        private const val STATE_DISCONNECTED: Int = 0
+        private const val STATE_CONNECTING: Int = 1
+        private const val STATE_CONNECTED: Int = 2
+
+        /** Return a client that can be used to make async remote authorization requests */
+        @JvmStatic
+        public fun create(context: Context): RemoteAuthClient {
+            val appContext: Context = context.applicationContext
+            return RemoteAuthClient(
+                object : ServiceBinder {
+                    override fun bindService(
+                        intent: Intent?,
+                        connection: ServiceConnection?,
+                        flags: Int
+                    ): Boolean {
+                        return appContext.bindService(intent, connection!!, flags)
+                    }
+
+                    override fun unbindService(connection: ServiceConnection?) {
+                        appContext.unbindService(connection!!)
+                    }
+                },
+                { command -> Handler(appContext.mainLooper).post(command) },
+                context.packageName
+            )
+        }
+    }
+
+    private var allocationSite: Throwable? =
+        Throwable("Explicit termination method 'close' not called")
+    private var connectionState: Int = STATE_DISCONNECTED
+    private var service: IAuthenticationRequestService? = null
+    private val outstandingRequests: MutableSet<RequestCallback> = HashSet()
+    private val queuedRunnables: Queue<Runnable> = ArrayDeque()
+    private val connection: ServiceConnection = RemoteAuthConnection()
+
+    /**
+     * This callback is notified when an async remote authentication request completes.
+     *
+     * Typically, your app should update its UI to let the user aware of the success or failure.
+     */
+    public abstract class Callback {
+
+        /**
+         * Called when an async remote authentication request completes successfully.
+         *
+         * see [.sendAuthorizationRequest]
+         */
+        @UiThread
+        public abstract fun onAuthorizationResponse(request: OAuthRequest, response: OAuthResponse)
+
+        /**
+         * Called when an async remote authentication request fails.
+         *
+         * see [.sendAuthorizationRequest]
+         */
+        @UiThread
+        public abstract fun onAuthorizationError(@ErrorCode errorCode: Int)
+    }
+
+    /**
+     * Send a remote auth request. This will cause an authorization UI to be presented on
+     * the user's phone.
+     * This request is asynchronous; the callback provided will be be notified when the request
+     * completes.
+     *
+     * @param request Request that will be sent to the phone. The auth response should redirect
+     * to the Wear OS companion. See [.WEAR_REDIRECT_URL_PREFIX]
+     *
+     * @Throws RuntimeException if the service has error to open the request
+     */
+    @UiThread
+    @SuppressLint("ExecutorRegistration")
+    public fun sendAuthorizationRequest(request: OAuthRequest, clientCallback: Callback) {
+        require(packageName == request.getPackageName()) {
+            "The request's package name is different from the auth client's package name."
+        }
+
+        if (connectionState == STATE_DISCONNECTED) {
+            connect()
+        }
+        whenConnected(
+            Runnable {
+                val callback = RequestCallback(request, clientCallback)
+                outstandingRequests.add(callback)
+                try {
+                    service!!.openUrl(request.toBundle(), callback)
+                } catch (e: Exception) {
+                    removePendingCallback(callback)
+                    throw RuntimeException(e)
+                }
+            }
+        )
+    }
+
+    /**
+     * Check that the explicit termination method 'close' is called
+     *
+     *  @Throws RuntimeException if the 'close' method was not called
+     */
+    protected fun finalize() {
+        if (allocationSite != null) {
+            throw RuntimeException(
+                "A RemoteAuthClient was acquired at the attached stack trace but never released" +
+                    " Call RemoteAuthClient.close()"
+            )
+        }
+    }
+
+    /**
+     * Frees any resources used by the client, dropping any outstanding requests. The client
+     * cannot be used to make requests thereafter.
+     */
+    @UiThread
+    override fun close() {
+        allocationSite = null
+        queuedRunnables.clear()
+        outstandingRequests.clear()
+        disconnect()
+    }
+
+    internal interface ServiceBinder {
+        /** See [Context.bindService].  */
+        fun bindService(intent: Intent?, connection: ServiceConnection?, flags: Int): Boolean
+
+        /** See [Context.unbindService].  */
+        fun unbindService(connection: ServiceConnection?)
+    }
+
+    /**
+     * Runs the given runnable immediately if already connected, or queues it for later if a
+     * connection has not yet been fully established.
+     */
+    private fun whenConnected(runnable: Runnable) {
+        if (connectionState == STATE_CONNECTED) {
+            runnable.run()
+        } else {
+            queuedRunnables.add(runnable)
+        }
+    }
+
+    private fun removePendingCallback(requestCallback: RequestCallback) {
+        outstandingRequests.remove(requestCallback)
+        if (outstandingRequests.isEmpty() && service != null) {
+            disconnect()
+        }
+    }
+
+    private fun connect() {
+        check(connectionState == STATE_DISCONNECTED) { "State is $connectionState" }
+        val intent =
+            Intent(ACTION_AUTH).setPackage(WEARABLE_PACKAGE_NAME)
+        val success: Boolean =
+            serviceBinder.bindService(intent, connection, Context.BIND_AUTO_CREATE)
+        if (success) {
+            connectionState = STATE_CONNECTING
+        } else {
+            throw RuntimeException("Failed to bind to Auth service")
+        }
+    }
+
+    private fun disconnect() {
+        if (connectionState != STATE_DISCONNECTED) {
+            serviceBinder.unbindService(connection)
+            service = null
+            connectionState = STATE_DISCONNECTED
+        }
+    }
+
+    /** Receives results of async requests to the remote auth service.  */
+    internal inner class RequestCallback internal constructor(
+        private val request: OAuthRequest,
+        private val clientCallback: Callback
+    ) : IAuthenticationRequestCallback.Stub() {
+
+        override fun getApiVersion(): Int = IAuthenticationRequestCallback.API_VERSION
+
+        /**
+         * Called when an aync remote authentication request is completed.
+         *
+         * Bundle contents:
+         * <ul><li>"responseUrl": the response URL from the Auth request (Uri)
+         * <ul><li>"error": an error code explaining why the request failed (int)
+         */
+        override fun onResult(result: Bundle) {
+            val errorCode = result.getInt(KEY_ERROR_CODE, NO_ERROR)
+            val responseUrl: Uri? = result.getParcelable(KEY_RESPONSE_URL)
+            onResult(OAuthResponse(errorCode, responseUrl))
+        }
+
+        @SuppressLint("SyntheticAccessor")
+        internal fun onResult(response: OAuthResponse) {
+            @ErrorCode val error = response.getErrorCode()
+            uiThreadExecutor.execute(
+                Runnable {
+                    removePendingCallback(this@RequestCallback)
+                    if (error == NO_ERROR) {
+                        clientCallback.onAuthorizationResponse(request, response)
+                    } else {
+                        clientCallback.onAuthorizationError(response.getErrorCode())
+                    }
+                }
+            )
+        }
+    }
+
+    /** Manages the connection with Wearable Auth service.  */
+    private inner class RemoteAuthConnection : ServiceConnection {
+        @UiThread
+        override fun onServiceConnected(name: ComponentName, boundService: IBinder) {
+            service = IAuthenticationRequestService.Stub.asInterface(boundService)
+            connectionState = STATE_CONNECTED
+            // Run all queued runnables
+            while (!queuedRunnables.isEmpty()) {
+                queuedRunnables.poll()!!.run()
+            }
+        }
+
+        @UiThread
+        override fun onServiceDisconnected(name: ComponentName) {
+            service = null
+        }
+    }
+}
\ No newline at end of file
diff --git a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/RemoteAuthService.kt b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/RemoteAuthService.kt
new file mode 100644
index 0000000..747bb43
--- /dev/null
+++ b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/RemoteAuthService.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.authentication
+
+import android.annotation.SuppressLint
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Binder
+import android.os.Bundle
+import android.os.IBinder
+import android.os.RemoteException
+import android.support.wearable.authentication.IAuthenticationRequestCallback
+import android.support.wearable.authentication.IAuthenticationRequestService
+import java.security.SecureRandom
+
+/**
+ * Interface for specifying how the service handles the remote auth requests.
+ */
+public interface RemoteAuthRequestHandler {
+
+    /**
+     * Whether the auth service is enabled, return false would give an early out by sending the
+     * 3p app a response with error code of ERROR_UNSUPPORTED
+     */
+    public fun isAuthSupported(): Boolean
+
+    /**
+     * Handle the auth request by sending it to the phone.
+     * Typically, if the paired phone is not connected, send a response with error code of
+     * ERROR_PHONE_UNAVAILABLE; otherwise listening for the response from the phone and send it
+     * back to the 3p app.
+     *
+     * [RemoteAuthService.sendResponseToCallback] is provided for sending response back to the
+     * callback provided by the 3p app.
+     *
+     */
+    public fun sendAuthRequest(
+        request: OAuthRequest,
+        packageNameAndRequestId: Pair<String, Int>
+    )
+}
+
+/*
+ * Extend this service class to trigger the handling of the remote auth requests, the
+ * RemoteAuthRequestHandler is specified when the service is bound, typically:
+ *
+ *  class AuthenticationService : RemoteAuthService {
+ *      override fun onBind(intent: Intent): IBinder {
+ *          return onBind(
+ *              intent,
+ *              object : RemoteAuthRequestHandler {
+ *                  override fun isAuthSupported(): Boolean {...}
+ *                  override fun sendAuthRequest(...) {
+ *                    ...
+ *                    sendResponseToCallback(...)
+ *                  }
+ *          })
+ *      }
+ *  }
+ */
+public abstract class RemoteAuthService : Service() {
+
+    public companion object {
+        @JvmStatic
+        private val callbacksByPackageNameAndRequestID:
+            MutableMap<Pair<String, Int>, RemoteAuthClient.RequestCallback> = HashMap()
+
+        /**
+         * To be called by the child class to invoke the callback with Response
+         */
+        @SuppressLint("DocumentExceptions")
+        @JvmStatic
+        public fun sendResponseToCallback(
+            response: OAuthResponse,
+            packageNameAndRequestId: Pair<String, Int>
+        ) {
+            try {
+                callbacksByPackageNameAndRequestID[packageNameAndRequestId]?.onResult(response)
+                callbacksByPackageNameAndRequestID.remove(packageNameAndRequestId)
+            } catch (e: RemoteException) {
+                throw e.cause!!
+            }
+        }
+
+        internal fun getCallback(packageNameAndRequestId: Pair<String, Int>):
+            RemoteAuthClient.RequestCallback? =
+                callbacksByPackageNameAndRequestID[packageNameAndRequestId]
+    }
+
+    private val secureRandom: SecureRandom = SecureRandom()
+
+    /**
+     * To be called by child class when implementing the [Service.onBind], provide the
+     * RemoteAuthRequestHandler and return the IBinder.
+     */
+    protected fun onBind(
+        @Suppress("UNUSED_PARAMETER") intent: Intent,
+        remoteAuthRequestHandler: RemoteAuthRequestHandler
+    ): IBinder = RemoteAuthServiceBinder(this, remoteAuthRequestHandler)
+
+    /**
+     * Implementation of [Service.onUnbind]
+     */
+    public override fun onUnbind(intent: Intent): Boolean {
+        callbacksByPackageNameAndRequestID.clear()
+        return super.onUnbind(intent)
+    }
+
+    /**
+     * Allow the child class to override the default behavior of the package name verification.
+     *
+     * By default, we check the request's package name belongs to the requester's UID.
+     */
+    protected open fun verifyPackageName(context: Context, requestPackageName: String?): Boolean {
+        val packagesForUID: Array<String>? =
+            context.packageManager.getPackagesForUid(Binder.getCallingUid())
+        return !(
+            requestPackageName.isNullOrEmpty() ||
+                packagesForUID.isNullOrEmpty() ||
+                !(packagesForUID.contains(requestPackageName))
+            )
+    }
+
+    internal inner class RemoteAuthServiceBinder(
+        private val context: Context,
+        private val remoteAuthRequestHandler: RemoteAuthRequestHandler
+    ) : IAuthenticationRequestService.Stub() {
+
+        override fun getApiVersion(): Int = IAuthenticationRequestService.API_VERSION
+
+        /**
+         * @throws SecurityException
+         */
+        override fun openUrl(
+            request: Bundle,
+            authenticationRequestCallback: IAuthenticationRequestCallback
+        ) {
+            if (remoteAuthRequestHandler.isAuthSupported()) {
+                val packageName = request.getString(RemoteAuthClient.KEY_PACKAGE_NAME)
+                if (!verifyPackageName(context, packageName)) {
+                    throw SecurityException("Failed to verify the Requester's package name")
+                }
+
+                val packageNameAndRequestId = Pair(packageName!!, secureRandom.nextInt())
+                callbacksByPackageNameAndRequestID[packageNameAndRequestId] =
+                    authenticationRequestCallback as RemoteAuthClient.RequestCallback
+
+                val requestUrl: Uri? = request.getParcelable(RemoteAuthClient.KEY_REQUEST_URL)
+                remoteAuthRequestHandler.sendAuthRequest(
+                    OAuthRequest(packageName, requestUrl!!),
+                    packageNameAndRequestId
+                )
+            } else {
+                (authenticationRequestCallback as RemoteAuthClient.RequestCallback).onResult(
+                    OAuthResponse.Builder()
+                        .setErrorCode(RemoteAuthClient.ERROR_UNSUPPORTED).build()
+                )
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/WearPhoneInteractionsTestRunner.kt b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/WearPhoneInteractionsTestRunner.kt
index 3ade521..fdff318 100644
--- a/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/WearPhoneInteractionsTestRunner.kt
+++ b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/WearPhoneInteractionsTestRunner.kt
@@ -29,11 +29,13 @@
  * inline classes. We don't need shadowing of our classes because we want to use the actual
  * objects in our tests.
  */
-class WearPhoneInteractionsTestRunner(testClass: Class<*>) : RobolectricTestRunner(testClass) {
+internal class WearPhoneInteractionsTestRunner(
+    testClass: Class<*>
+) : RobolectricTestRunner(testClass) {
     override fun createClassLoaderConfig(method: FrameworkMethod): InstrumentationConfiguration =
         InstrumentationConfiguration.Builder(
             super.createClassLoaderConfig(method)
         )
             .doNotInstrumentPackage("androidx.wear.phone.interactions")
             .build()
-}
\ No newline at end of file
+}
diff --git a/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/CodeVerifierCodeChallengeTest.kt b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/CodeVerifierCodeChallengeTest.kt
new file mode 100644
index 0000000..7fb442d
--- /dev/null
+++ b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/CodeVerifierCodeChallengeTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.authentication
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Test
+
+/** Unit tests for [CodeVerifier] and [CodeChallenge] */
+public class CodeVerifierCodeChallengeTest {
+    @Test
+    public fun testVerifierDefaultConstructor() {
+        val verifier = CodeVerifier()
+        assertEquals(43, verifier.getValue().length)
+    }
+
+    @Test
+    public fun testVerifierConstructor() {
+        val verifier = CodeVerifier(96)
+        assertEquals(128, verifier.getValue().length)
+    }
+
+    @Test
+    public fun testVerifierConstructorInvalidParam() {
+        try {
+            CodeVerifier(100)
+            fail("should fail due to verifier over the required length")
+        } catch (e: Exception) {
+            // Expected
+        }
+    }
+
+    @Test
+    public fun testVerifierEquality() {
+        val verifier = CodeVerifier()
+        assertTrue(verifier.equals(CodeVerifier(verifier.getValue())))
+    }
+
+    @Test
+    public fun testVerifierInequality() {
+        assertFalse(CodeVerifier().equals(CodeVerifier()))
+        assertFalse(CodeVerifier(50).equals(CodeVerifier(50)))
+        assertFalse(CodeVerifier().equals(null))
+    }
+
+    @Test
+    public fun testChallengeTransformS256() {
+        // see https://tools.ietf.org/html/rfc7636#appendix-A
+        val verifier = CodeVerifier("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk")
+        val challenge = CodeChallenge(verifier)
+        assertEquals("E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", challenge.getValue())
+    }
+
+    @Test
+    public fun testChallengeEquality() {
+        val verifierValue = "jdshfkshg-8973834_SDFSSGE"
+        assertTrue(
+            CodeChallenge(CodeVerifier(verifierValue)).equals(
+                CodeChallenge(CodeVerifier(verifierValue))
+            )
+        )
+    }
+
+    @Test
+    public fun testChallengeInequality() {
+        assertFalse(CodeChallenge(CodeVerifier()).equals(CodeChallenge(CodeVerifier())))
+        assertFalse(CodeChallenge(CodeVerifier()).equals(null))
+    }
+}
\ No newline at end of file
diff --git a/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/OAuthRequestResponseTest.kt b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/OAuthRequestResponseTest.kt
new file mode 100644
index 0000000..cbbaa46
--- /dev/null
+++ b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/OAuthRequestResponseTest.kt
@@ -0,0 +1,363 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.authentication
+
+import android.net.Uri
+import androidx.wear.phone.interactions.WearPhoneInteractionsTestRunner
+import org.junit.Assert.assertEquals
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.internal.DoNotInstrument
+
+/** Unit tests for [OAuthRequest] and [OAuthResponse] */
+@RunWith(WearPhoneInteractionsTestRunner::class)
+@DoNotInstrument
+public class OAuthRequestResponseTest {
+    internal companion object {
+        private const val authProviderUrl = "http://account.myapp.com/auth"
+        private const val clientId = "iamtheclient"
+        private const val appPackageName = "com.friendlyapp"
+        private const val redirectUrl = OAuthRequest.WEAR_REDIRECT_URL_PREFIX
+        private const val redirectUrlWithPackageName = "$redirectUrl$appPackageName"
+        private const val customRedirectUrl = "https://app.example.com/oauth2redirect"
+        private const val customRedirectUrlWithPackageName = "$customRedirectUrl/$appPackageName"
+        private val responseUrl = Uri.parse("http://myresponseurl")
+    }
+
+    private fun checkBuildSuccess(
+        builder: OAuthRequest.Builder,
+        expectedAuthProviderUrl: String,
+        expectedClientId: String,
+        expectedRedirectUri: String,
+        expectedCodeChallenge: String,
+    ) {
+        var request: OAuthRequest? = null
+        try {
+            request = builder.build()
+        } catch (e: Exception) {
+            fail("The build shall succeed and this line will not be executed")
+        }
+
+        val requestUrl = request!!.getRequestUrl()
+        assertEquals(requestUrl.toString().indexOf(expectedAuthProviderUrl), 0)
+        assertEquals(requestUrl.getQueryParameter("redirect_uri"), expectedRedirectUri)
+        assertEquals(requestUrl.getQueryParameter("client_id"), expectedClientId)
+        assertEquals(requestUrl.getQueryParameter("response_type"), "code")
+        assertEquals(requestUrl.getQueryParameter("code_challenge"), expectedCodeChallenge)
+        assertEquals(requestUrl.getQueryParameter("code_challenge_method"), "S256")
+    }
+
+    private fun checkBuildFailure(builder: OAuthRequest.Builder, errorMsg: String) {
+        try {
+            builder.build()
+            fail("should fail without providing correct/adequate info for building request")
+        } catch (e: Exception) {
+            assertEquals(errorMsg, e.message)
+        }
+    }
+
+    @Test
+    public fun testRequestBuildScuccessWithSetters() {
+        val codeChallenge = CodeChallenge(CodeVerifier())
+        val requestBuilder = OAuthRequest.Builder(appPackageName)
+            .setAuthProviderUrl(authProviderUrl = Uri.parse(authProviderUrl))
+            .setClientId(clientId)
+            .setCodeChallenge(codeChallenge)
+
+        checkBuildSuccess(
+            requestBuilder,
+            authProviderUrl,
+            clientId,
+            redirectUrlWithPackageName,
+            codeChallenge.getValue()
+        )
+    }
+
+    @Test
+    public fun testRequestBuildScuccessWithCustomRedirectUti() {
+        val codeChallenge = CodeChallenge(CodeVerifier())
+        val requestBuilder = OAuthRequest.Builder(appPackageName)
+            .setAuthProviderUrl(authProviderUrl = Uri.parse(authProviderUrl))
+            .setClientId(clientId)
+            .setRedirectUrl(Uri.parse(customRedirectUrl))
+            .setCodeChallenge(codeChallenge)
+
+        checkBuildSuccess(
+            requestBuilder,
+            authProviderUrl,
+            clientId,
+            customRedirectUrlWithPackageName,
+            codeChallenge.getValue()
+        )
+    }
+
+    @Test
+    public fun testRequestBuildSuccessWithCompleteUrl() {
+        val codeChallenge = CodeChallenge(CodeVerifier())
+        val requestBuilder = OAuthRequest.Builder(appPackageName)
+            .setAuthProviderUrl(
+                authProviderUrl = Uri.parse(
+                    "$authProviderUrl?client_id=$clientId" +
+                        "&redirect_uri=$redirectUrlWithPackageName" +
+                        "&response_type=code" +
+                        "&code_challenge=${codeChallenge.getValue()}" +
+                        "&code_challenge_method=S256"
+                )
+            )
+
+        checkBuildSuccess(
+            requestBuilder,
+            authProviderUrl,
+            clientId,
+            redirectUrlWithPackageName,
+            codeChallenge.getValue()
+        )
+    }
+
+    @Test
+    public fun testRequestBuildFailureWithoutAuthProviderUrl() {
+        val builder = OAuthRequest.Builder(appPackageName)
+            .setClientId(clientId)
+            .setCodeChallenge(CodeChallenge(CodeVerifier()))
+
+        checkBuildFailure(
+            builder,
+            "The request requires the auth provider url to be provided."
+        )
+    }
+
+    @Test
+    public fun testRequestBuildFailureWithoutClientId() {
+        val builder = OAuthRequest.Builder(appPackageName)
+            .setAuthProviderUrl(authProviderUrl = Uri.parse(authProviderUrl))
+            .setCodeChallenge(CodeChallenge(CodeVerifier()))
+
+        checkBuildFailure(
+            builder,
+            "The use of Proof Key for Code Exchange is required for authentication, " +
+                "please provide client_id in the request."
+        )
+    }
+
+    @Test
+    public fun testRequestBuildFailureWithConflictedClientId() {
+        val builder = OAuthRequest.Builder(appPackageName)
+            .setAuthProviderUrl(
+                Uri.parse("$authProviderUrl?client_id=XXX")
+            )
+            .setClientId(clientId)
+            .setCodeChallenge(CodeChallenge(CodeVerifier()))
+
+        checkBuildFailure(
+            builder,
+            "The 'client_id' query param already exists in the authProviderUrl, " +
+                "expect to have the value of '$clientId', but " +
+                "'XXX' is given. Please correct it,  or leave it out " +
+                "to allow the request builder to append it automatically."
+        )
+    }
+
+    @Test
+    public fun testRequestBuildSuccessWithDuplicatedClientId() {
+        val codeChallenge = CodeChallenge(CodeVerifier())
+        val builder = OAuthRequest.Builder(appPackageName)
+            .setAuthProviderUrl(
+                Uri.parse("$authProviderUrl?client_id=$clientId")
+            )
+            .setClientId(clientId)
+            .setCodeChallenge(codeChallenge)
+
+        checkBuildSuccess(
+            builder,
+            authProviderUrl,
+            clientId,
+            redirectUrlWithPackageName,
+            codeChallenge.getValue()
+        )
+    }
+
+    @Test
+    public fun testRequestBuildFailureWithoutCodeChallenge() {
+        val builder = OAuthRequest.Builder(appPackageName)
+            .setAuthProviderUrl(authProviderUrl = Uri.parse(authProviderUrl))
+            .setClientId(clientId)
+
+        checkBuildFailure(
+            builder,
+            "The use of Proof Key for Code Exchange is required for authentication, " +
+                "please provide code_challenge in the request."
+        )
+    }
+
+    @Test
+    public fun testRequestBuildFailureWithConflictedCodeChallenge() {
+        val codeChallenge = CodeChallenge(CodeVerifier())
+        val builder = OAuthRequest.Builder(appPackageName)
+            .setAuthProviderUrl(
+                Uri.parse("$authProviderUrl?code_challenge=XXX")
+            )
+            .setClientId(clientId)
+            .setCodeChallenge(codeChallenge)
+
+        checkBuildFailure(
+            builder,
+            "The 'code_challenge' query param already exists in the authProviderUrl, " +
+                "expect to have the value of '${codeChallenge.getValue()}', but " +
+                "'XXX' is given. Please correct it,  or leave it out " +
+                "to allow the request builder to append it automatically."
+        )
+    }
+
+    @Test
+    public fun testRequestBuildSuccessWithDuplicatedCodeChallenge() {
+        val codeChallenge = CodeChallenge(CodeVerifier())
+        val builder = OAuthRequest.Builder(appPackageName)
+            .setAuthProviderUrl(
+                Uri.parse("$authProviderUrl?code_challenge=${codeChallenge.getValue()}")
+            )
+            .setClientId(clientId)
+            .setCodeChallenge(codeChallenge)
+
+        checkBuildSuccess(
+            builder,
+            authProviderUrl,
+            clientId,
+            redirectUrlWithPackageName,
+            codeChallenge.getValue()
+        )
+    }
+
+    @Test
+    public fun testRequestBuildFailureWithWrongResponseType() {
+        val builder = OAuthRequest.Builder(appPackageName)
+            .setAuthProviderUrl(
+                Uri.parse("$authProviderUrl?response_type=XXX")
+            )
+            .setClientId(clientId)
+            .setCodeChallenge(CodeChallenge(CodeVerifier()))
+
+        checkBuildFailure(
+            builder,
+            "The 'response_type' query param already exists in the authProviderUrl, " +
+                "expect to have the value of 'code', but " +
+                "'XXX' is given. Please correct it,  or leave it out " +
+                "to allow the request builder to append it automatically."
+        )
+    }
+
+    @Test
+    public fun testRequestBuildFailureWithDuplicatedResponseType() {
+        val codeChallenge = CodeChallenge(CodeVerifier())
+        val builder = OAuthRequest.Builder(appPackageName)
+            .setAuthProviderUrl(
+                Uri.parse("$authProviderUrl?response_type=code")
+            )
+            .setClientId(clientId)
+            .setCodeChallenge(codeChallenge)
+
+        checkBuildSuccess(
+            builder,
+            authProviderUrl,
+            clientId,
+            redirectUrlWithPackageName,
+            codeChallenge.getValue()
+        )
+    }
+
+    @Test
+    public fun testRequestBuildFailureWithWrongCodeChallengeMethod() {
+        val builder = OAuthRequest.Builder(appPackageName)
+            .setAuthProviderUrl(
+                Uri.parse("$authProviderUrl?code_challenge_method=PLAIN")
+            )
+            .setClientId(clientId)
+            .setCodeChallenge(CodeChallenge(CodeVerifier()))
+
+        checkBuildFailure(
+            builder,
+            "The 'code_challenge_method' query param already exists in the authProviderUrl, " +
+                "expect to have the value of 'S256', but " +
+                "'PLAIN' is given. Please correct it,  or leave it out " +
+                "to allow the request builder to append it automatically."
+        )
+    }
+
+    @Test
+    public fun testRequestBuildFailureWithDuplicatedCodeChallengeMethod() {
+        val codeChallenge = CodeChallenge(CodeVerifier())
+        val builder = OAuthRequest.Builder(appPackageName)
+            .setAuthProviderUrl(
+                Uri.parse("$authProviderUrl?code_challenge_method=S256")
+            )
+            .setClientId(clientId)
+            .setCodeChallenge(codeChallenge)
+
+        checkBuildSuccess(
+            builder,
+            authProviderUrl,
+            clientId,
+            redirectUrlWithPackageName,
+            codeChallenge.getValue()
+        )
+    }
+
+    @Test
+    public fun testRequestBuildFailureWithConflictedRedirectUri() {
+        val codeChallenge = CodeChallenge(CodeVerifier())
+        val builder = OAuthRequest.Builder(appPackageName)
+            .setAuthProviderUrl(
+                Uri.parse("$authProviderUrl?redirect_uri=$redirectUrlWithPackageName")
+            )
+            .setRedirectUrl(Uri.parse(customRedirectUrl))
+            .setClientId(clientId)
+            .setCodeChallenge(codeChallenge)
+
+        checkBuildFailure(
+            builder,
+            "The 'redirect_uri' query param already exists in the authProviderUrl, " +
+                "expect to have the value of '$customRedirectUrlWithPackageName', but " +
+                "'$redirectUrlWithPackageName' is given. Please correct it,  or leave it out " +
+                "to allow the request builder to append it automatically."
+        )
+    }
+
+    @Test
+    public fun testNoErrorResponseBuild() {
+        val response = OAuthResponse.Builder().setResponseUrl(responseUrl).build()
+
+        assertEquals(RemoteAuthClient.NO_ERROR, response.getErrorCode())
+        assertEquals(responseUrl, response.getResponseUrl())
+    }
+
+    @Test
+    public fun testErrorResponseBuild() {
+        val response1 = OAuthResponse.Builder()
+            .setErrorCode(RemoteAuthClient.ERROR_UNSUPPORTED)
+            .build()
+
+        assertEquals(RemoteAuthClient.ERROR_UNSUPPORTED, response1.getErrorCode())
+        assertEquals(null, response1.getResponseUrl())
+
+        val response2 = OAuthResponse.Builder()
+            .setErrorCode(RemoteAuthClient.ERROR_PHONE_UNAVAILABLE)
+            .build()
+
+        assertEquals(RemoteAuthClient.ERROR_PHONE_UNAVAILABLE, response2.getErrorCode())
+        assertEquals(null, response2.getResponseUrl())
+    }
+}
\ No newline at end of file
diff --git a/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/RemoteAuthTest.kt b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/RemoteAuthTest.kt
new file mode 100644
index 0000000..8d8bf82
--- /dev/null
+++ b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/RemoteAuthTest.kt
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.authentication
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.net.Uri
+import android.os.IBinder
+import android.os.RemoteException
+import android.util.Pair
+import androidx.wear.phone.interactions.WearPhoneInteractionsTestRunner
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.robolectric.annotation.internal.DoNotInstrument
+import java.util.ArrayList
+import java.util.concurrent.Executor
+
+/** Unit tests for [RemoteAuthClient].  */
+@RunWith(WearPhoneInteractionsTestRunner::class)
+@DoNotInstrument // Needed because it is defined in the "android" package.
+public class RemoteAuthTest {
+
+    internal companion object {
+        private val DIRECT_EXECUTOR = Executor { command -> command.run() }
+        private const val authProviderUrlA = "http://myrequesturl/a?client_id=iamtheclient"
+        private const val authProviderUrlB = "http://myrequesturl/b?client_id=iamtheclient"
+        private val responseUrl = Uri.parse("http://myresponseurl")
+        private const val appPackageName = "com.friendlyapp"
+        private val requestA =
+            OAuthRequest.Builder(appPackageName)
+                .setAuthProviderUrl(Uri.parse(authProviderUrlA))
+                .setCodeChallenge(CodeChallenge(CodeVerifier()))
+                .build()
+        private val requestB =
+            OAuthRequest.Builder(appPackageName)
+                .setAuthProviderUrl(Uri.parse(authProviderUrlB))
+                .setCodeChallenge(CodeChallenge(CodeVerifier()))
+                .build()
+        private val response =
+            OAuthResponse.Builder().setResponseUrl(responseUrl).build()
+
+        // Note: This can't be static as Robolectric isn't set up at class init time.
+        private val mServiceName = ComponentName(
+            "com.google.android.wearable.app", "auth_lib_shouldnt_care_about_this"
+        )
+        private val mockCallback: RemoteAuthClient.Callback =
+            Mockito.mock(RemoteAuthClient.Callback::class.java)
+    }
+
+    private var fakeServiceBinder: FakeServiceBinder = FakeServiceBinder()
+    private var fakeService: FakeClockworkHomeAuthService = FakeClockworkHomeAuthService()
+    private var clientUnderTest: RemoteAuthClient =
+        RemoteAuthClient(fakeServiceBinder, DIRECT_EXECUTOR, appPackageName)
+
+    @Test
+    public fun doesntConnectUntilARequestIsMade() {
+        // WHEN the client is created
+        // THEN the Auth library should not yet connect to Clockwork Home
+        Assert.assertEquals(ConnectionState.DISCONNECTED, fakeServiceBinder.state)
+    }
+
+    @Test
+    public fun sendAuthorizationRequestShouldMakeConnectionToClockworkHome() {
+        val requestUri = "http://myrequesturl?client_id=xxx"
+        // WHEN an authorization request is sent
+        clientUnderTest.sendAuthorizationRequest(
+            OAuthRequest.Builder(appPackageName)
+                .setAuthProviderUrl(Uri.parse(requestUri))
+                .setCodeChallenge(CodeChallenge(CodeVerifier()))
+                .build(),
+            mockCallback
+        )
+        // THEN a connection is made to Clockwork Home's Auth service
+        Assert.assertEquals(ConnectionState.CONNECTING, fakeServiceBinder.state)
+    }
+
+    @Test
+    public fun sendAuthorizationRequestShouldCallBinderMethod() {
+        // WHEN an authorization request is sent
+        clientUnderTest.sendAuthorizationRequest(requestA, mockCallback)
+        fakeServiceBinder.completeConnection()
+        // THEN a request is made to Clockwork Home
+        val request = fakeService.requests[0]
+        val requestReceived = request.first
+        // THEN the request url is set correctly
+        Assert.assertEquals(
+            requestA.getRequestUrl(),
+            requestReceived.getRequestUrl()
+        )
+        Assert.assertEquals(
+            requestReceived.getRequestUrl().toString().indexOf(authProviderUrlA),
+            0
+        )
+    }
+
+    @Test
+    public fun twoQueuedAuthorizationRequestsBeforeConnectCompletes() {
+        // GIVEN two authorization requests were made before connecting to Clockwork Home completes
+        clientUnderTest.sendAuthorizationRequest(requestA, mockCallback)
+        clientUnderTest.sendAuthorizationRequest(requestB, mockCallback)
+        // WHEN the connection does complete
+        fakeServiceBinder.completeConnection()
+        // THEN two requests are made to Clockwork Home
+        val requestAReceived = fakeService.requests[0].first
+        val requestBReceived = fakeService.requests[1].first
+        Assert.assertEquals(2, fakeService.requests.size.toLong())
+        // THEN the request url is set correctly for both (A then B)
+        Assert.assertEquals(
+            requestA.getRequestUrl(),
+            requestAReceived.getRequestUrl()
+        )
+        Assert.assertEquals(
+            requestB.getRequestUrl(),
+            requestBReceived.getRequestUrl()
+        )
+        Assert.assertEquals(
+            requestAReceived.getRequestUrl().toString().indexOf(authProviderUrlA),
+            0
+        )
+        Assert.assertEquals(
+            requestBReceived.getRequestUrl().toString().indexOf(authProviderUrlB),
+            0
+        )
+    }
+
+    @Test
+    @Throws(RemoteException::class)
+    public fun requestCompletionShouldCallBackToClient() {
+        // GIVEN an authorization request was sent
+        clientUnderTest.sendAuthorizationRequest(requestA, mockCallback)
+        fakeServiceBinder.completeConnection()
+        val request = fakeService.requests[0]
+        // WHEN the request completes
+        // callback supplied earlier is called with the correct request URL and response URL
+        Mockito.verify(mockCallback).onAuthorizationResponse(request.first, response)
+    }
+
+    @Test
+    @Throws(RemoteException::class)
+    public fun doesntDisconnectWhenRequestStillInProgress() {
+        // GIVEN 2 authorization requests were sent
+        clientUnderTest.sendAuthorizationRequest(requestA, mockCallback)
+        // GIVEN the async binding to Clockwork Home completed after the 1st but before the 2nd
+        fakeServiceBinder.completeConnection()
+        clientUnderTest.sendAuthorizationRequest(requestB, mockCallback)
+        // WHEN the first one completes
+        RemoteAuthService.sendResponseToCallback(
+            response,
+            fakeService.requests[0].second
+        )
+        // THEN the service remains connected (as there's still a request ongoing, and we won't get
+        // the callback for the other request if we unbind now)
+        Assert.assertEquals(ConnectionState.CONNECTED, fakeServiceBinder.state)
+    }
+
+    @Test
+    @Throws(RemoteException::class)
+    public fun disconnectsWhenAllRequestsComplete() {
+        // GIVEN 2 authorization requests were sent
+        clientUnderTest.sendAuthorizationRequest(requestA, mockCallback)
+        // GIVEN the async binding to Clockwork Home completed after the 1st but before the 2nd
+        fakeServiceBinder.completeConnection()
+        clientUnderTest.sendAuthorizationRequest(requestB, mockCallback)
+        RemoteAuthService.sendResponseToCallback(
+            response,
+            fakeService.requests[0].second
+        )
+        // WHEN the other completes
+        RemoteAuthService.sendResponseToCallback(
+            response,
+            fakeService.requests[1].second
+        )
+        // THEN the OAuth library disconnects from Clockwork Home
+        Assert.assertEquals(ConnectionState.DISCONNECTED, fakeServiceBinder.state)
+    }
+
+    private enum class ConnectionState {
+        DISCONNECTED, CONNECTING, CONNECTED
+    }
+
+    /** Fakes binding to Clockwork Home.  */
+    private inner class FakeServiceBinder : RemoteAuthClient.ServiceBinder {
+        var state = ConnectionState.DISCONNECTED
+        private var serviceConnection: ServiceConnection? = null
+        override fun bindService(
+            intent: Intent?,
+            connection: ServiceConnection?,
+            flags: Int
+        ): Boolean {
+            if (intent!!.getPackage() != RemoteAuthClient.WEARABLE_PACKAGE_NAME) {
+                throw UnsupportedOperationException()
+            }
+            if (intent.action != RemoteAuthClient.ACTION_AUTH) {
+                throw UnsupportedOperationException()
+            }
+            check(state == ConnectionState.DISCONNECTED) { "Already connected or connecting" }
+            state = ConnectionState.CONNECTING
+            serviceConnection = connection
+            return true
+        }
+
+        fun completeConnection() {
+            Assert.assertTrue(state == ConnectionState.CONNECTING)
+            state = ConnectionState.CONNECTED
+            serviceConnection!!.onServiceConnected(mServiceName, fakeService.onBind(Intent()))
+        }
+
+        override fun unbindService(connection: ServiceConnection?) {
+            check(state != ConnectionState.DISCONNECTED) { "Not connected; can't disconnect" }
+            state = ConnectionState.DISCONNECTED
+            serviceConnection = null
+        }
+    }
+
+    /**
+     * Fake implementation of the OAuth service in Clockwork Home. Instead of talking to the user's
+     * phone, this class just records the method calls that were made so we can handle them manually
+     * in our tests.
+     */
+    private inner class FakeClockworkHomeAuthService : RemoteAuthService() {
+        private val requestHandler: RemoteAuthRequestHandler
+
+        val requests: MutableList<Pair<OAuthRequest, kotlin.Pair<String, Int>>> =
+            ArrayList()
+
+        init {
+            requestHandler = AuthenticationRequestHandler()
+        }
+
+        override fun onBind(intent: Intent): IBinder {
+            return onBind(intent, requestHandler)
+        }
+
+        override fun verifyPackageName(context: Context, requestPackageName: String?): Boolean {
+            return true
+        }
+
+        private inner class AuthenticationRequestHandler : RemoteAuthRequestHandler {
+            override fun isAuthSupported(): Boolean {
+                return true
+            }
+
+            override fun sendAuthRequest(
+                request: OAuthRequest,
+                packageNameAndRequestId: kotlin.Pair<String, Int>
+            ) {
+                if (fakeServiceBinder.state != ConnectionState.CONNECTED) {
+                    throw RemoteException("not connected")
+                }
+                requests.add(Pair.create(request, packageNameAndRequestId))
+                mockCallback.onAuthorizationResponse(request, response)
+            }
+        }
+    }
+}
diff --git a/wear/wear-phone-interactions/src/test/resources/robolectric.properties b/wear/wear-phone-interactions/src/test/resources/robolectric.properties
index c0066c1..74bb2a9 100644
--- a/wear/wear-phone-interactions/src/test/resources/robolectric.properties
+++ b/wear/wear-phone-interactions/src/test/resources/robolectric.properties
@@ -14,7 +14,7 @@
 # limitations under the License.
 #
 
-# Robolectric currently doesn't support API 30, so we have to explicitly the target sdk levels for
-# now.
+# Robolectric currently doesn't support API 30, so we have to explicitly specify the target sdk
+# levels for now.
 # TODO(b/177072877): Remove when no longer necessary.
-sdk=25,26,27,28
+sdk=28
diff --git a/wear/wear-remote-interactions/api/current.txt b/wear/wear-remote-interactions/api/current.txt
index f0446b9..0a4a745 100644
--- a/wear/wear-remote-interactions/api/current.txt
+++ b/wear/wear-remote-interactions/api/current.txt
@@ -1,6 +1,14 @@
 // Signature format: 4.0
 package androidx.wear.remote.interactions {
 
+  @RequiresApi(android.os.Build.VERSION_CODES.N) public final class PlayStoreAvailability {
+    method public int getPlayStoreAvailabilityOnPhone(android.content.Context context);
+    field public static final androidx.wear.remote.interactions.PlayStoreAvailability INSTANCE;
+    field public static final int PLAY_STORE_AVAILABLE = 1; // 0x1
+    field public static final int PLAY_STORE_ERROR_UNKNOWN = 0; // 0x0
+    field public static final int PLAY_STORE_UNAVAILABLE = 2; // 0x2
+  }
+
   public final class WatchFaceConfigIntentHelper {
     method public static String? getPeerIdExtra(android.content.Intent watchFaceIntent);
     method public static android.content.ComponentName? getWatchFaceComponentExtra(android.content.Intent watchFaceIntent);
diff --git a/wear/wear-remote-interactions/api/public_plus_experimental_current.txt b/wear/wear-remote-interactions/api/public_plus_experimental_current.txt
index f0446b9..0a4a745 100644
--- a/wear/wear-remote-interactions/api/public_plus_experimental_current.txt
+++ b/wear/wear-remote-interactions/api/public_plus_experimental_current.txt
@@ -1,6 +1,14 @@
 // Signature format: 4.0
 package androidx.wear.remote.interactions {
 
+  @RequiresApi(android.os.Build.VERSION_CODES.N) public final class PlayStoreAvailability {
+    method public int getPlayStoreAvailabilityOnPhone(android.content.Context context);
+    field public static final androidx.wear.remote.interactions.PlayStoreAvailability INSTANCE;
+    field public static final int PLAY_STORE_AVAILABLE = 1; // 0x1
+    field public static final int PLAY_STORE_ERROR_UNKNOWN = 0; // 0x0
+    field public static final int PLAY_STORE_UNAVAILABLE = 2; // 0x2
+  }
+
   public final class WatchFaceConfigIntentHelper {
     method public static String? getPeerIdExtra(android.content.Intent watchFaceIntent);
     method public static android.content.ComponentName? getWatchFaceComponentExtra(android.content.Intent watchFaceIntent);
diff --git a/wear/wear-remote-interactions/api/restricted_current.txt b/wear/wear-remote-interactions/api/restricted_current.txt
index f0446b9..0a4a745 100644
--- a/wear/wear-remote-interactions/api/restricted_current.txt
+++ b/wear/wear-remote-interactions/api/restricted_current.txt
@@ -1,6 +1,14 @@
 // Signature format: 4.0
 package androidx.wear.remote.interactions {
 
+  @RequiresApi(android.os.Build.VERSION_CODES.N) public final class PlayStoreAvailability {
+    method public int getPlayStoreAvailabilityOnPhone(android.content.Context context);
+    field public static final androidx.wear.remote.interactions.PlayStoreAvailability INSTANCE;
+    field public static final int PLAY_STORE_AVAILABLE = 1; // 0x1
+    field public static final int PLAY_STORE_ERROR_UNKNOWN = 0; // 0x0
+    field public static final int PLAY_STORE_UNAVAILABLE = 2; // 0x2
+  }
+
   public final class WatchFaceConfigIntentHelper {
     method public static String? getPeerIdExtra(android.content.Intent watchFaceIntent);
     method public static android.content.ComponentName? getWatchFaceComponentExtra(android.content.Intent watchFaceIntent);
diff --git a/wear/wear-remote-interactions/build.gradle b/wear/wear-remote-interactions/build.gradle
index d61dcea..3b5f727 100644
--- a/wear/wear-remote-interactions/build.gradle
+++ b/wear/wear-remote-interactions/build.gradle
@@ -37,6 +37,12 @@
     testImplementation(ANDROIDX_TEST_EXT_JUNIT)
     testImplementation(ANDROIDX_TEST_CORE)
     testImplementation(ANDROIDX_TEST_RULES)
+    testImplementation(ANDROIDX_TEST_RUNNER)
+    testImplementation(ROBOLECTRIC)
+    testImplementation(MOCKITO_CORE)
+    testImplementation(MOCKITO_KOTLIN)
+
+    implementation(PLAY_SERVICES_BASE)
 }
 
 android {
diff --git a/wear/wear-remote-interactions/src/main/java/androidx/wear/remote/interactions/PlayStoreAvailability.kt b/wear/wear-remote-interactions/src/main/java/androidx/wear/remote/interactions/PlayStoreAvailability.kt
new file mode 100644
index 0000000..02f04f9
--- /dev/null
+++ b/wear/wear-remote-interactions/src/main/java/androidx/wear/remote/interactions/PlayStoreAvailability.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.wear.remote.interactions
+
+import android.content.Context
+import android.net.Uri
+import android.os.Build
+import android.provider.Settings
+import androidx.annotation.IntDef
+import androidx.annotation.RequiresApi
+import com.google.android.gms.common.ConnectionResult
+import com.google.android.gms.common.GoogleApiAvailability
+
+/**
+ * Helper class for checking whether the phone paired to a given Wear OS device has the Play Store.
+ */
+@RequiresApi(Build.VERSION_CODES.N)
+public object PlayStoreAvailability {
+    /**
+     * This value means that there was an error in checking for whether the Play Store is available
+     * on the phone.
+     */
+    public const val PLAY_STORE_ERROR_UNKNOWN: Int = 0
+
+    /** This value means that the Play Store is available on the phone.  */
+    public const val PLAY_STORE_AVAILABLE: Int = 1
+
+    /** This value means that the Play Store is not available on the phone.  */
+    public const val PLAY_STORE_UNAVAILABLE: Int = 2
+
+    private const val PLAY_STORE_AVAILABILITY_PATH = "play_store_availability"
+    internal const val SETTINGS_AUTHORITY_URI = "com.google.android.wearable.settings"
+    internal val PLAY_STORE_AVAILABILITY_URI = Uri.Builder()
+        .scheme("content")
+        .authority(SETTINGS_AUTHORITY_URI)
+        .path(PLAY_STORE_AVAILABILITY_PATH)
+        .build()
+
+    // The name of the row which stores the play store availability setting in versions before R.
+    internal const val KEY_PLAY_STORE_AVAILABILITY = "play_store_availability"
+
+    // The name of the settings value which stores the play store availability setting in versions
+    // from R.
+    private const val SETTINGS_PLAY_STORE_AVAILABILITY = "phone_play_store_availability"
+
+    internal const val SYSTEM_FEATURE_WATCH: String = "android.hardware.type.watch"
+
+    /**
+     * Returns whether the Play Store is available on the Phone. If
+     * [PLAY_STORE_ERROR_UNKNOWN] is returned, the caller should try again later. This
+     * method should not be run on the main thread.
+     *
+     * @return One of three values: [PLAY_STORE_AVAILABLE],
+     * [PLAY_STORE_UNAVAILABLE], or [PLAY_STORE_ERROR_UNKNOWN].
+     */
+    @PlayStoreStatus
+    public fun getPlayStoreAvailabilityOnPhone(context: Context): Int {
+        val isCurrentDeviceAWatch = context.packageManager.hasSystemFeature(
+            SYSTEM_FEATURE_WATCH
+        )
+
+        if (!isCurrentDeviceAWatch) {
+            val isPlayServiceAvailable =
+                GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context)
+            return if (isPlayServiceAvailable == ConnectionResult.SUCCESS) PLAY_STORE_AVAILABLE
+            else PLAY_STORE_UNAVAILABLE
+        }
+
+        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
+            context.contentResolver.query(
+                PLAY_STORE_AVAILABILITY_URI, null, null, null,
+                null
+            )?.use { cursor ->
+                while (cursor.moveToNext()) {
+                    if (KEY_PLAY_STORE_AVAILABILITY == cursor.getString(0)) {
+                        return cursor.getInt(1)
+                    }
+                }
+            }
+        } else {
+            return Settings.Global.getInt(
+                context.contentResolver, SETTINGS_PLAY_STORE_AVAILABILITY,
+                PLAY_STORE_ERROR_UNKNOWN
+            )
+        }
+        return PLAY_STORE_ERROR_UNKNOWN
+    }
+
+    /** @hide */
+    @IntDef(
+        PLAY_STORE_ERROR_UNKNOWN,
+        PLAY_STORE_AVAILABLE,
+        PLAY_STORE_UNAVAILABLE
+    )
+    @Retention(AnnotationRetention.SOURCE)
+    public annotation class PlayStoreStatus
+}
\ No newline at end of file
diff --git a/wear/wear-remote-interactions/src/test/java/androidx/wear/remote/interactions/PlayStoreAvailabilityTest.kt b/wear/wear-remote-interactions/src/test/java/androidx/wear/remote/interactions/PlayStoreAvailabilityTest.kt
new file mode 100644
index 0000000..2560004
--- /dev/null
+++ b/wear/wear-remote-interactions/src/test/java/androidx/wear/remote/interactions/PlayStoreAvailabilityTest.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.remote.interactions
+
+import android.content.ContentProvider
+import android.content.ContentResolver
+import android.content.Context
+import android.database.Cursor
+import android.database.MatrixCursor
+import androidx.test.core.app.ApplicationProvider
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+import org.robolectric.Shadows
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadows.ShadowContentResolver
+import org.robolectric.shadows.ShadowPackageManager
+
+@RunWith(WearRemoteInteractionsTestRunner::class)
+@DoNotInstrument // Stop Robolectric instrumenting this class due to it being in package "android".
+class PlayStoreAvailabilityTest {
+    @Mock
+    private var mockContentProvider: ContentProvider? = null
+    private var contentResolver: ContentResolver? = null
+    private var shadowPackageManager: ShadowPackageManager? = null
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        ShadowContentResolver.registerProviderInternal(
+            PlayStoreAvailability.SETTINGS_AUTHORITY_URI,
+            mockContentProvider
+        )
+        val context: Context = ApplicationProvider.getApplicationContext()
+        contentResolver = context.contentResolver
+        shadowPackageManager = Shadows.shadowOf(context.packageManager)
+        shadowPackageManager?.setSystemFeature(PlayStoreAvailability.SYSTEM_FEATURE_WATCH, true)
+    }
+
+    @Test
+    @Config(sdk = [25, 26, 27, 28])
+    fun getPlayStoreAvailabilityOnPhone_returnsAvailable() {
+        createFakePlayStoreAvailabilityQuery(PlayStoreAvailability.PLAY_STORE_AVAILABLE)
+        assertEquals(
+            PlayStoreAvailability.getPlayStoreAvailabilityOnPhone(
+                ApplicationProvider.getApplicationContext()
+            ),
+            PlayStoreAvailability.PLAY_STORE_AVAILABLE
+        )
+    }
+
+    @Test
+    @Config(sdk = [25, 26, 27, 28])
+    fun getPlayStoreAvailabilityOnPhone_returnsUnavailable() {
+        createFakePlayStoreAvailabilityQuery(PlayStoreAvailability.PLAY_STORE_UNAVAILABLE)
+        assertEquals(
+            PlayStoreAvailability.getPlayStoreAvailabilityOnPhone(
+                ApplicationProvider.getApplicationContext()
+            ),
+            PlayStoreAvailability.PLAY_STORE_UNAVAILABLE
+        )
+    }
+
+    @Test
+    @Config(sdk = [25, 26, 27, 28])
+    fun getPlayStoreAvailabilityOnPhone_returnsError() {
+        assertEquals(
+            PlayStoreAvailability.getPlayStoreAvailabilityOnPhone(
+                ApplicationProvider.getApplicationContext()
+            ),
+            PlayStoreAvailability.PLAY_STORE_ERROR_UNKNOWN
+        )
+    }
+
+    /*
+    TODO(b/178086256): Since Roboelectric doesn't support API 30 for now, add tests for it when it
+     is supported.
+    */
+
+    companion object {
+        private fun createFakePlayStoreAvailabilityCursor(availability: Int): Cursor {
+            val cursor = MatrixCursor(arrayOf("key", "value"))
+            cursor.addRow(
+                arrayOf<Any>(PlayStoreAvailability.KEY_PLAY_STORE_AVAILABILITY, availability)
+            )
+            return cursor
+        }
+    }
+
+    private fun createFakePlayStoreAvailabilityQuery(availability: Int) {
+        Mockito.`when`(
+            mockContentProvider!!.query(
+                ArgumentMatchers.eq(PlayStoreAvailability.PLAY_STORE_AVAILABILITY_URI),
+                ArgumentMatchers.any(),
+                ArgumentMatchers.any(),
+                ArgumentMatchers.any(),
+                ArgumentMatchers.any()
+            )
+        )
+            .thenReturn(createFakePlayStoreAvailabilityCursor(availability))
+    }
+}
diff --git a/wear/wear-remote-interactions/src/test/java/androidx/wear/remote/interactions/WearRemoteInteractionsTestRunner.kt b/wear/wear-remote-interactions/src/test/java/androidx/wear/remote/interactions/WearRemoteInteractionsTestRunner.kt
new file mode 100644
index 0000000..c15dd4b
--- /dev/null
+++ b/wear/wear-remote-interactions/src/test/java/androidx/wear/remote/interactions/WearRemoteInteractionsTestRunner.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.wear.remote.interactions
+
+import org.junit.runners.model.FrameworkMethod
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.internal.bytecode.InstrumentationConfiguration
+
+/**
+ * A [RobolectricTestRunner] for [androidx.wear.remote.interactions] unit tests.
+ *
+ * It has instrumentation turned off for the [androidx.wear.remote.interactions] package.
+ *
+ * Robolectric tries to instrument Kotlin classes, and it throws errors when it encounters
+ * companion objects, constructors with default values for parameters, and data classes with
+ * inline classes. We don't need shadowing of our classes because we want to use the actual
+ * objects in our tests.
+ */
+class WearRemoteInteractionsTestRunner(testClass: Class<*>) : RobolectricTestRunner(testClass) {
+    override fun createClassLoaderConfig(method: FrameworkMethod): InstrumentationConfiguration =
+        InstrumentationConfiguration.Builder(
+            super.createClassLoaderConfig(method)
+        )
+            .doNotInstrumentPackage("androidx.wear.remote.interactions")
+            .build()
+}
\ No newline at end of file
diff --git a/wear/wear-remote-interactions/src/test/resources/robolectric.properties b/wear/wear-remote-interactions/src/test/resources/robolectric.properties
new file mode 100644
index 0000000..c0066c1
--- /dev/null
+++ b/wear/wear-remote-interactions/src/test/resources/robolectric.properties
@@ -0,0 +1,20 @@
+#
+# Copyright 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Robolectric currently doesn't support API 30, so we have to explicitly the target sdk levels for
+# now.
+# TODO(b/177072877): Remove when no longer necessary.
+sdk=25,26,27,28
diff --git a/window/window-extensions/build.gradle b/window/window-extensions/build.gradle
index e4db938..5b1a650 100644
--- a/window/window-extensions/build.gradle
+++ b/window/window-extensions/build.gradle
@@ -49,7 +49,7 @@
 
 androidx {
     name = "Jetpack WindowManager library Extensions"
-    publish = Publish.SNAPSHOT_ONLY
+    publish = Publish.SNAPSHOT_AND_RELEASE // Only to generate per-project-zips
     runApiTasks = new RunApiTasks.Yes("Need to track API surface before moving to publish")
     mavenGroup = LibraryGroups.WINDOW
     mavenVersion = LibraryVersions.WINDOW_EXTENSIONS
diff --git a/window/window-sidecar/build.gradle b/window/window-sidecar/build.gradle
index 6c81f6e..d8b8fd3 100644
--- a/window/window-sidecar/build.gradle
+++ b/window/window-sidecar/build.gradle
@@ -36,7 +36,7 @@
 
 androidx {
     name = "Jetpack WindowManager library Sidecar"
-    publish = Publish.NONE
+    publish = Publish.SNAPSHOT_AND_RELEASE // Only to generate per-project-zips
     runApiTasks = new RunApiTasks.Yes("Need to track API surface but should never publish")
     mavenGroup = LibraryGroups.WINDOW
     mavenVersion = LibraryVersions.WINDOW_SIDECAR