Merge "Kotlin 1.6.0" into androidx-main
diff --git a/activity/activity-ktx/build.gradle b/activity/activity-ktx/build.gradle
index e8c10bf..cc7ef29 100644
--- a/activity/activity-ktx/build.gradle
+++ b/activity/activity-ktx/build.gradle
@@ -33,7 +33,7 @@
         because 'Mirror activity dependency graph for -ktx artifacts'
     }
     api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1")
-    api("androidx.savedstate:savedstate-ktx:1.1.0") {
+    api(projectOrArtifact(":savedstate:savedstate-ktx")) {
         because 'Mirror activity dependency graph for -ktx artifacts'
     }
     api(libs.kotlinStdlib)
diff --git a/activity/activity/build.gradle b/activity/activity/build.gradle
index 10ce7d3..a2fa9b7 100644
--- a/activity/activity/build.gradle
+++ b/activity/activity/build.gradle
@@ -20,7 +20,7 @@
     api(projectOrArtifact(":core:core"))
     api("androidx.lifecycle:lifecycle-runtime:2.3.1")
     api("androidx.lifecycle:lifecycle-viewmodel:2.3.1")
-    api("androidx.savedstate:savedstate:1.1.0")
+    api(projectOrArtifact(":savedstate:savedstate"))
     api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.1")
     implementation("androidx.tracing:tracing:1.0.0")
     api(libs.kotlinStdlib)
diff --git a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivitySavedStateTest.kt b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivitySavedStateTest.kt
index 7c1c691..00c2002 100644
--- a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivitySavedStateTest.kt
+++ b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivitySavedStateTest.kt
@@ -18,6 +18,8 @@
 
 import android.os.Bundle
 import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
 import androidx.savedstate.SavedStateRegistry
 import androidx.test.core.app.ActivityScenario
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -90,6 +92,16 @@
             recreate()
         }
     }
+
+    @Test
+    @Throws(Throwable::class)
+    fun savedStateEarlyRegisterInitAddedLifecycleObserver() {
+        with(ActivityScenario.launch(SavedStateActivity::class.java)) {
+            initializeSavedState()
+            SavedStateActivity.checkEnabledInInitAddedLifecycleObserver = true
+            recreate()
+        }
+    }
 }
 
 private class DefaultProvider : SavedStateRegistry.SavedStateProvider {
@@ -115,6 +127,16 @@
                 checkEnabledInOnContextAvailable = false
             }
         }
+        lifecycle.addObserver(object : LifecycleEventObserver {
+            override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+                if (event == Lifecycle.Event.ON_CREATE &&
+                    checkEnabledInInitAddedLifecycleObserver) {
+                    checkDefaultSavedState(savedStateRegistry)
+                    checkEnabledInInitAddedLifecycleObserver = false
+                    lifecycle.removeObserver(this)
+                }
+            }
+        })
     }
 
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -127,6 +149,7 @@
 
     companion object {
         internal var checkEnabledInOnCreate = false
+        internal var checkEnabledInInitAddedLifecycleObserver = false
         internal var checkEnabledInOnContextAvailable = false
     }
 }
diff --git a/activity/activity/src/main/java/androidx/activity/ComponentActivity.java b/activity/activity/src/main/java/androidx/activity/ComponentActivity.java
index 8990084..030ec7f 100644
--- a/activity/activity/src/main/java/androidx/activity/ComponentActivity.java
+++ b/activity/activity/src/main/java/androidx/activity/ComponentActivity.java
@@ -271,6 +271,7 @@
                 getLifecycle().removeObserver(this);
             }
         });
+        mSavedStateRegistryController.performAttach();
 
         if (19 <= SDK_INT && SDK_INT <= 23) {
             getLifecycle().addObserver(new ImmLeaksCleaner(this));
diff --git a/busytown/impl/build-studio-and-androidx.sh b/busytown/impl/build-studio-and-androidx.sh
index 2aa37c3..861723a 100755
--- a/busytown/impl/build-studio-and-androidx.sh
+++ b/busytown/impl/build-studio-and-androidx.sh
@@ -52,7 +52,7 @@
 
 function zipStudio() {
   cd "$STUDIO_DIR/out/"
-  zip -r "$DIST_DIR/tools.zip" repo
+  zip -qr "$DIST_DIR/tools.zip" repo
   cd -
 }
 
diff --git a/camera/camera-extensions/build.gradle b/camera/camera-extensions/build.gradle
index 5833337..d16ef32 100644
--- a/camera/camera-extensions/build.gradle
+++ b/camera/camera-extensions/build.gradle
@@ -56,6 +56,7 @@
     androidTestImplementation(libs.multidex)
     androidTestImplementation(project(":camera:camera-lifecycle"))
     androidTestImplementation(project(":camera:camera-testing"))
+    androidTestImplementation(project(":camera:camera-video"))
     androidTestImplementation(project(":internal-testutils-truth"))
     androidTestImplementation(project(":camera:camera-testlib-extensions"))
     // To use the testlib to have the implementation of the extensions-stub interface.
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt
index 81efa20..e883ed5 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt
@@ -21,6 +21,7 @@
 import androidx.camera.core.Camera
 import androidx.camera.core.CameraInfo
 import androidx.camera.core.CameraSelector
+import androidx.camera.core.VideoCapture
 import androidx.camera.core.impl.CameraInfoInternal
 import androidx.camera.extensions.internal.ExtensionVersion
 import androidx.camera.extensions.internal.Version
@@ -430,6 +431,23 @@
         }
     }
 
+    @Test
+    fun throwIllegalArgumentException_whenBindingVideoCapture(): Unit = runBlocking {
+        val extensionCameraSelector = checkExtensionAvailabilityAndInit()
+
+        withContext(Dispatchers.Main) {
+            val fakeLifecycleOwner = FakeLifecycleOwner()
+
+            assertThrows<IllegalArgumentException> {
+                cameraProvider.bindToLifecycle(
+                    fakeLifecycleOwner,
+                    extensionCameraSelector,
+                    VideoCapture.Builder().build()
+                )
+            }
+        }
+    }
+
     private fun checkExtensionAvailabilityAndInit(): CameraSelector {
         extensionsManager = ExtensionsManager.getInstanceAsync(
             context,
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ExtensionsUseCaseConfigFactory.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ExtensionsUseCaseConfigFactory.java
index b792fe7..fc383e5 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ExtensionsUseCaseConfigFactory.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ExtensionsUseCaseConfigFactory.java
@@ -62,6 +62,9 @@
                 mutableOptionsBundle =
                         MutableOptionsBundle.from(mPreviewConfigProvider.getConfig());
                 break;
+            case VIDEO_CAPTURE:
+                throw new IllegalArgumentException("CameraX Extensions doesn't support "
+                        + "VideoCapture!");
             default:
                 return null;
         }
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ToggleButtonUITest.java b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ToggleButtonUITest.java
index a9af102..25f75e6 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ToggleButtonUITest.java
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ToggleButtonUITest.java
@@ -132,6 +132,7 @@
         cameraProvider.shutdown().get(10, TimeUnit.SECONDS);
     }
 
+
     @Test
     public void testFlashToggleButton() {
         waitFor(new WaitForViewToShow(R.id.constraintLayout));
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeInputField.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeInputField.kt
index edc57c1..5e03fe3 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeInputField.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeInputField.kt
@@ -39,6 +39,7 @@
 import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
 
 @Composable
 fun InputFieldDemo() {
@@ -74,6 +75,13 @@
     }
 }
 
+@Composable
+fun DialogInputFieldDemo(onNavigateUp: () -> Unit) {
+    Dialog(onDismissRequest = onNavigateUp) {
+        InputFieldDemo()
+    }
+}
+
 @OptIn(ExperimentalComposeUiApi::class)
 @Composable
 internal fun EditLine(
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
index db87997..77be109 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
@@ -42,7 +42,10 @@
                 ComposableDemo("Min/Max Lines") { BasicTextFieldMinMaxDemo() },
                 ComposableDemo("Ime SingleLine") { ImeSingleLineDemo() },
                 ComposableDemo("Capitalization/AutoCorrect") { CapitalizationAutoCorrectDemo() },
-                ComposableDemo("TextFieldValue") { TextFieldValueDemo() }
+                ComposableDemo("TextFieldValue") { TextFieldValueDemo() },
+                ComposableDemo("Inside Dialog") { onNavigateUp ->
+                    DialogInputFieldDemo(onNavigateUp)
+                },
             )
         ),
         ComposableDemo("Text Accessibility") { TextAccessibilityDemo() }
diff --git a/compose/integration-tests/demos/common/src/main/java/androidx/compose/integration/demos/common/Demo.kt b/compose/integration-tests/demos/common/src/main/java/androidx/compose/integration/demos/common/Demo.kt
index 2c1d864..7271573 100644
--- a/compose/integration-tests/demos/common/src/main/java/androidx/compose/integration/demos/common/Demo.kt
+++ b/compose/integration-tests/demos/common/src/main/java/androidx/compose/integration/demos/common/Demo.kt
@@ -45,7 +45,8 @@
 /**
  * Demo that displays [Composable] [content] when selected.
  */
-class ComposableDemo(title: String, val content: @Composable () -> Unit) : Demo(title)
+class ComposableDemo(title: String, val content: @Composable (onNavigateUp: () -> Unit) -> Unit) :
+    Demo(title)
 
 /**
  * A category of [Demo]s, that will display a list of [demos] when selected.
diff --git a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoApp.kt b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoApp.kt
index a878e475..858fa8d 100644
--- a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoApp.kt
+++ b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoApp.kt
@@ -92,7 +92,7 @@
         }
     ) { innerPadding ->
         val modifier = Modifier.padding(innerPadding)
-        DemoContent(modifier, currentDemo, isFiltering, filterText, onNavigateToDemo)
+        DemoContent(modifier, currentDemo, isFiltering, filterText, onNavigateToDemo, onNavigateUp)
     }
 }
 
@@ -102,7 +102,8 @@
     currentDemo: Demo,
     isFiltering: Boolean,
     filterText: String,
-    onNavigate: (Demo) -> Unit
+    onNavigate: (Demo) -> Unit,
+    onNavigateUp: () -> Unit
 ) {
     Crossfade(isFiltering to currentDemo) { (filtering, demo) ->
         Surface(modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
@@ -113,19 +114,19 @@
                     onNavigate = onNavigate
                 )
             } else {
-                DisplayDemo(demo, onNavigate)
+                DisplayDemo(demo, onNavigate, onNavigateUp)
             }
         }
     }
 }
 
 @Composable
-private fun DisplayDemo(demo: Demo, onNavigate: (Demo) -> Unit) {
+private fun DisplayDemo(demo: Demo, onNavigate: (Demo) -> Unit, onNavigateUp: () -> Unit) {
     when (demo) {
         is ActivityDemo<*> -> {
             /* should never get here as activity demos are not added to the backstack*/
         }
-        is ComposableDemo -> demo.content()
+        is ComposableDemo -> demo.content(onNavigateUp)
         is DemoCategory -> DisplayDemoCategory(demo, onNavigate)
         is FragmentDemo<*> -> {
             lateinit var view: FragmentContainerView
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/IoSettingsStartupBenchmark.kt b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/IoSettingsStartupBenchmark.kt
index 13ba40b..cc5e31f 100644
--- a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/IoSettingsStartupBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/IoSettingsStartupBenchmark.kt
@@ -48,8 +48,6 @@
     companion object {
         @Parameterized.Parameters(name = "startup={0},compilation={1}")
         @JvmStatic
-        fun parameters() = createStartupCompilationParams(
-            compilationModes = COMPOSE_COMPILATION_MODES
-        )
+        fun parameters() = createStartupCompilationParams()
     }
 }
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/NestedListsScrollBenchmark.kt b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/NestedListsScrollBenchmark.kt
index 4b3fe6b..9b20220 100644
--- a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/NestedListsScrollBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/NestedListsScrollBenchmark.kt
@@ -84,8 +84,6 @@
 
         @Parameterized.Parameters(name = "compilation={0}")
         @JvmStatic
-        fun parameters() = createCompilationParams(
-            compilationModes = COMPOSE_COMPILATION_MODES
-        )
+        fun parameters() = createCompilationParams()
     }
 }
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/SmallListStartupBenchmark.kt b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/SmallListStartupBenchmark.kt
index 3100c8e..cb9e113 100644
--- a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/SmallListStartupBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/SmallListStartupBenchmark.kt
@@ -49,8 +49,6 @@
     companion object {
         @Parameterized.Parameters(name = "startup={0},compilation={1}")
         @JvmStatic
-        fun parameters() = createStartupCompilationParams(
-            compilationModes = COMPOSE_COMPILATION_MODES
-        )
+        fun parameters() = createStartupCompilationParams()
     }
 }
\ No newline at end of file
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialListScrollBenchmark.kt b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialListScrollBenchmark.kt
index 102d4ee..69dc8d6 100644
--- a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialListScrollBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialListScrollBenchmark.kt
@@ -84,8 +84,6 @@
 
         @Parameterized.Parameters(name = "compilation={0}")
         @JvmStatic
-        fun parameters() = createCompilationParams(
-            compilationModes = COMPOSE_COMPILATION_MODES
-        )
+        fun parameters() = createCompilationParams()
     }
 }
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialStartupBenchmark.kt b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialStartupBenchmark.kt
index 94f358c..1a6254b 100644
--- a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialStartupBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialStartupBenchmark.kt
@@ -48,8 +48,6 @@
     companion object {
         @Parameterized.Parameters(name = "startup={0},compilation={1}")
         @JvmStatic
-        fun parameters() = createStartupCompilationParams(
-            compilationModes = COMPOSE_COMPILATION_MODES
-        )
+        fun parameters() = createStartupCompilationParams()
     }
 }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/RadioButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/RadioButton.kt
index 22cb755..ebc9347 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/RadioButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/RadioButton.kt
@@ -91,7 +91,7 @@
                 interactionSource = interactionSource,
                 indication = rememberRipple(
                     bounded = false,
-                    radius = RadioButtonRippleRadius
+                    radius = RadioButtonTokens.StateLayerSize / 2
                 )
             )
         } else {
@@ -240,7 +240,6 @@
 
 private const val RadioAnimationDuration = 100
 
-private val RadioButtonRippleRadius = 24.dp
 private val RadioButtonPadding = 2.dp
 private val RadioButtonDotSize = 12.dp
 private val RadioStrokeWidth = 2.dp
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupLayoutTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupLayoutTest.kt
new file mode 100644
index 0000000..1183a84
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupLayoutTest.kt
@@ -0,0 +1,302 @@
+/*
+ * 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.compose.ui.window
+
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import androidx.compose.runtime.remember
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.layout.AlignmentLine
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.UUID
+
+/**
+ * Tests for the internal [PopupLayout] view used by [Popup].
+ * When adding new tests, consider writing the tests against the [Popup] composable directly first,
+ * since that's the public API, and only adding tests here if the tests need to interact in ways
+ * that aren't easily supported by the compose test APIs.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class PopupLayoutTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun canCalculatePosition_onlyWhenSizeAndCoordinatesAreAvailable() {
+        val layout = createPopupLayout()
+        assertThat(layout.canCalculatePosition).isFalse()
+
+        // Only size available.
+        layout.popupContentSize = IntSize.Zero
+        assertThat(layout.canCalculatePosition).isFalse()
+
+        // Only coordinates available.
+        layout.popupContentSize = null
+        layout.updateParentLayoutCoordinates(NoopLayoutCoordinates)
+        assertThat(layout.canCalculatePosition).isFalse()
+
+        // Everything available.
+        layout.popupContentSize = IntSize.Zero
+        assertThat(layout.canCalculatePosition).isTrue()
+    }
+
+    @Test
+    fun positionUpdated_whenCoordinatesUpdated() {
+        val coordinates = MutableLayoutCoordinates()
+        val layout = createPopupLayout(
+            positionProvider = object : PopupPositionProvider {
+                override fun calculatePosition(
+                    anchorBounds: IntRect,
+                    windowSize: IntSize,
+                    layoutDirection: LayoutDirection,
+                    popupContentSize: IntSize
+                ): IntOffset = anchorBounds.topLeft
+            },
+        )
+        layout.popupContentSize = IntSize.Zero
+
+        assertThat(layout.params.x).isEqualTo(0)
+        assertThat(layout.params.y).isEqualTo(0)
+
+        coordinates.windowOffset = Offset(50f, 50f)
+        layout.updateParentLayoutCoordinates(coordinates)
+
+        assertThat(layout.params.x).isEqualTo(50)
+        assertThat(layout.params.y).isEqualTo(50)
+    }
+
+    @Test
+    fun positionNotUpdated_whenCoordinatesUpdated_withSameParentBounds() {
+        var paramUpdateCount = 0
+        val layout = createPopupLayout(
+            positionProvider = object : PopupPositionProvider {
+                override fun calculatePosition(
+                    anchorBounds: IntRect,
+                    windowSize: IntSize,
+                    layoutDirection: LayoutDirection,
+                    popupContentSize: IntSize
+                ): IntOffset = anchorBounds.topLeft
+            },
+            popupLayoutHelper = object : NoopPopupLayoutHelper() {
+                override fun updateViewLayout(
+                    windowManager: WindowManager,
+                    popupView: View,
+                    params: ViewGroup.LayoutParams
+                ) {
+                    paramUpdateCount++
+                }
+            }
+        )
+
+        // Set size before coordinates to match the order that the compose runtime uses.
+        layout.popupContentSize = IntSize.Zero
+        layout.updateParentLayoutCoordinates(MutableLayoutCoordinates())
+
+        assertThat(paramUpdateCount).isEqualTo(1)
+
+        // Different coordinates object but with the same values, so shouldn't trigger a position
+        // update.
+        layout.updateParentLayoutCoordinates(MutableLayoutCoordinates())
+
+        assertThat(paramUpdateCount).isEqualTo(1)
+    }
+
+    @Test
+    fun positionNotUpdated_onParentBoundsUpdateRequested_withSameParentBounds() {
+        var paramUpdateCount = 0
+        val layout = createPopupLayout(
+            positionProvider = object : PopupPositionProvider {
+                override fun calculatePosition(
+                    anchorBounds: IntRect,
+                    windowSize: IntSize,
+                    layoutDirection: LayoutDirection,
+                    popupContentSize: IntSize
+                ): IntOffset = anchorBounds.topLeft
+            },
+            popupLayoutHelper = object : NoopPopupLayoutHelper() {
+                override fun updateViewLayout(
+                    windowManager: WindowManager,
+                    popupView: View,
+                    params: ViewGroup.LayoutParams
+                ) {
+                    paramUpdateCount++
+                }
+            }
+        )
+
+        // Set size before coordinates to match the order that the compose runtime uses.
+        layout.popupContentSize = IntSize.Zero
+        layout.updateParentLayoutCoordinates(MutableLayoutCoordinates())
+
+        assertThat(paramUpdateCount).isEqualTo(1)
+
+        layout.updateParentBounds()
+
+        assertThat(paramUpdateCount).isEqualTo(1)
+    }
+
+    @Test
+    fun positionUpdated_onParentBoundsUpdateRequested_withDifferentParentBounds() {
+        var paramUpdateCount = 0
+        val coordinates = MutableLayoutCoordinates()
+        val layout = createPopupLayout(
+            positionProvider = object : PopupPositionProvider {
+                override fun calculatePosition(
+                    anchorBounds: IntRect,
+                    windowSize: IntSize,
+                    layoutDirection: LayoutDirection,
+                    popupContentSize: IntSize
+                ): IntOffset = anchorBounds.topLeft
+            },
+            popupLayoutHelper = object : NoopPopupLayoutHelper() {
+                override fun updateViewLayout(
+                    windowManager: WindowManager,
+                    popupView: View,
+                    params: ViewGroup.LayoutParams
+                ) {
+                    paramUpdateCount++
+                }
+            }
+        )
+
+        // Set size before coordinates to match the order that the compose runtime uses.
+        layout.popupContentSize = IntSize.Zero
+        layout.updateParentLayoutCoordinates(coordinates)
+
+        assertThat(layout.params.x).isEqualTo(0)
+        assertThat(layout.params.y).isEqualTo(0)
+
+        coordinates.windowOffset = Offset(50f, 50f)
+        layout.updateParentBounds()
+
+        assertThat(layout.params.x).isEqualTo(50)
+        assertThat(layout.params.y).isEqualTo(50)
+    }
+
+    private fun createPopupLayout(
+        onDismissRequest: (() -> Unit)? = null,
+        properties: PopupProperties = PopupProperties(),
+        density: Density = rule.density,
+        positionProvider: PopupPositionProvider = ZeroPositionProvider,
+        popupLayoutHelper: PopupLayoutHelper = NoopPopupLayoutHelper()
+    ): PopupLayout {
+        lateinit var layout: PopupLayout
+        rule.setContent {
+            val view = LocalView.current
+            remember {
+                PopupLayout(
+                    onDismissRequest = onDismissRequest,
+                    properties = properties,
+                    testTag = "test popup",
+                    composeView = view,
+                    density = density,
+                    initialPositionProvider = positionProvider,
+                    popupId = UUID.randomUUID(),
+                    popupLayoutHelper = popupLayoutHelper
+                ).also { layout = it }
+            }
+        }
+        return rule.runOnIdle { layout }
+    }
+
+    private companion object {
+        val ZeroPositionProvider = object : PopupPositionProvider {
+            override fun calculatePosition(
+                anchorBounds: IntRect,
+                windowSize: IntSize,
+                layoutDirection: LayoutDirection,
+                popupContentSize: IntSize
+            ): IntOffset = IntOffset.Zero
+        }
+
+        val NoopLayoutCoordinates: LayoutCoordinates = MutableLayoutCoordinates()
+
+        /**
+         * An implementation of [LayoutCoordinates] that allows explicitly setting values but only
+         * supports the minimum required subset of operations that [PopupLayout] uses.
+         */
+        private class MutableLayoutCoordinates : LayoutCoordinates {
+            override var size: IntSize = IntSize.Zero
+            override val providedAlignmentLines: Set<AlignmentLine> = emptySet()
+            override var parentLayoutCoordinates: LayoutCoordinates? = null
+            override var parentCoordinates: LayoutCoordinates? = null
+            override var isAttached: Boolean = false
+
+            var windowOffset: Offset = Offset.Zero
+
+            override fun windowToLocal(relativeToWindow: Offset): Offset =
+                relativeToWindow - windowOffset
+
+            override fun localToWindow(relativeToLocal: Offset): Offset =
+                windowOffset + relativeToLocal
+
+            override fun localToRoot(relativeToLocal: Offset): Offset =
+                throw UnsupportedOperationException()
+
+            override fun localPositionOf(
+                sourceCoordinates: LayoutCoordinates,
+                relativeToSource: Offset
+            ): Offset = throw UnsupportedOperationException()
+
+            override fun localBoundingBoxOf(
+                sourceCoordinates: LayoutCoordinates,
+                clipBounds: Boolean
+            ): Rect = throw UnsupportedOperationException()
+
+            override fun get(alignmentLine: AlignmentLine): Int =
+                throw UnsupportedOperationException()
+        }
+
+        private open class NoopPopupLayoutHelper : PopupLayoutHelper {
+            override fun getWindowVisibleDisplayFrame(
+                composeView: View,
+                outRect: android.graphics.Rect
+            ) {
+                // do nothing
+            }
+
+            override fun setGestureExclusionRects(composeView: View, width: Int, height: Int) {
+                // do nothing
+            }
+
+            override fun updateViewLayout(
+                windowManager: WindowManager,
+                popupView: View,
+                params: ViewGroup.LayoutParams
+            ) {
+                // do nothing
+            }
+        }
+    }
+}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
index 0fe17b6..60fbbcc 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
@@ -27,15 +27,17 @@
 import android.view.MotionEvent
 import android.view.View
 import android.view.View.MeasureSpec.makeMeasureSpec
+import android.view.ViewGroup
 import android.view.ViewOutlineProvider
-import android.view.ViewTreeObserver
 import android.view.WindowManager
 import androidx.annotation.RequiresApi
+import androidx.annotation.VisibleForTesting
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionContext
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.compositionLocalOf
 import androidx.compose.runtime.derivedStateOf
@@ -52,6 +54,7 @@
 import androidx.compose.ui.R
 import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.layout.positionInWindow
@@ -72,6 +75,8 @@
 import androidx.lifecycle.ViewTreeLifecycleOwner
 import androidx.lifecycle.ViewTreeViewModelStoreOwner
 import androidx.savedstate.ViewTreeSavedStateRegistryOwner
+import kotlinx.coroutines.android.awaitFrame
+import kotlinx.coroutines.isActive
 import org.jetbrains.annotations.TestOnly
 import java.util.UUID
 import kotlin.math.roundToInt
@@ -281,22 +286,33 @@
         onDispose {}
     }
 
+    // The parent's bounds can change on any frame without onGloballyPositioned being called, if
+    // e.g. the soft keyboard changes visibility. For that reason, we need to check if we've moved
+    // on every frame. However, we don't need to handle all moves – most position changes will be
+    // handled by onGloballyPositioned. This polling loop only needs to handle the case where the
+    // view's absolute position on the screen has changed, so we do a quick check to see if it has,
+    // and only do the other position calculations in that case.
+    LaunchedEffect(popupLayout) {
+        while (isActive) {
+            awaitFrame()
+            popupLayout.pollForLocationOnScreenChange()
+        }
+    }
+
     // TODO(soboleva): Look at module arrangement so that Box can be
     //  used instead of this custom Layout
     // Get the parent's position, size and layout direction
     Layout(
         content = {},
-        modifier = Modifier.onGloballyPositioned { childCoordinates ->
-            val coordinates = childCoordinates.parentLayoutCoordinates!!
-            val layoutSize = coordinates.size
-
-            val position = coordinates.positionInWindow()
-            val layoutPosition = IntOffset(position.x.roundToInt(), position.y.roundToInt())
-
-            popupLayout.parentBounds = IntRect(layoutPosition, layoutSize)
-            // Update the popup's position
-            popupLayout.updatePosition()
-        }
+        modifier = Modifier
+            .onGloballyPositioned { childCoordinates ->
+                // This callback is best-effort – the screen coordinates of this layout node can
+                // change at any time without this callback being fired (e.g. during IME visibility
+                // change). For that reason, updating the position in this callback is not
+                // sufficient, and the coordinates are also re-calculated on every frame.
+                val parentCoordinates = childCoordinates.parentLayoutCoordinates!!
+                popupLayout.updateParentLayoutCoordinates(parentCoordinates)
+            }
     ) { _, _ ->
         popupLayout.parentLayoutDirection = layoutDirection
         layout(0, 0) {}
@@ -353,46 +369,45 @@
  * @param composeView The parent view of the popup which is the AndroidComposeView.
  */
 @SuppressLint("ViewConstructor")
-private class PopupLayout(
+internal class PopupLayout(
     private var onDismissRequest: (() -> Unit)?,
     private var properties: PopupProperties,
     var testTag: String,
     private val composeView: View,
     density: Density,
     initialPositionProvider: PopupPositionProvider,
-    popupId: UUID
+    popupId: UUID,
+    private val popupLayoutHelper: PopupLayoutHelper = if (Build.VERSION.SDK_INT >= 29) {
+        PopupLayoutHelperImpl29()
+    } else {
+        PopupLayoutHelperImpl()
+    }
 ) : AbstractComposeView(composeView.context),
-    ViewRootForInspector,
-    ViewTreeObserver.OnGlobalLayoutListener,
-    View.OnAttachStateChangeListener {
+    ViewRootForInspector {
     private val windowManager =
         composeView.context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
-    private val params = createLayoutParams()
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    internal val params = createLayoutParams()
 
     /** The logic of positioning the popup relative to its parent. */
     var positionProvider = initialPositionProvider
 
     // Position params
     var parentLayoutDirection: LayoutDirection = LayoutDirection.Ltr
-    var parentBounds: IntRect? by mutableStateOf(null)
     var popupContentSize: IntSize? by mutableStateOf(null)
+    private var parentLayoutCoordinates: LayoutCoordinates? by mutableStateOf(null)
+    private var parentBounds: IntRect? = null
 
-    // Track parent bounds and content size; only show popup once we have both
-    val canCalculatePosition by derivedStateOf { parentBounds != null && popupContentSize != null }
+    /** Track parent coordinates and content size; only show popup once we have both. */
+    val canCalculatePosition by derivedStateOf {
+        parentLayoutCoordinates != null && popupContentSize != null
+    }
 
     private val maxSupportedElevation = 30.dp
 
-    private val popupLayoutHelper: PopupLayoutHelper = if (Build.VERSION.SDK_INT >= 29) {
-        PopupLayoutHelperImpl29()
-    } else {
-        PopupLayoutHelperImpl()
-    }
-
     // The window visible frame used for the last popup position calculation.
     private val previousWindowVisibleFrame = Rect()
-    private val tmpWindowVisibleFrame = Rect()
-    // Whether the PopupLayout is currently listening to global layout changes.
-    private var isListeningToGlobalLayout = false
 
     override val subCompositionView: AbstractComposeView get() = this
 
@@ -401,12 +416,6 @@
         ViewTreeLifecycleOwner.set(this, ViewTreeLifecycleOwner.get(composeView))
         ViewTreeViewModelStoreOwner.set(this, ViewTreeViewModelStoreOwner.get(composeView))
         ViewTreeSavedStateRegistryOwner.set(this, ViewTreeSavedStateRegistryOwner.get(composeView))
-        // We are listening to composeView detach changes, in order to remove properly the
-        // global layout listener we add to its viewTreeObserver. We cannot just remove the
-        // listener in during popup's dismiss as this can happen only after the composeView was
-        // detached and no longer references the original viewTreeObserver.
-        composeView.addOnAttachStateChangeListener(this)
-        registerOnGlobalLayoutListener()
         // Set unique id for AbstractComposeView. This allows state restoration for the state
         // defined inside the Popup via rememberSaveable()
         setTag(R.id.compose_view_saveable_id_tag, "Popup:$popupId")
@@ -470,7 +479,7 @@
         val child = getChildAt(0) ?: return
         params.width = child.measuredWidth
         params.height = child.measuredHeight
-        windowManager.updateViewLayout(this, params)
+        popupLayoutHelper.updateViewLayout(windowManager, this, params)
     }
 
     private val displayWidth: Int
@@ -556,7 +565,63 @@
 
     private fun applyNewFlags(flags: Int) {
         params.flags = flags
-        windowManager.updateViewLayout(this, params)
+        popupLayoutHelper.updateViewLayout(windowManager, this, params)
+    }
+
+    /**
+     * Updates the [LayoutCoordinates] object that is used by [updateParentBounds] to calculate
+     * the position of the popup. If the new [LayoutCoordinates] reports new parent bounds, calls
+     * [updatePosition].
+     */
+    fun updateParentLayoutCoordinates(parentLayoutCoordinates: LayoutCoordinates) {
+        this.parentLayoutCoordinates = parentLayoutCoordinates
+        updateParentBounds()
+    }
+
+    /**
+     * Used by [pollForLocationOnScreenChange] to read the [composeView]'s absolute position
+     * on screen. The array is stored as a field instead of allocated in the method because it's
+     * called on every frame.
+     */
+    private val locationOnScreen = IntArray(2)
+
+    /**
+     * Returns true if the absolute location of the [composeView] on the screen has changed since
+     * the last call. This method asks the view for its location instead of using Compose APIs like
+     * [LayoutCoordinates] because it does less work, and this method is intended to be called on
+     * every frame.
+     *
+     * The location can change without any callbacks being fired if, for example, the soft keyboard
+     * is shown or hidden when the window is in `adjustPan` mode. In that case, the window's root
+     * view (`ViewRootImpl`) will "scroll" the view hierarchy in a special way that doesn't fire any
+     * callbacks.
+     */
+    fun pollForLocationOnScreenChange() {
+        val (oldX, oldY) = locationOnScreen
+        composeView.getLocationOnScreen(locationOnScreen)
+        if (oldX != locationOnScreen[0] || oldY != locationOnScreen[1]) {
+            updateParentBounds()
+        }
+    }
+
+    /**
+     * Re-calculates the bounds of the parent layout node that this popup is anchored to. If they've
+     * changed since the last call, calls [updatePosition] to actually calculate the popup's new
+     * position and update the window.
+     */
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    internal fun updateParentBounds() {
+        val coordinates = parentLayoutCoordinates ?: return
+        val layoutSize = coordinates.size
+
+        val position = coordinates.positionInWindow()
+        val layoutPosition = IntOffset(position.x.roundToInt(), position.y.roundToInt())
+
+        val newParentBounds = IntRect(layoutPosition, layoutSize)
+        if (newParentBounds != parentBounds) {
+            this.parentBounds = newParentBounds
+            updatePosition()
+        }
     }
 
     /**
@@ -567,7 +632,7 @@
         val popupContentSize = popupContentSize ?: return
 
         val windowSize = previousWindowVisibleFrame.let {
-            composeView.getWindowVisibleDisplayFrame(it)
+            popupLayoutHelper.getWindowVisibleDisplayFrame(composeView, it)
             val bounds = it.toIntBounds()
             IntSize(width = bounds.width, height = bounds.height)
         }
@@ -588,7 +653,7 @@
             popupLayoutHelper.setGestureExclusionRects(this, windowSize.width, windowSize.height)
         }
 
-        windowManager.updateViewLayout(this, params)
+        popupLayoutHelper.updateViewLayout(windowManager, this, params)
     }
 
     /**
@@ -596,8 +661,6 @@
      */
     fun dismiss() {
         ViewTreeLifecycleOwner.set(this, null)
-        unregisterOnGlobalLayoutListener()
-        composeView.removeOnAttachStateChangeListener(this)
         windowManager.removeViewImmediate(this)
     }
 
@@ -660,7 +723,7 @@
             // Enables us to intercept outside clicks even when popup is not focusable
             flags = flags or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
 
-            type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
+            type = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL
 
             // Get the Window token from the parent view
             token = composeView.applicationWindowToken
@@ -676,51 +739,43 @@
             title = composeView.context.resources.getString(R.string.default_popup_window_title)
         }
     }
-
-    private fun Rect.toIntBounds() = IntRect(
-        left = left,
-        top = top,
-        right = right,
-        bottom = bottom
-    )
-
-    override fun onViewAttachedToWindow(composeView: View) = registerOnGlobalLayoutListener()
-
-    override fun onViewDetachedFromWindow(composeView: View) = unregisterOnGlobalLayoutListener()
-
-    private fun registerOnGlobalLayoutListener() {
-        if (isListeningToGlobalLayout || !composeView.isAttachedToWindow) return
-        composeView.viewTreeObserver.addOnGlobalLayoutListener(this)
-        isListeningToGlobalLayout = true
-    }
-
-    private fun unregisterOnGlobalLayoutListener() {
-        if (!isListeningToGlobalLayout) return
-        composeView.viewTreeObserver.removeOnGlobalLayoutListener(this)
-        isListeningToGlobalLayout = false
-    }
-
-    override fun onGlobalLayout() {
-        // Update the position of the popup, in case getWindowVisibleDisplayFrame has changed.
-        composeView.getWindowVisibleDisplayFrame(tmpWindowVisibleFrame)
-        if (tmpWindowVisibleFrame != previousWindowVisibleFrame) {
-            updatePosition()
-        }
-    }
 }
 
-private interface PopupLayoutHelper {
+/**
+ * Collection of methods delegated to platform methods to support APIs only available on newer
+ * platforms and testing.
+ */
+@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+internal interface PopupLayoutHelper {
+    fun getWindowVisibleDisplayFrame(composeView: View, outRect: Rect)
     fun setGestureExclusionRects(composeView: View, width: Int, height: Int)
+    fun updateViewLayout(
+        windowManager: WindowManager,
+        popupView: View,
+        params: ViewGroup.LayoutParams
+    )
 }
 
-private class PopupLayoutHelperImpl : PopupLayoutHelper {
+private open class PopupLayoutHelperImpl : PopupLayoutHelper {
+    override fun getWindowVisibleDisplayFrame(composeView: View, outRect: Rect) {
+        composeView.getWindowVisibleDisplayFrame(outRect)
+    }
+
     override fun setGestureExclusionRects(composeView: View, width: Int, height: Int) {
         // do nothing
     }
+
+    override fun updateViewLayout(
+        windowManager: WindowManager,
+        popupView: View,
+        params: ViewGroup.LayoutParams
+    ) {
+        windowManager.updateViewLayout(popupView, params)
+    }
 }
 
 @RequiresApi(29)
-private class PopupLayoutHelperImpl29 : PopupLayoutHelper {
+private class PopupLayoutHelperImpl29 : PopupLayoutHelperImpl() {
     override fun setGestureExclusionRects(composeView: View, width: Int, height: Int) {
         composeView.systemGestureExclusionRects = mutableListOf(
             Rect(
@@ -741,6 +796,13 @@
     return false
 }
 
+private fun Rect.toIntBounds() = IntRect(
+    left = left,
+    top = top,
+    right = right,
+    bottom = bottom
+)
+
 /**
  * Returns whether the given view is an underlying decor view of a popup. If the given testTag is
  * supplied it also verifies that the popup has such tag assigned.
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnGloballyPositionedModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnGloballyPositionedModifier.kt
index c636e8c..7fde46e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnGloballyPositionedModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnGloballyPositionedModifier.kt
@@ -27,6 +27,13 @@
  * global position of the content may have changed.
  * Note that it will be called **after** a composition when the coordinates are finalized.
  *
+ * This callback will be invoked at least once when the [LayoutCoordinates] are available, and every
+ * time the element's position changes within the window. However, it is not guaranteed to be
+ * invoked every time the position _relative to the screen_ of the modified element changes. For
+ * example, the system may move the contents inside a window around without firing a callback.
+ * If you are using the [LayoutCoordinates] to calculate position on the screen, and not just inside
+ * the window, you may not receive a callback.
+ *
  * Usage example:
  * @sample androidx.compose.ui.samples.OnGloballyPositioned
  */
diff --git a/core/core-performance/build.gradle b/core/core-performance/build.gradle
index 8b5f982..67c0305 100644
--- a/core/core-performance/build.gradle
+++ b/core/core-performance/build.gradle
@@ -26,7 +26,9 @@
 
 dependencies {
     api(libs.kotlinStdlib)
-    // Add dependencies here
+    testImplementation(libs.kotlinStdlib)
+    testImplementation(libs.junit)
+    testImplementation(libs.truth)
 }
 
 androidx {
diff --git a/core/core-performance/src/main/kotlin/androidx/core/performance/PerformanceClass.kt b/core/core-performance/src/main/kotlin/androidx/core/performance/PerformanceClass.kt
new file mode 100644
index 0000000..a3a849c
--- /dev/null
+++ b/core/core-performance/src/main/kotlin/androidx/core/performance/PerformanceClass.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.core.performance
+
+/**
+ * Reports the media performance class of the device.
+ */
+class PerformanceClass {
+
+    /**
+     * The media performance class of the device or 0 if none.
+     * <p>
+     * If this value is not <code>0</code>, the device conforms to the media performance class
+     * definition of the SDK version of this value. This value never changes while a device is
+     * booted, but it may increase when the hardware manufacturer provides an OTA update.
+     * <p>
+     * Possible non-zero values are defined in {@link Build.VERSION_CODES} starting with
+     * {@link Build.VERSION_CODES#R}.
+     * <p>
+     * Defaults to {@link Build.MEDIA_PERFORMANCE_CLASS}
+     */
+    fun getPerformanceClass(): Int {
+        return 0
+    }
+}
diff --git a/core/core-performance/src/test/kotlin/androidx/core/performance/PerformanceClassTest.kt b/core/core-performance/src/test/kotlin/androidx/core/performance/PerformanceClassTest.kt
new file mode 100644
index 0000000..294d3e9
--- /dev/null
+++ b/core/core-performance/src/test/kotlin/androidx/core/performance/PerformanceClassTest.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.core.performance
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/** Unit tests for [PerformanceClass]. */
+@RunWith(JUnit4::class)
+class PerformanceClassTest {
+
+    private val pc = PerformanceClass()
+
+    @Test
+    fun getPerformanceClass() {
+        assertThat(pc.getPerformanceClass()).isEqualTo(0)
+    }
+}
\ No newline at end of file
diff --git a/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java b/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java
index b27dd32..3c5ea44 100644
--- a/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java
@@ -465,6 +465,7 @@
         assertMockAccessibilityDelegateWorkingOnView(mockDelegate);
     }
 
+    @FlakyTest(bugId = 206644987)
     @Test
     @SdkSuppress(minSdkVersion = 19)
     public void testSetAccessibilityPaneTitle_sendsOutCorrectEvent() throws TimeoutException {
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/PartiallyCoveredActivityTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/PartiallyCoveredActivityTest.java
index 77aca19..9c1aa32 100644
--- a/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/PartiallyCoveredActivityTest.java
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/PartiallyCoveredActivityTest.java
@@ -46,6 +46,7 @@
 import androidx.lifecycle.testapp.NavigationDialogActivity;
 import androidx.lifecycle.testapp.TestEvent;
 import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -126,6 +127,7 @@
         runTest(activity);
     }
 
+    @FlakyTest(bugId = 206645367)
     @Test
     public void coveredWithDialog_fragment() throws Throwable {
         CollectingSupportFragment fragment = new CollectingSupportFragment();
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCallbackTest.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCallbackTest.java
index 8f859b1..8a7aaf3 100644
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCallbackTest.java
+++ b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCallbackTest.java
@@ -62,6 +62,7 @@
 import androidx.media2.test.client.RemoteMediaSession;
 import androidx.media2.test.common.TestUtils;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 
 import org.junit.After;
@@ -79,6 +80,7 @@
 /**
  * Tests {@link MediaController.ControllerCallback}.
  */
+@FlakyTest(bugId = 202942942)
 @RunWith(AndroidJUnit4.class)
 @LargeTest
 public class MediaControllerCallbackTest extends MediaSessionTestBase {
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java
index 30bad23..c86c41e 100644
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java
+++ b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java
@@ -64,6 +64,7 @@
 /**
  * Tests for {@link MediaControllerCompat.Callback} with {@link MediaSession}.
  */
+@FlakyTest(bugId = 202942942)
 @RunWith(AndroidJUnit4.class)
 @LargeTest
 public class MediaControllerCompatCallbackWithMediaSessionTest extends MediaSessionTestBase {
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerTest.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerTest.java
index 845c0e3..7002170 100644
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerTest.java
+++ b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerTest.java
@@ -60,6 +60,7 @@
 import androidx.media2.test.common.PollingCheck;
 import androidx.media2.test.common.TestUtils;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 
 import org.junit.After;
@@ -77,6 +78,7 @@
 /**
  * Tests {@link MediaController}.
  */
+@FlakyTest(bugId = 202942942)
 @RunWith(AndroidJUnit4.class)
 @LargeTest
 public class MediaControllerTest extends MediaSessionTestBase {
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java
index 236caaa..d388a0b 100644
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java
+++ b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java
@@ -63,6 +63,7 @@
 import androidx.media2.test.service.MockRemotePlayer;
 import androidx.media2.test.service.RemoteMediaControllerCompat;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 
 import org.junit.After;
@@ -78,6 +79,7 @@
 /**
  * Tests {@link SessionCallback} working with {@link MediaControllerCompat}.
  */
+@FlakyTest(bugId = 202942942)
 @RunWith(AndroidJUnit4.class)
 @LargeTest
 public class MediaSessionCallbackWithMediaControllerCompatTest extends MediaSessionTestBase {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XAnnotated.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XAnnotated.kt
index 9ad903b..d24da07 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XAnnotated.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XAnnotated.kt
@@ -149,6 +149,17 @@
     }
 
     /**
+     * Returns the [Annotation]s that are annotated with [annotationName]
+     */
+    fun getAnnotationsAnnotatedWith(
+        annotationName: ClassName
+    ): Set<XAnnotation> {
+        return getAllAnnotations().filter {
+            it.type.typeElement?.hasAnnotation(annotationName) ?: false
+        }.toSet()
+    }
+
+    /**
      * Returns the [XAnnotation] that has the same qualified name as [annotationName].
      *
      * @see [hasAnnotation]
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
index de97611..5503384 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
@@ -119,6 +119,63 @@
     }
 
     @Test
+    fun getAnnotationsAnnotatedWith() {
+        val source = Source.kotlin(
+            "MyClass.kt",
+            """
+            package foo.bar
+
+            @Target(AnnotationTarget.ANNOTATION_CLASS)
+            @Retention(AnnotationRetention.SOURCE)
+            annotation class SourceAnnotation
+
+            @Target(AnnotationTarget.ANNOTATION_CLASS)
+            @Retention(AnnotationRetention.BINARY)
+            annotation class BinaryAnnotation
+
+            @Target(AnnotationTarget.ANNOTATION_CLASS)
+            @Retention(AnnotationRetention.RUNTIME)
+            annotation class RuntimeAnnotation
+
+            @SourceAnnotation
+            @BinaryAnnotation
+            @RuntimeAnnotation
+            @Target(AnnotationTarget.CLASS)
+            @Retention(AnnotationRetention.RUNTIME)
+            annotation class Foo
+
+            @Foo
+            class MyClass
+            """.trimIndent()
+        )
+        runTest(
+            sources = listOf(source),
+        ) { invocation ->
+            val element = invocation.processingEnv.requireTypeElement("foo.bar.MyClass")
+
+            val annotationsForAnnotations = if (preCompiled) {
+                // Source level annotations are gone if it's pre-compiled
+                listOf("BinaryAnnotation", "RuntimeAnnotation")
+            } else {
+                listOf("SourceAnnotation", "BinaryAnnotation", "RuntimeAnnotation")
+            }
+
+            annotationsForAnnotations.forEach {
+                val annotations = element.getAnnotationsAnnotatedWith(
+                    ClassName.get("foo.bar", it))
+                assertThat(annotations).hasSize(1)
+                val annotation = annotations.first()
+                assertThat(annotation.name)
+                    .isEqualTo("Foo")
+                assertThat(annotation.qualifiedName)
+                    .isEqualTo("foo.bar.Foo")
+                assertThat(annotation.type.typeElement)
+                    .isEqualTo(invocation.processingEnv.requireTypeElement("foo.bar.Foo"))
+            }
+        }
+    }
+
+    @Test
     fun annotationsInClassPathCanBeBoxed() {
         val source = Source.kotlin(
             "MyClass.kt",
diff --git a/savedstate/savedstate/api/current.txt b/savedstate/savedstate/api/current.txt
index aac862a..71beea9 100644
--- a/savedstate/savedstate/api/current.txt
+++ b/savedstate/savedstate/api/current.txt
@@ -20,6 +20,7 @@
   public final class SavedStateRegistryController {
     method public static androidx.savedstate.SavedStateRegistryController create(androidx.savedstate.SavedStateRegistryOwner);
     method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+    method @MainThread public void performAttach();
     method @MainThread public void performRestore(android.os.Bundle?);
     method @MainThread public void performSave(android.os.Bundle);
   }
diff --git a/savedstate/savedstate/api/public_plus_experimental_current.txt b/savedstate/savedstate/api/public_plus_experimental_current.txt
index aac862a..71beea9 100644
--- a/savedstate/savedstate/api/public_plus_experimental_current.txt
+++ b/savedstate/savedstate/api/public_plus_experimental_current.txt
@@ -20,6 +20,7 @@
   public final class SavedStateRegistryController {
     method public static androidx.savedstate.SavedStateRegistryController create(androidx.savedstate.SavedStateRegistryOwner);
     method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+    method @MainThread public void performAttach();
     method @MainThread public void performRestore(android.os.Bundle?);
     method @MainThread public void performSave(android.os.Bundle);
   }
diff --git a/savedstate/savedstate/api/restricted_current.txt b/savedstate/savedstate/api/restricted_current.txt
index aac862a..71beea9 100644
--- a/savedstate/savedstate/api/restricted_current.txt
+++ b/savedstate/savedstate/api/restricted_current.txt
@@ -20,6 +20,7 @@
   public final class SavedStateRegistryController {
     method public static androidx.savedstate.SavedStateRegistryController create(androidx.savedstate.SavedStateRegistryOwner);
     method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+    method @MainThread public void performAttach();
     method @MainThread public void performRestore(android.os.Bundle?);
     method @MainThread public void performSave(android.os.Bundle);
   }
diff --git a/savedstate/savedstate/src/androidTest/java/androidx/savedstate/SavedStateRegistryTest.kt b/savedstate/savedstate/src/androidTest/java/androidx/savedstate/SavedStateRegistryTest.kt
index 368ee9a..702bb98 100644
--- a/savedstate/savedstate/src/androidTest/java/androidx/savedstate/SavedStateRegistryTest.kt
+++ b/savedstate/savedstate/src/androidTest/java/androidx/savedstate/SavedStateRegistryTest.kt
@@ -18,6 +18,7 @@
 
 import android.os.Bundle
 import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
 import androidx.lifecycle.LifecycleRegistry
 import androidx.test.annotation.UiThreadTest
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -169,6 +170,26 @@
         owner.savedStateRegistry.runOnNextRecreation(ToBeRecreated::class.java)
     }
 
+    @UiThreadTest
+    @Test
+    fun runOnNextRecreationFromEarlyRegisteredObserver() {
+        val owner = FakeSavedStateRegistryOwner()
+        owner.savedStateRegistryController.performAttach()
+        // shouldn't throw, though we aren't even created
+        owner.lifecycle.addObserver(
+            LifecycleEventObserver { _, event ->
+                if (event == Lifecycle.Event.ON_START)
+                    owner.savedStateRegistry.runOnNextRecreation(ToBeRecreated::class.java)
+            }
+        )
+        owner.savedStateRegistryController.performRestore(null)
+        owner.lifecycleRegistry.currentState = Lifecycle.State.STARTED
+        // now ON_STOP event will be sent
+        owner.lifecycleRegistry.currentState = Lifecycle.State.CREATED
+        // now ON_START event will be sent again, previously registered observer shouldn't throw
+        owner.lifecycleRegistry.currentState = Lifecycle.State.STARTED
+    }
+
     private class TestFlow(val lastState: Bundle?) {
         fun recreate(block: (FakeSavedStateRegistryOwner) -> Unit): TestFlow {
             val fakeOwner = FakeSavedStateRegistryOwner()
diff --git a/savedstate/savedstate/src/main/java/androidx/savedstate/Recreator.java b/savedstate/savedstate/src/main/java/androidx/savedstate/Recreator.java
index 638eec2..33647d3 100644
--- a/savedstate/savedstate/src/main/java/androidx/savedstate/Recreator.java
+++ b/savedstate/savedstate/src/main/java/androidx/savedstate/Recreator.java
@@ -17,7 +17,6 @@
 package androidx.savedstate;
 
 
-import android.annotation.SuppressLint;
 import android.os.Bundle;
 
 import androidx.annotation.NonNull;
@@ -31,7 +30,6 @@
 import java.util.HashSet;
 import java.util.Set;
 
-@SuppressLint("RestrictedApi")
 final class Recreator implements LifecycleEventObserver {
 
     static final String CLASSES_KEY = "classes_to_restore";
diff --git a/savedstate/savedstate/src/main/java/androidx/savedstate/SavedStateRegistry.java b/savedstate/savedstate/src/main/java/androidx/savedstate/SavedStateRegistry.java
index 68bdb26..a8a4ef2 100644
--- a/savedstate/savedstate/src/main/java/androidx/savedstate/SavedStateRegistry.java
+++ b/savedstate/savedstate/src/main/java/androidx/savedstate/SavedStateRegistry.java
@@ -42,6 +42,7 @@
 
     private final SafeIterableMap<String, SavedStateProvider> mComponents =
             new SafeIterableMap<>();
+    private boolean mAttached;
     @Nullable
     private Bundle mRestoredState;
     private boolean mRestored;
@@ -180,17 +181,13 @@
     }
 
     /**
-     * An interface for an owner of this @{code {@link SavedStateRegistry} to restore saved state.
-     *
+     * An interface for an owner of this @{code {@link SavedStateRegistry} to attach this
+     * to a {@link Lifecycle}.
      */
-    @SuppressWarnings("WeakerAccess")
     @MainThread
-    void performRestore(@NonNull Lifecycle lifecycle, @Nullable Bundle savedState) {
-        if (mRestored) {
-            throw new IllegalStateException("SavedStateRegistry was already restored.");
-        }
-        if (savedState != null) {
-            mRestoredState = savedState.getBundle(SAVED_COMPONENTS_KEY);
+    void performAttach(@NonNull Lifecycle lifecycle) {
+        if (mAttached) {
+            throw new IllegalStateException("SavedStateRegistry was already attached.");
         }
 
         lifecycle.addObserver((LifecycleEventObserver) (source, event) -> {
@@ -201,6 +198,26 @@
             }
         });
 
+        mAttached = true;
+    }
+
+    /**
+     * An interface for an owner of this @{code {@link SavedStateRegistry} to restore saved state.
+     *
+     */
+    @MainThread
+    void performRestore(@Nullable Bundle savedState) {
+        if (!mAttached) {
+            throw new IllegalStateException("You must call performAttach() before calling "
+                    + "performRestore(Bundle).");
+        }
+        if (mRestored) {
+            throw new IllegalStateException("SavedStateRegistry was already restored.");
+        }
+        if (savedState != null) {
+            mRestoredState = savedState.getBundle(SAVED_COMPONENTS_KEY);
+        }
+
         mRestored = true;
     }
 
diff --git a/savedstate/savedstate/src/main/java/androidx/savedstate/SavedStateRegistryController.java b/savedstate/savedstate/src/main/java/androidx/savedstate/SavedStateRegistryController.java
index 6fb25d2..98fe68f 100644
--- a/savedstate/savedstate/src/main/java/androidx/savedstate/SavedStateRegistryController.java
+++ b/savedstate/savedstate/src/main/java/androidx/savedstate/SavedStateRegistryController.java
@@ -33,6 +33,8 @@
     private final SavedStateRegistryOwner mOwner;
     private final SavedStateRegistry mRegistry;
 
+    private boolean mAttached = false;
+
     private SavedStateRegistryController(SavedStateRegistryOwner owner) {
         mOwner = owner;
         mRegistry = new SavedStateRegistry();
@@ -47,19 +49,42 @@
     }
 
     /**
-     * An interface for an owner of this {@link SavedStateRegistry} to restore saved state.
-     *
-     * @param savedState restored state
+     * Perform the initial, one time attachment necessary to configure this
+     * {@link SavedStateRegistry}. This must be called when the owner's {@link Lifecycle} is
+     * {@link Lifecycle.State#INITIALIZED} and before you call
+     * {@link #performRestore(Bundle)}.
      */
     @MainThread
-    public void performRestore(@Nullable Bundle savedState) {
+    public void performAttach() {
         Lifecycle lifecycle = mOwner.getLifecycle();
         if (lifecycle.getCurrentState() != Lifecycle.State.INITIALIZED) {
             throw new IllegalStateException("Restarter must be created only during "
                     + "owner's initialization stage");
         }
         lifecycle.addObserver(new Recreator(mOwner));
-        mRegistry.performRestore(lifecycle, savedState);
+        mRegistry.performAttach(lifecycle);
+
+        mAttached = true;
+    }
+
+    /**
+     * An interface for an owner of this {@link SavedStateRegistry} to restore saved state.
+     *
+     * @param savedState restored state
+     */
+    @MainThread
+    public void performRestore(@Nullable Bundle savedState) {
+        // To support backward compatibility with libraries that do not explicitly
+        // call performAttach(), we make sure that work is done here
+        if (!mAttached) {
+            performAttach();
+        }
+        Lifecycle lifecycle = mOwner.getLifecycle();
+        if (lifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
+            throw new IllegalStateException("performRestore cannot be called when owner "
+                    + " is " + lifecycle.getCurrentState());
+        }
+        mRegistry.performRestore(savedState);
     }
 
     /**
diff --git a/testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt b/testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt
index 7f59172..ad1ce6b 100644
--- a/testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt
+++ b/testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt
@@ -27,6 +27,16 @@
 import androidx.benchmark.macro.junit4.MacrobenchmarkRule
 
 /**
+ * Default compilation modes to test for all AndroidX macrobenchmarks.
+ */
+val COMPILATION_MODES = listOf(
+    CompilationMode.None,
+    CompilationMode.Interpreted,
+    CompilationMode.BaselineProfile,
+    CompilationMode.SpeedProfile()
+)
+
+/**
  * Temporary, while transitioning to new metrics
  */
 @RequiresApi(23)
@@ -64,11 +74,7 @@
         StartupMode.WARM,
         StartupMode.COLD
     ),
-    compilationModes: List<CompilationMode> = listOf(
-        CompilationMode.None,
-        CompilationMode.Interpreted,
-        CompilationMode.SpeedProfile()
-    )
+    compilationModes: List<CompilationMode> = COMPILATION_MODES
 ): List<Array<Any>> = mutableListOf<Array<Any>>().apply {
     for (startupMode in startupModes) {
         for (compilationMode in compilationModes) {
@@ -83,11 +89,7 @@
 
 @RequiresApi(21)
 fun createCompilationParams(
-    compilationModes: List<CompilationMode> = listOf(
-        CompilationMode.None,
-        CompilationMode.Interpreted,
-        CompilationMode.SpeedProfile()
-    )
+    compilationModes: List<CompilationMode> = COMPILATION_MODES
 ): List<Array<Any>> = mutableListOf<Array<Any>>().apply {
     for (compilationMode in compilationModes) {
         // Skip configs that can't run, so they don't clutter Studio benchmark
diff --git a/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/Common.kt b/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/Common.kt
index f2d6b09..b60c1cc 100644
--- a/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/Common.kt
+++ b/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/Common.kt
@@ -16,13 +16,4 @@
 
 package androidx.wear.compose.integration.macrobenchmark.test
 
-import androidx.benchmark.macro.CompilationMode
-
-internal val COMPOSE_COMPILATION_MODES = listOf(
-    CompilationMode.None,
-    CompilationMode.Interpreted,
-    CompilationMode.SpeedProfile(),
-    CompilationMode.BaselineProfile,
-)
-
 internal val CONTENT_DESCRIPTION = "find-me"
\ No newline at end of file
diff --git a/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/ScrollBenchmark.kt b/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/ScrollBenchmark.kt
index d547f80..453ae52 100644
--- a/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/ScrollBenchmark.kt
+++ b/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/ScrollBenchmark.kt
@@ -79,8 +79,6 @@
 
         @Parameterized.Parameters(name = "compilation={0}")
         @JvmStatic
-        fun parameters() = createCompilationParams(
-            compilationModes = COMPOSE_COMPILATION_MODES
-        )
+        fun parameters() = createCompilationParams()
     }
 }
\ No newline at end of file
diff --git a/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/StartupBenchmark.kt b/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/StartupBenchmark.kt
index 967cc01..0b8754f 100644
--- a/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/StartupBenchmark.kt
+++ b/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/StartupBenchmark.kt
@@ -48,13 +48,6 @@
     companion object {
         @Parameterized.Parameters(name = "startup={0},compilation={1}")
         @JvmStatic
-        fun parameters() = createStartupCompilationParams(
-            compilationModes = listOf(
-                CompilationMode.None,
-                CompilationMode.Interpreted,
-                CompilationMode.SpeedProfile(),
-                CompilationMode.BaselineProfile,
-            )
-        )
+        fun parameters() = createStartupCompilationParams()
     }
 }
\ No newline at end of file
diff --git a/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/SwipeBenchmark.kt b/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/SwipeBenchmark.kt
index 03738ed..1721bec 100644
--- a/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/SwipeBenchmark.kt
+++ b/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/SwipeBenchmark.kt
@@ -79,8 +79,6 @@
 
         @Parameterized.Parameters(name = "compilation={0}")
         @JvmStatic
-        fun parameters() = createCompilationParams(
-            compilationModes = COMPOSE_COMPILATION_MODES
-        )
+        fun parameters() = createCompilationParams()
     }
 }
\ No newline at end of file
diff --git a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
index 435cb4e..552c556 100644
--- a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
+++ b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
@@ -32,6 +32,7 @@
 import android.view.SurfaceHolder
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.MediumTest
 import androidx.test.screenshot.AndroidXScreenshotTestRule
 import androidx.test.screenshot.assertAgainstGolden
@@ -676,6 +677,7 @@
         assertThat(awaitWithTimeout(deferredInteractiveInstance2).instanceId).isEqualTo("testId")
     }
 
+    @FlakyTest(bugId = 206646906)
     @SuppressLint("NewApi") // renderWatchFaceToBitmap
     @Test
     fun getOrCreateInteractiveWatchFaceClient_existingOpenInstance_styleChange() {
diff --git a/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceServiceImageTest.kt b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceServiceImageTest.kt
index 8bd3a06..b926b8c 100644
--- a/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceServiceImageTest.kt
+++ b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceServiceImageTest.kt
@@ -563,6 +563,7 @@
         )
     }
 
+    @FlakyTest(bugId = 206648285)
     @SuppressLint("NewApi")
     @Test
     public fun testCommandTakeOpenGLScreenShot() {
@@ -715,6 +716,7 @@
         )
     }
 
+    @FlakyTest(bugId = 206647510)
     @SuppressLint("NewApi")
     @Test
     public fun testHighlightRightComplicationInScreenshot() {
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewRenderProcessClientTest.java b/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewRenderProcessClientTest.java
index 1a47757..bc4ffb3 100644
--- a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewRenderProcessClientTest.java
+++ b/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewRenderProcessClientTest.java
@@ -37,7 +37,7 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
-@FlakyTest(bugId = 204342476)
+@FlakyTest(bugId = 204197604)
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class WebViewRenderProcessClientTest {
diff --git a/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java b/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java
index ef51631..6ac25bd 100644
--- a/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java
+++ b/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java
@@ -47,7 +47,6 @@
      * device, {@code null} otherwise. The implementation must match the API level reported in
      * {@link WindowExtensions}.
      * @return the OEM implementation of {@link WindowLayoutComponent}
-     * @throws UnsupportedOperationException if the device does not support
      */
     @Nullable
     WindowLayoutComponent getWindowLayoutComponent();
diff --git a/window/window/src/androidTest/java/androidx/window/layout/SafeWindowLayoutComponentProviderTest.kt b/window/window/src/androidTest/java/androidx/window/layout/SafeWindowLayoutComponentProviderTest.kt
new file mode 100644
index 0000000..bfa5b7f
--- /dev/null
+++ b/window/window/src/androidTest/java/androidx/window/layout/SafeWindowLayoutComponentProviderTest.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.window.layout
+
+import androidx.window.extensions.WindowExtensionsProvider
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Test
+
+/**
+ * An integration test to verify that if [WindowExtensionsProvider] is present then
+ * [SafeWindowLayoutComponentProvider.windowLayoutComponent] will return a value. This can fail if
+ * the implementation of window:extensions:extensions does not have the expected API.
+ */
+class SafeWindowLayoutComponentProviderTest {
+
+    /**
+     * Test that if [WindowExtensionsProvider] is available then
+     * [SafeWindowLayoutComponentProvider.windowLayoutComponent] returns a non-null value.
+     */
+    @Test
+    fun windowLayoutComponentIsAvailable_ifProviderIsAvailable() {
+        val safeComponent = SafeWindowLayoutComponentProvider.windowLayoutComponent
+
+        try {
+            val extensions = WindowExtensionsProvider.getWindowExtensions()
+            val actualComponent = extensions.windowLayoutComponent
+            if (actualComponent == null) {
+                assertNull(safeComponent)
+            } else {
+                assertNotNull(safeComponent)
+            }
+        } catch (e: UnsupportedOperationException) {
+            // Invalid implementation of extensions
+            assertNull(safeComponent)
+        }
+    }
+}
\ No newline at end of file
diff --git a/window/window/src/androidTest/java/androidx/window/layout/SidecarCompatDeviceTest.kt b/window/window/src/androidTest/java/androidx/window/layout/SidecarCompatDeviceTest.kt
index 5909aaa..1657c65 100644
--- a/window/window/src/androidTest/java/androidx/window/layout/SidecarCompatDeviceTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/layout/SidecarCompatDeviceTest.kt
@@ -18,7 +18,6 @@
 
 package androidx.window.layout
 
-import android.content.Context
 import android.content.pm.ActivityInfo
 import androidx.test.core.app.ActivityScenario
 import androidx.test.core.app.ApplicationProvider
@@ -26,6 +25,7 @@
 import androidx.test.filters.LargeTest
 import androidx.window.TestConfigChangeHandlingActivity
 import androidx.window.WindowTestBase
+import androidx.window.core.SpecificationComputer.VerificationMode.QUIET
 import androidx.window.core.Version
 import androidx.window.layout.ExtensionInterfaceCompat.ExtensionCallbackInterface
 import androidx.window.layout.HardwareFoldingFeature.Type
@@ -63,7 +63,9 @@
     @Before
     public fun setUp() {
         assumeValidSidecar()
-        sidecarCompat = SidecarCompat(ApplicationProvider.getApplicationContext() as Context)
+        val sidecar = SidecarCompat.getSidecarCompat(ApplicationProvider.getApplicationContext())
+        // TODO(b/206055949) convert to strict validation.
+        sidecarCompat = SidecarCompat(sidecar, SidecarAdapter(verificationMode = QUIET))
     }
 
     @Test
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/BenchmarkingUtils.kt b/window/window/src/debug/java/androidx/window/core/BuildConfig.kt
similarity index 60%
rename from compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/BenchmarkingUtils.kt
rename to window/window/src/debug/java/androidx/window/core/BuildConfig.kt
index 253df9c..6dfe4b0 100644
--- a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/BenchmarkingUtils.kt
+++ b/window/window/src/debug/java/androidx/window/core/BuildConfig.kt
@@ -14,13 +14,15 @@
  * limitations under the License.
  */
 
-package androidx.compose.integration.macrobenchmark
+package androidx.window.core
 
-import androidx.benchmark.macro.CompilationMode
+import androidx.window.core.SpecificationComputer.VerificationMode
+import androidx.window.core.SpecificationComputer.VerificationMode.STRICT
 
-internal val COMPOSE_COMPILATION_MODES = listOf(
-    CompilationMode.None,
-    CompilationMode.Interpreted,
-    CompilationMode.SpeedProfile(),
-    CompilationMode.BaselineProfile,
-)
\ No newline at end of file
+/**
+ * A configuration to be used for the debug flavor of the library. Default [VerificationMode] is
+ * [STRICT] so to surface errors.
+ */
+internal object BuildConfig {
+    val verificationMode: VerificationMode = STRICT
+}
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/core/SpecificationComputer.kt b/window/window/src/main/java/androidx/window/core/SpecificationComputer.kt
new file mode 100644
index 0000000..70b7623
--- /dev/null
+++ b/window/window/src/main/java/androidx/window/core/SpecificationComputer.kt
@@ -0,0 +1,181 @@
+/*
+ * 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.window.core
+
+import android.util.Log
+import androidx.window.core.SpecificationComputer.VerificationMode.LOG
+import androidx.window.core.SpecificationComputer.VerificationMode.QUIET
+import androidx.window.core.SpecificationComputer.VerificationMode.STRICT
+
+/**
+ * An [Exception] to signal that strict mode has been violated. This class should only be used
+ * within the [SpecificationComputer]
+ */
+internal class WindowStrictModeException(message: String) : Exception(message)
+
+/**
+ * The root abstract class to represent a [SpecificationComputer]. A [SpecificationComputer] can be
+ * used to validate assumptions about objects and interfaces when working with OEM provided
+ * libraries. The [SpecificationComputer] has three verification modes, [STRICT], [LOG], and
+ * [QUIET]. For [STRICT] mode, the [SpecificationComputer] will throw an exception when a required
+ * condition is not met. For [LOG] mode, the [SpecificationComputer] will create a debug log when a
+ * required condition is not met. For [QUIET] mode, value is simply converted to null if the
+ * specification is not met.
+ */
+internal abstract class SpecificationComputer<T : Any> {
+    /**
+     * An enum to specify which [VerificationMode] should be used with the [SpecificationComputer]
+     */
+    enum class VerificationMode {
+        STRICT,
+        LOG,
+        QUIET
+    }
+
+    /**
+     * Checks if the required condition is met and returns a [SpecificationComputer] based on the
+     * result. If the condition is met then the [SpecificationComputer] that is returned will
+     * continue to process additional checks. If the condition fails then the
+     * [SpecificationComputer] will have different behavior depending on the verification mode. If
+     * the verification mode is [STRICT] an [Exception] will be thrown when [compute] is
+     * called. If the verification mode is [LOG] a debug log will be written. If the verification
+     * mode is [QUIET] then an empty [SpecificationComputer] is returned.
+     *
+     * Use [require] when an assumption must be true. One key example is when translating from an
+     * OEM provided library into a local object you want to verify the attributes. If an attribute
+     * does not match expectations then this can raise an exception at the source.
+     *
+     * One use case for this is our integration tests for devices. We can write good assumptions in
+     * a test because we would need external knowledge of the device. One example is a device with a
+     * fold. We expect the fold to propagate through the translation layer but we can only assert
+     * the presence of a fold if we know the device has a fold. As a substitution we can turn on
+     * strict mode and this will throw an exception when translating causing the test to fail.
+     *
+     * @param message a description of what the condition is testing. This should be user readable
+     * @param condition a test that the current value is expected to pass.
+     * @return A [SpecificationComputer] that will continue computing if the condition was met or
+     * skip subsequent checks if the condition was not met.
+     */
+    abstract fun require(message: String, condition: T.() -> Boolean): SpecificationComputer<T>
+
+    /**
+     * Computes the result of the previous checks and the returned value depends on the
+     * [VerificationMode]. For [STRICT] mode an [Exception] may be thrown if the condition was
+     * required. For [LOG] mode a debug log is written to the [Logger]. For [QUIET] mode there are
+     * no side effects.
+     * @return The value if all the checks passed or none of the checks were required, null
+     * otherwise.
+     * @throws WindowStrictModeException if [STRICT] mode is set and a required check fails.
+     */
+    abstract fun compute(): T?
+
+    protected fun createMessage(value: Any, message: String): String {
+        return "$message value: $value"
+    }
+
+    companion object {
+        /**
+         * Start a specification for the receiver.
+         *
+         * @param T a non-null value that the specification will run against.
+         * @param tag an identifier that will be used when writing debug logs.
+         * @param verificationMode determines if the checks should throw, log, or fail silently.
+         * @param logger a [Logger] that can be substituted for testing purposes. The default
+         * value writes a log to LogCat
+         * @return A [SpecificationComputer] that is initially valid and can check other
+         * conditions.
+         */
+        fun <T : Any> T.startSpecification(
+            tag: String,
+            verificationMode: VerificationMode = BuildConfig.verificationMode,
+            logger: Logger = AndroidLogger
+        ): SpecificationComputer<T> {
+            return ValidSpecification(this, tag, verificationMode, logger)
+        }
+    }
+}
+
+/**
+ * A [SpecificationComputer] where the value has passed all previous checks.
+ */
+private class ValidSpecification<T : Any>(
+    val value: T,
+    val tag: String,
+    val verificationMode: VerificationMode,
+    val logger: Logger
+) : SpecificationComputer<T>() {
+
+    override fun require(message: String, condition: T.() -> Boolean): SpecificationComputer<T> {
+        return if (condition(value)) {
+            this
+        } else {
+            FailedSpecification(
+                value = value,
+                tag = tag,
+                message = message,
+                logger = logger,
+                verificationMode = verificationMode
+            )
+        }
+    }
+
+    override fun compute(): T {
+        return value
+    }
+}
+
+/**
+ * A [SpecificationComputer] that has failed a required check
+ */
+private class FailedSpecification<T : Any>(
+    val value: T,
+    val tag: String,
+    val message: String,
+    val logger: Logger,
+    val verificationMode: VerificationMode
+) : SpecificationComputer<T>() {
+
+    val exception: WindowStrictModeException =
+        WindowStrictModeException(createMessage(value, message)).apply {
+            stackTrace = stackTrace.drop(2).toTypedArray()
+        }
+
+    override fun require(message: String, condition: T.() -> Boolean): SpecificationComputer<T> {
+        return this
+    }
+
+    override fun compute(): T? {
+        return when (verificationMode) {
+            STRICT -> throw exception
+            LOG -> {
+                logger.debug(tag, createMessage(value, message))
+                null
+            }
+            QUIET -> null
+        }
+    }
+}
+
+internal interface Logger {
+    fun debug(tag: String, message: String)
+}
+
+internal object AndroidLogger : Logger {
+    override fun debug(tag: String, message: String) {
+        Log.d(tag, message)
+    }
+}
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/layout/SafeWindowLayoutComponentProvider.kt b/window/window/src/main/java/androidx/window/layout/SafeWindowLayoutComponentProvider.kt
new file mode 100644
index 0000000..6785ecf
--- /dev/null
+++ b/window/window/src/main/java/androidx/window/layout/SafeWindowLayoutComponentProvider.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.window.layout
+
+import android.app.Activity
+import android.graphics.Rect
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.window.extensions.WindowExtensionsProvider
+import androidx.window.extensions.layout.WindowLayoutComponent
+import java.lang.reflect.Method
+import java.lang.reflect.Modifier
+import java.util.function.Consumer
+import kotlin.reflect.KClass
+
+internal object SafeWindowLayoutComponentProvider {
+
+    val windowLayoutComponent: WindowLayoutComponent? by lazy {
+        val loader = SafeWindowLayoutComponentProvider::class.java.classLoader
+        if (loader != null && canUseWindowLayoutComponent(loader)) {
+            try {
+                WindowExtensionsProvider.getWindowExtensions().windowLayoutComponent
+            } catch (e: UnsupportedOperationException) {
+                null
+            }
+        } else {
+            null
+        }
+    }
+
+    private fun canUseWindowLayoutComponent(classLoader: ClassLoader): Boolean {
+        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            isWindowLayoutProviderValid(classLoader) &&
+                isWindowExtensionsValid(classLoader) &&
+                isWindowLayoutComponentValid(classLoader) &&
+                isFoldingFeatureValid(classLoader)
+        } else {
+            false
+        }
+    }
+
+    private fun isWindowLayoutProviderValid(classLoader: ClassLoader): Boolean {
+        return validate {
+            val providerClass = windowExtensionsProviderClass(classLoader)
+            val getWindowExtensionsMethod = providerClass.getDeclaredMethod("getWindowExtensions")
+            val windowExtensionsClass = windowExtensionsClass(classLoader)
+            getWindowExtensionsMethod.doesReturn(windowExtensionsClass) &&
+                getWindowExtensionsMethod.isPublic
+        }
+    }
+
+    private fun isWindowExtensionsValid(classLoader: ClassLoader): Boolean {
+        return validate {
+            val extensionsClass = windowExtensionsClass(classLoader)
+            val getWindowLayoutComponentMethod =
+                extensionsClass.getMethod("getWindowLayoutComponent")
+            val windowLayoutComponentClass = windowLayoutComponentClass(classLoader)
+            getWindowLayoutComponentMethod.isPublic &&
+                getWindowLayoutComponentMethod.doesReturn(windowLayoutComponentClass)
+        }
+    }
+
+    private fun isFoldingFeatureValid(classLoader: ClassLoader): Boolean {
+        return validate {
+            val foldingFeatureClass = foldingFeatureClass(classLoader)
+            val getBoundsMethod = foldingFeatureClass.getMethod("getBounds")
+            val getTypeMethod = foldingFeatureClass.getMethod("getType")
+            val getStateMethod = foldingFeatureClass.getMethod("getState")
+            getBoundsMethod.doesReturn(Rect::class) &&
+                getBoundsMethod.isPublic &&
+                getTypeMethod.doesReturn(Int::class) &&
+                getTypeMethod.isPublic &&
+                getStateMethod.doesReturn(Int::class) &&
+                getStateMethod.isPublic
+        }
+    }
+
+    @RequiresApi(24)
+    private fun isWindowLayoutComponentValid(classLoader: ClassLoader): Boolean {
+        return validate {
+            val windowLayoutComponent = windowLayoutComponentClass(classLoader)
+            val addListenerMethod = windowLayoutComponent
+                .getMethod(
+                    "addWindowLayoutInfoListener",
+                    Activity::class.java,
+                    Consumer::class.java
+                )
+            val removeListenerMethod = windowLayoutComponent
+                .getMethod("removeWindowLayoutInfoListener", Consumer::class.java)
+            addListenerMethod.isPublic && removeListenerMethod.isPublic
+        }
+    }
+
+    private fun validate(block: () -> Boolean): Boolean {
+        return try {
+            block()
+        } catch (noClass: ClassNotFoundException) {
+            false
+        } catch (noMethod: NoSuchMethodException) {
+            false
+        }
+    }
+
+    private val Method.isPublic: Boolean
+        get() {
+            return Modifier.isPublic(modifiers)
+        }
+
+    private fun Method.doesReturn(clazz: KClass<*>): Boolean {
+        return doesReturn(clazz.java)
+    }
+
+    private fun Method.doesReturn(clazz: Class<*>): Boolean {
+        return returnType.equals(clazz)
+    }
+
+    private fun windowExtensionsProviderClass(classLoader: ClassLoader) =
+        classLoader.loadClass("androidx.window.extensions.WindowExtensionsProvider")
+
+    private fun windowExtensionsClass(classLoader: ClassLoader) =
+        classLoader.loadClass("androidx.window.extensions.WindowExtensions")
+
+    private fun foldingFeatureClass(classLoader: ClassLoader) =
+        classLoader.loadClass("androidx.window.extensions.layout.FoldingFeature")
+
+    private fun windowLayoutComponentClass(classLoader: ClassLoader) =
+        classLoader.loadClass("androidx.window.extensions.layout.WindowLayoutComponent")
+}
diff --git a/window/window/src/main/java/androidx/window/layout/SidecarAdapter.kt b/window/window/src/main/java/androidx/window/layout/SidecarAdapter.kt
index 75624d8..b51d717 100644
--- a/window/window/src/main/java/androidx/window/layout/SidecarAdapter.kt
+++ b/window/window/src/main/java/androidx/window/layout/SidecarAdapter.kt
@@ -18,19 +18,26 @@
 package androidx.window.layout
 
 import android.annotation.SuppressLint
-import android.util.Log
 import androidx.annotation.VisibleForTesting
 import androidx.window.core.Bounds
+import androidx.window.core.SpecificationComputer.Companion.startSpecification
+import androidx.window.core.SpecificationComputer.VerificationMode
+import androidx.window.core.SpecificationComputer.VerificationMode.QUIET
+import androidx.window.layout.HardwareFoldingFeature.Type.Companion.FOLD
+import androidx.window.layout.HardwareFoldingFeature.Type.Companion.HINGE
 import androidx.window.layout.SidecarWindowBackend.Companion.DEBUG
 import androidx.window.sidecar.SidecarDeviceState
 import androidx.window.sidecar.SidecarDisplayFeature
+import androidx.window.sidecar.SidecarDisplayFeature.TYPE_FOLD
+import androidx.window.sidecar.SidecarDisplayFeature.TYPE_HINGE
 import androidx.window.sidecar.SidecarWindowLayoutInfo
 import java.lang.reflect.InvocationTargetException
 
 /**
  * A class for translating Sidecar data classes.
  */
-internal class SidecarAdapter {
+// TODO(b/206055949) convert to strict validation.
+internal class SidecarAdapter(private val verificationMode: VerificationMode = QUIET) {
 
     fun translate(
         sidecarDisplayFeatures: List<SidecarDisplayFeature>,
@@ -141,6 +148,48 @@
         }
     }
 
+    /**
+     * Converts the display feature from extension. Can return `null` if there is an issue
+     * with the value passed from extension.
+     */
+    internal fun translate(
+        feature: SidecarDisplayFeature,
+        deviceState: SidecarDeviceState
+    ): DisplayFeature? {
+        val checkedFeature = feature.startSpecification(TAG, verificationMode)
+            .require("Type must be either TYPE_FOLD or TYPE_HINGE") {
+                type == TYPE_FOLD || type == TYPE_HINGE
+            }
+            .require("Feature bounds must not be 0") { rect.width() != 0 || rect.height() != 0 }
+            .require("TYPE_FOLD must have 0 area") {
+                if (type == TYPE_FOLD) {
+                    rect.width() == 0 || rect.height() == 0
+                } else {
+                    true
+                }
+            }
+            .require("Feature be pinned to either left or top") {
+                rect.left == 0 || rect.top == 0
+            }
+            .compute() ?: return null
+        val type = when (checkedFeature.type) {
+            TYPE_FOLD -> FOLD
+            TYPE_HINGE -> HINGE
+            else -> {
+                return null
+            }
+        }
+        val state = when (getSidecarDevicePosture(deviceState)) {
+            SidecarDeviceState.POSTURE_CLOSED,
+            SidecarDeviceState.POSTURE_UNKNOWN,
+            SidecarDeviceState.POSTURE_FLIPPED -> return null
+            SidecarDeviceState.POSTURE_HALF_OPENED -> FoldingFeature.State.HALF_OPENED
+            SidecarDeviceState.POSTURE_OPENED -> FoldingFeature.State.FLAT
+            else -> FoldingFeature.State.FLAT
+        }
+        return HardwareFoldingFeature(Bounds(feature.rect), type, state)
+    }
+
     companion object {
         private val TAG = SidecarAdapter::class.java.simpleName
 
@@ -271,77 +320,5 @@
                 }
             }
         }
-
-        /**
-         * Converts the display feature from extension. Can return `null` if there is an issue
-         * with the value passed from extension.
-         */
-        internal fun translate(
-            feature: SidecarDisplayFeature,
-            deviceState: SidecarDeviceState
-        ): DisplayFeature? {
-            val bounds = feature.rect
-            if (bounds.width() == 0 && bounds.height() == 0) {
-                if (DEBUG) {
-                    Log.d(TAG, "Passed a display feature with empty rect, skipping: $feature")
-                }
-                return null
-            }
-            if (feature.type == SidecarDisplayFeature.TYPE_FOLD) {
-                if (bounds.width() != 0 && bounds.height() != 0) {
-                    // Bounds for fold types are expected to be zero-wide or zero-high.
-                    // See DisplayFeature#getBounds().
-                    if (DEBUG) {
-                        Log.d(
-                            TAG,
-                            "Passed a non-zero area display feature expected to be zero-area, " +
-                                "skipping: $feature"
-                        )
-                    }
-                    return null
-                }
-            }
-            if (feature.type == SidecarDisplayFeature.TYPE_HINGE ||
-                feature.type == SidecarDisplayFeature.TYPE_FOLD
-            ) {
-                // TODO(b/175507310): Reinstate after fix on the OEM side.
-                if (
-                    !(
-                        bounds.left == 0 /* && bounds.right == windowBounds.width()*/ ||
-                            bounds.top == 0 /* && bounds.bottom == windowBounds.height()*/
-                        )
-                ) {
-                    // Bounds for fold and hinge types are expected to span the entire window space.
-                    // See DisplayFeature#getBounds().
-                    if (DEBUG) {
-                        Log.d(
-                            TAG,
-                            "Passed a display feature expected to span the entire window but " +
-                                "does not, skipping: $feature"
-                        )
-                    }
-                    return null
-                }
-            }
-            val type = when (feature.type) {
-                SidecarDisplayFeature.TYPE_FOLD -> HardwareFoldingFeature.Type.FOLD
-                SidecarDisplayFeature.TYPE_HINGE -> HardwareFoldingFeature.Type.HINGE
-                else -> {
-                    if (DEBUG) {
-                        Log.d(TAG, "Unknown feature type: ${feature.type}, skipping feature.")
-                    }
-                    return null
-                }
-            }
-            val state = when (getSidecarDevicePosture(deviceState)) {
-                SidecarDeviceState.POSTURE_CLOSED,
-                SidecarDeviceState.POSTURE_UNKNOWN,
-                SidecarDeviceState.POSTURE_FLIPPED -> return null
-                SidecarDeviceState.POSTURE_HALF_OPENED -> FoldingFeature.State.HALF_OPENED
-                SidecarDeviceState.POSTURE_OPENED -> FoldingFeature.State.FLAT
-                else -> FoldingFeature.State.FLAT
-            }
-            return HardwareFoldingFeature(Bounds(feature.rect), type, state)
-        }
     }
 }
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/layout/SidecarCompat.kt b/window/window/src/main/java/androidx/window/layout/SidecarCompat.kt
index 78131e5..4548540 100644
--- a/window/window/src/main/java/androidx/window/layout/SidecarCompat.kt
+++ b/window/window/src/main/java/androidx/window/layout/SidecarCompat.kt
@@ -60,7 +60,7 @@
     private var extensionCallback: ExtensionCallbackInterface? = null
 
     constructor(context: Context) : this(
-        SidecarProvider.getSidecarImpl(context.applicationContext),
+        getSidecarCompat(context),
         SidecarAdapter()
     )
 
@@ -456,6 +456,10 @@
                 null
             }
 
+        internal fun getSidecarCompat(context: Context): SidecarInterface? {
+            return SidecarProvider.getSidecarImpl(context.applicationContext)
+        }
+
         /**
          * A utility method [Activity] to return an optional [IBinder] window token from an
          * [Activity].
diff --git a/window/window/src/main/java/androidx/window/layout/WindowInfoTracker.kt b/window/window/src/main/java/androidx/window/layout/WindowInfoTracker.kt
index 8ff4ba2..683c043 100644
--- a/window/window/src/main/java/androidx/window/layout/WindowInfoTracker.kt
+++ b/window/window/src/main/java/androidx/window/layout/WindowInfoTracker.kt
@@ -21,7 +21,6 @@
 import android.util.Log
 import androidx.annotation.RestrictTo
 import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
-import androidx.window.extensions.WindowExtensionsProvider
 import kotlinx.coroutines.flow.Flow
 
 /**
@@ -75,7 +74,7 @@
         @Suppress("MemberVisibilityCanBePrivate") // Avoid synthetic accessor
         internal fun windowBackend(context: Context): WindowBackend {
             val extensionBackend = try {
-                WindowExtensionsProvider.getWindowExtensions().windowLayoutComponent
+                SafeWindowLayoutComponentProvider.windowLayoutComponent
                     ?.let(::ExtensionWindowLayoutInfoBackend)
             } catch (t: Throwable) {
                 if (DEBUG) {
diff --git a/window/window/src/release/java/androidx/window/core/BuildConfig.kt b/window/window/src/release/java/androidx/window/core/BuildConfig.kt
new file mode 100644
index 0000000..7706338
--- /dev/null
+++ b/window/window/src/release/java/androidx/window/core/BuildConfig.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.window.core
+
+import androidx.window.core.SpecificationComputer.VerificationMode
+import androidx.window.core.SpecificationComputer.VerificationMode.QUIET
+
+/**
+ * A configuration to be used for the release flavor of the library. Default [VerificationMode] is
+ * [QUIET] so that apps do not crash when there are errors that we can handle.
+ */
+internal object BuildConfig {
+    val verificationMode: VerificationMode = QUIET
+}
\ No newline at end of file
diff --git a/window/window/src/test/java/androidx/window/core/SpecificationComputerTest.kt b/window/window/src/test/java/androidx/window/core/SpecificationComputerTest.kt
new file mode 100644
index 0000000..bf12475
--- /dev/null
+++ b/window/window/src/test/java/androidx/window/core/SpecificationComputerTest.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.window.core
+
+import androidx.window.core.SpecificationComputer.Companion.startSpecification
+import androidx.window.core.SpecificationComputer.VerificationMode.LOG
+import androidx.window.core.SpecificationComputer.VerificationMode.QUIET
+import androidx.window.core.SpecificationComputer.VerificationMode.STRICT
+import com.nhaarman.mockitokotlin2.eq
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.verify
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.mockito.ArgumentMatchers.contains
+
+/**
+ * Tests [SpecificationComputer] to verify the following behaviors.
+ * The key use-cases are when [SpecificationComputer.require] has failed and the expected behaviors
+ * are as follows. For [STRICT] mode, [SpecificationComputer.compute] should throw an exception.
+ * For [QUIET] mode, [SpecificationComputer.compute] should return a null value. For [LOG] mode,
+ * [SpecificationComputer.compute] should write a log and return a null value.
+ */
+class SpecificationComputerTest {
+
+    @Test
+    fun run_returnsValidValue() {
+        val actual = DATA.startSpecification(TAG, QUIET).compute()
+
+        assertEquals(DATA, actual)
+    }
+
+    @Test
+    fun run_returnsNullIfRequiredCheckFails() {
+        val actual = DATA.startSpecification(TAG, QUIET)
+            .require(MESSAGE) { this.isEmpty() }
+            .compute()
+
+        assertNull(actual)
+    }
+
+    @Test(expected = WindowStrictModeException::class)
+    fun run_throwsIfStrictModeEnabled() {
+        DATA.startSpecification(TAG, STRICT)
+            .require(MESSAGE) { this.isEmpty() }
+            .compute()
+    }
+
+    @Test
+    fun run_exceptionStackAtRunCaller() {
+        var actual: WindowStrictModeException? = null
+        try {
+            DATA.startSpecification(TAG, STRICT)
+                .require(MESSAGE) { this.isEmpty() }
+                .compute()
+        } catch (e: WindowStrictModeException) {
+            actual = e
+        }
+        assertNotNull(actual)
+        assertEquals("run_exceptionStackAtRunCaller", actual!!.stackTrace.first().methodName)
+        assertTrue(actual.message!!.contains(MESSAGE))
+    }
+
+    @Test
+    fun run_logsIfLoggingModeEnabled() {
+        val logger = mock<Logger>()
+        DATA.startSpecification(TAG, LOG, logger)
+            .require(MESSAGE) { this.isEmpty() }
+            .compute()
+
+        verify(logger).debug(eq(TAG), contains(MESSAGE))
+    }
+
+    @Test
+    fun check_logsIfLoggingModeEnabled() {
+        val logger = mock<Logger>()
+        DATA.startSpecification(TAG, LOG, logger)
+            .require(MESSAGE) { this.isEmpty() }
+            .compute()
+
+        verify(logger).debug(eq(TAG), contains(MESSAGE))
+    }
+
+    companion object {
+        const val DATA = "test-data-23849573"
+        const val TAG = "test-tag-3462353414123412"
+        const val MESSAGE = "test-message-123412431241235"
+    }
+}
\ No newline at end of file
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/SchedulersTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/SchedulersTest.java
index d964c4e..c1d5d98 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/SchedulersTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/SchedulersTest.java
@@ -28,6 +28,7 @@
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SdkSuppress;
 import androidx.work.impl.background.systemalarm.SystemAlarmScheduler;
@@ -52,6 +53,7 @@
         mAppContext = ApplicationProvider.getApplicationContext();
     }
 
+    @FlakyTest(bugId = 206647994)
     @Test
     @SdkSuppress(minSdkVersion = WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL)
     public void testGetBackgroundScheduler_withJobSchedulerApiLevel() {