Merge "Migrate TestNavigatorDestinationBuilderTest to Google Truth asserts" into androidx-main
diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml
index 4eebecbb..9003d6a 100644
--- a/.github/workflows/integration_tests.yml
+++ b/.github/workflows/integration_tests.yml
@@ -30,3 +30,26 @@
         with:
           name: outputs
           path: ${{ steps.dirs.outputs.output-dir }}
+  teardown:
+    runs-on: ubuntu-latest
+    needs: [run_integration_tests]
+    if: always()
+    steps:
+      - name: Parse workflow status
+        id: workflow-status
+        run: |
+          set -x
+          if [ "${{ needs.run_integration_tests.result }}" == "success" ]                  && \
+            [ "${{ github.event.workflow_run.conclusion }}" == "success" ]
+          then
+            echo "::set-output name=result::true"
+          else
+            echo "::set-output name=result::false"
+          fi
+
+      - name: Result WebHook
+        uses: androidx/github-workflow-webhook-action@main
+        with:
+          url: 'https://androidx.dev/github/androidX/presubmit/hook'
+          secret: ${{ secrets.ANDROIDX_PRESUBMIT_HOOK_SECRET }}
+          payload: '{ "platform": "all", "token": "${{ secrets.GITHUB_TOKEN }}", "state": "completed", "success": ${{ steps.workflow-status.outputs.result }}, "src" : "workflow_run" }'
diff --git a/.github/workflows/register_workflow_start.yml b/.github/workflows/register_workflow_start.yml
new file mode 100644
index 0000000..8a66d98
--- /dev/null
+++ b/.github/workflows/register_workflow_start.yml
@@ -0,0 +1,17 @@
+name: Register Workflow Run with AndroidX
+on:
+  workflow_run:
+    workflows: ["AndroidX Presubmits"]
+    types: [requested]
+
+jobs:
+  ping_androidx_dev:
+    runs-on: ubuntu-latest
+    name: "Start webhook"
+    steps:
+      - name: "Ping AndroidX hook"
+        uses: androidx/github-workflow-webhook-action@main
+        with:
+          url: 'https://androidx.dev/github/androidX/presubmit/hook'
+          secret: ${{ secrets.ANDROIDX_PRESUBMIT_HOOK_SECRET }}
+          payload: '{ "platform": "all", "token": "${{ secrets.GITHUB_TOKEN }}", "state": "started", "src" : "workflow_run"}'
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextEmojiTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextEmojiTest.java
index 68d9eae..1b1cdb7c 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextEmojiTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextEmojiTest.java
@@ -99,4 +99,14 @@
 
         assertThat(textEmailAddress.getInputType()).isEqualTo(platformTextView.getInputType());
     }
+
+    @Test
+    @UiThreadTest
+    public void setKeyListener_null_setsToNull() {
+        AppCompatEditText textEmailAddress =
+                mActivityTestRule.getActivity().findViewById(R.id.text_email_address);
+
+        textEmailAddress.setKeyListener(null);
+        assertThat(textEmailAddress.getKeyListener()).isNull();
+    }
 }
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java
index 30e7abd..c68f361 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java
@@ -340,7 +340,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void setKeyListener(@NonNull KeyListener keyListener) {
+    public void setKeyListener(@Nullable KeyListener keyListener) {
         super.setKeyListener(mAppCompatEmojiEditTextHelper.getKeyListener(keyListener));
     }
 
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEmojiEditTextHelper.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEmojiEditTextHelper.java
index 4a08ec1..e3f8b80 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEmojiEditTextHelper.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEmojiEditTextHelper.java
@@ -121,10 +121,10 @@
      *
      * @param keyListener KeyListener passed into {@link TextView#setKeyListener(KeyListener)}
      *
-     * @return a new KeyListener instance that wraps {@code keyListener}.
+     * @return a new KeyListener instance that wraps {@code keyListener}, or null if passed null.
      */
-    @NonNull
-    KeyListener getKeyListener(@NonNull KeyListener keyListener) {
+    @Nullable
+    KeyListener getKeyListener(@Nullable KeyListener keyListener) {
         return mEmojiEditTextHelper.getKeyListener(keyListener);
     }
 
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index 3550a72..fe922a5 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -86,7 +86,7 @@
     val LIFECYCLE_EXTENSIONS = Version("2.2.0")
     val LOADER = Version("1.2.0-alpha01")
     val MEDIA = Version("1.4.0-beta01")
-    val MEDIA2 = Version("1.2.0-alpha02")
+    val MEDIA2 = Version("1.2.0-beta01")
     val MEDIAROUTER = Version("1.3.0-alpha02")
     val NAVIGATION = Version("2.4.0-alpha04")
     val PAGING = Version("3.1.0-alpha02")
diff --git a/buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt
index bacc7dc..736d53d 100644
--- a/buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt
@@ -574,6 +574,7 @@
     "androidx/navigation/**",
     "androidx/paging/**",
     "androidx/room/**",
+    "androidx/wear/**",
     "androidx/window/**",
     "androidx/work/**"
 )
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
index ef559a3..b7a01d8 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
@@ -294,7 +294,7 @@
             .setFileDescriptor(fd)
             .build()
 
-        assertThrows(IllegalArgumentException::class.java) {
+        assertThrows(IllegalStateException::class.java) {
             recorder.prepareRecording(outputOptions)
         }
     }
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/NavigationTemplate.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/NavigationTemplate.java
index c3045fe..0b58d66 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/NavigationTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/NavigationTemplate.java
@@ -301,7 +301,7 @@
          * Sets the background color to use for the navigation information.
          *
          * <p>Depending on contrast requirements, capabilities of the vehicle screens, or other
-         *  factors, the color may be ignored by the host or overridden by the vehicle system.
+         * factors, the color may be ignored by the host or overridden by the vehicle system.
          */
         @NonNull
         public Builder setBackgroundColor(@NonNull CarColor backgroundColor) {
@@ -358,6 +358,11 @@
          *
          * <p>The host will draw the buttons in an area that is associated with map controls.
          *
+         * <p>If the app does not include the {@link Action#PAN} button in this
+         * {@link ActionStrip}, the app will not receive the user input for panning gestures from
+         * {@link SurfaceCallback} methods, and the host will exit any previously activated pan
+         * mode.
+         *
          * <h4>Requirements</h4>
          *
          * This template allows up to 4 {@link Action}s in its map {@link ActionStrip}. Only
@@ -380,6 +385,11 @@
          * Sets a {@link PanModeListener} that notifies when the user enters and exits
          * the pan mode.
          *
+         * <p>If the app does not include the {@link Action#PAN} button in the map
+         * {@link ActionStrip}, the app will not receive the user input for panning gestures from
+         * {@link SurfaceCallback} methods, and the host will exit any previously activated pan
+         * mode.
+         *
          * @throws NullPointerException if {@code panModeListener} is {@code null}
          */
         @SuppressLint({"MissingGetterMatchingBuilder", "ExecutorRegistration"})
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Arrangement.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Arrangement.kt
index 4fe2fd4..30aa2ac 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Arrangement.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Arrangement.kt
@@ -169,7 +169,7 @@
             outPositions: IntArray
         ) = placeRightOrBottom(totalSize, sizes, outPositions, reverseInput = false)
 
-        override fun toString() = "Arrangement#Start"
+        override fun toString() = "Arrangement#Bottom"
     }
 
     /**
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/text/Text.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/text/Text.kt
new file mode 100644
index 0000000..ac61a09
--- /dev/null
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/text/Text.kt
@@ -0,0 +1,385 @@
+/*
+ * 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.
+ */
+
+// Ignore lint warnings in documentation snippets
+@file:Suppress("unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "PreviewMustBeTopLevelFunction")
+
+package androidx.compose.integration.docs.text
+
+import android.util.Log
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.ClickableText
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.text.selection.DisableSelection
+import androidx.compose.foundation.text.selection.SelectionContainer
+import androidx.compose.material.OutlinedTextField
+import androidx.compose.material.Text
+import androidx.compose.material.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.ParagraphStyle
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.font.Font
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.text.withStyle
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+/**
+ * This file lets DevRel track changes to snippets present in
+ * https://developer.android.com/jetpack/compose/text
+ *
+ * No action required if it's modified.
+ */
+
+private object TextSnippet1 {
+    @Composable
+    fun SimpleText() {
+        Text("Hello World")
+    }
+}
+
+private object TextSnippet2 {
+    @Composable
+    fun StringResourceText() {
+        Text(stringResource(R.string.hello_world))
+    }
+}
+
+private object TextSnippet3 {
+    @Composable
+    fun BlueText() {
+        Text("Hello World", color = Color.Blue)
+    }
+}
+
+private object TextSnippet4 {
+    @Composable
+    fun BigText() {
+        Text("Hello World", fontSize = 30.sp)
+    }
+}
+
+private object TextSnippet5 {
+    @Composable
+    fun ItalicText() {
+        Text("Hello World", fontStyle = FontStyle.Italic)
+    }
+}
+
+private object TextSnippet6 {
+    @Composable
+    fun BoldText() {
+        Text("Hello World", fontWeight = FontWeight.Bold)
+    }
+}
+
+private object TextSnippet7 {
+    @Preview(showBackground = true)
+    @Composable
+    fun CenterText() {
+        Text(
+            "Hello World", textAlign = TextAlign.Center,
+            modifier = Modifier.width(150.dp)
+        )
+    }
+}
+
+private object TextSnippet8 {
+    @Composable
+    fun DifferentFonts() {
+        Column {
+            Text("Hello World", fontFamily = FontFamily.Serif)
+            Text("Hello World", fontFamily = FontFamily.SansSerif)
+        }
+    }
+}
+
+@Composable
+private fun TextSnippet9() {
+    val firaSansFamily = FontFamily(
+        Font(R.font.firasans_light, FontWeight.Light),
+        Font(R.font.firasans_regular, FontWeight.Normal),
+        Font(R.font.firasans_italic, FontWeight.Normal, FontStyle.Italic),
+        Font(R.font.firasans_medium, FontWeight.Medium),
+        Font(R.font.firasans_bold, FontWeight.Bold)
+    )
+}
+
+/* NOTE:
+ * Snippet in docs page simplifies the arguments, using "..." for everything before
+ * the font values. If code in TextSnippet10 changes, make the corresponding change
+ * to this code, which is in the doc:
+Column {
+    Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Light)
+    Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Normal)
+    Text(
+        ..., fontFamily = firaSansFamily, fontWeight = FontWeight.Normal,
+        fontStyle = FontStyle.Italic
+    )
+    Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Medium)
+    Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Bold)
+}
+ *
+ */
+@Composable
+private fun TextSnippet10() {
+    Column {
+        Text(text = "test", fontFamily = firaSansFamily, fontWeight = FontWeight.Light)
+        Text(text = "test", fontFamily = firaSansFamily, fontWeight = FontWeight.Normal)
+        Text(
+            text = "test", fontFamily = firaSansFamily, fontWeight = FontWeight.Normal,
+            fontStyle = FontStyle.Italic
+        )
+        Text(text = "test", fontFamily = firaSansFamily, fontWeight = FontWeight.Medium)
+        Text(text = "test", fontFamily = firaSansFamily, fontWeight = FontWeight.Bold)
+    }
+}
+
+private object TextSnippet11 {
+    @Composable
+    fun MultipleStylesInText() {
+        Text(
+            buildAnnotatedString {
+                withStyle(style = SpanStyle(color = Color.Blue)) {
+                    append("H")
+                }
+                append("ello ")
+
+                withStyle(style = SpanStyle(fontWeight = FontWeight.Bold, color = Color.Red)) {
+                    append("W")
+                }
+                append("orld")
+            }
+        )
+    }
+}
+
+private object TextSnippet12 {
+    @Composable
+    fun ParagraphStyle() {
+        Text(
+            buildAnnotatedString {
+                withStyle(style = ParagraphStyle(lineHeight = 30.sp)) {
+                    withStyle(style = SpanStyle(color = Color.Blue)) {
+                        append("Hello\n")
+                    }
+                    withStyle(
+                        style = SpanStyle(
+                            fontWeight = FontWeight.Bold,
+                            color = Color.Red
+                        )
+                    ) {
+                        append("World\n")
+                    }
+                    append("Compose")
+                }
+            }
+        )
+    }
+}
+
+private object TextSnippet13 {
+    @Composable
+    fun LongText() {
+        Text("hello ".repeat(50), maxLines = 2)
+    }
+}
+
+private object TextSnippet14 {
+    @Composable
+    fun OverflowedText() {
+        Text("Hello Compose ".repeat(50), maxLines = 2, overflow = TextOverflow.Ellipsis)
+    }
+}
+
+private object TextSnippet15 {
+    @Composable
+    fun SelectableText() {
+        SelectionContainer {
+            Text("This text is selectable")
+        }
+    }
+}
+
+private object TextSnippet16 {
+    @Composable
+    fun PartiallySelectableText() {
+        SelectionContainer {
+            Column {
+                Text("This text is selectable")
+                Text("This one too")
+                Text("This one as well")
+                DisableSelection {
+                    Text("But not this one")
+                    Text("Neither this one")
+                }
+                Text("But again, you can select this one")
+                Text("And this one too")
+            }
+        }
+    }
+}
+
+private object TextSnippet17 {
+    @Composable
+    fun SimpleClickableText() {
+        ClickableText(
+            text = AnnotatedString("Click Me"),
+            onClick = { offset ->
+                Log.d("ClickableText", "$offset -th character is clicked.")
+            }
+        )
+    }
+}
+
+private object TextSnippet18 {
+    @Composable
+    fun AnnotatedClickableText() {
+        val annotatedText = buildAnnotatedString {
+            append("Click ")
+
+            // We attach this *URL* annotation to the following content
+            // until `pop()` is called
+            pushStringAnnotation(
+                tag = "URL",
+                annotation = "https://developer.android.com"
+            )
+            withStyle(
+                style = SpanStyle(
+                    color = Color.Blue,
+                    fontWeight = FontWeight.Bold
+                )
+            ) {
+                append("here")
+            }
+
+            pop()
+        }
+
+        ClickableText(
+            text = annotatedText,
+            onClick = { offset ->
+                // We check if there is an *URL* annotation attached to the text
+                // at the clicked position
+                annotatedText.getStringAnnotations(
+                    tag = "URL", start = offset,
+                    end = offset
+                )
+                    .firstOrNull()?.let { annotation ->
+                        // If yes, we log its value
+                        Log.d("Clicked URL", annotation.item)
+                    }
+            }
+        )
+    }
+}
+
+private object TextSnippet19 {
+    @Composable
+    fun SimpleFilledTextFieldSample() {
+        var text by remember { mutableStateOf("Hello") }
+
+        TextField(
+            value = text,
+            onValueChange = { text = it },
+            label = { Text("Label") }
+        )
+    }
+}
+
+private object TextSnippet20 {
+    @Composable
+    fun SimpleOutlinedTextFieldSample() {
+        var text by remember { mutableStateOf("") }
+
+        OutlinedTextField(
+            value = text,
+            onValueChange = { text = it },
+            label = { Text("Label") }
+        )
+    }
+}
+
+private object TextSnippet21 {
+    @Composable
+    fun StyledTextField() {
+        var value by remember { mutableStateOf("Hello\nWorld\nInvisible") }
+
+        TextField(
+            value = value,
+            onValueChange = { value = it },
+            label = { Text("Enter text") },
+            maxLines = 2,
+            textStyle = TextStyle(color = Color.Blue, fontWeight = FontWeight.Bold),
+            modifier = Modifier.padding(20.dp)
+        )
+    }
+}
+
+private object TextSnippet22 {
+    @Composable
+    fun PasswordTextField() {
+        var password by rememberSaveable { mutableStateOf("") }
+
+        TextField(
+            value = password,
+            onValueChange = { password = it },
+            label = { Text("Enter password") },
+            visualTransformation = PasswordVisualTransformation(),
+            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
+        )
+    }
+}
+
+/*
+ * Fakes needed for snippets to build:
+ */
+
+private object R {
+    object string {
+        const val hello_world = 1
+    }
+
+    object font {
+        const val firasans_light = 1
+        const val firasans_regular = 1
+        const val firasans_italic = 1
+        const val firasans_medium = 1
+        const val firasans_bold = 1
+    }
+}
+
+private val firaSansFamily = FontFamily()
diff --git a/compose/material/material-icons-extended/build.gradle b/compose/material/material-icons-extended/build.gradle
index eafbc6a..c69aab7 100644
--- a/compose/material/material-icons-extended/build.gradle
+++ b/compose/material/material-icons-extended/build.gradle
@@ -19,6 +19,7 @@
 import androidx.build.LibraryType
 import androidx.build.RunApiTasks
 import androidx.compose.material.icons.generator.tasks.IconGenerationTask
+import androidx.compose.material.icons.generator.tasks.ExtendedIconGenerationTask
 
 plugins {
     id("AndroidXPlugin")
@@ -121,6 +122,8 @@
             } else {
                 v.registerPostJavacGeneratedBytecode(configurations.embedThemesRelease)
             }
+            // Manually set up source jar generation
+            ExtendedIconGenerationTask.registerSourceJarOnly(project, v)
         }
     }
 } else {
diff --git a/compose/material/material/api/1.0.0-beta10.txt b/compose/material/material/api/1.0.0-beta10.txt
index b6e8f4d..80547b2 100644
--- a/compose/material/material/api/1.0.0-beta10.txt
+++ b/compose/material/material/api/1.0.0-beta10.txt
@@ -47,6 +47,9 @@
   public final class BackdropScaffoldKt {
   }
 
+  public final class BadgeKt {
+  }
+
   public final class BottomNavigationDefaults {
     method public float getElevation-D9Ej5fM();
     property public final float Elevation;
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index b6e8f4d..80547b2 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -47,6 +47,9 @@
   public final class BackdropScaffoldKt {
   }
 
+  public final class BadgeKt {
+  }
+
   public final class BottomNavigationDefaults {
     method public float getElevation-D9Ej5fM();
     property public final float Elevation;
diff --git a/compose/material/material/api/public_plus_experimental_1.0.0-beta10.txt b/compose/material/material/api/public_plus_experimental_1.0.0-beta10.txt
index aa65079..6631c34a 100644
--- a/compose/material/material/api/public_plus_experimental_1.0.0-beta10.txt
+++ b/compose/material/material/api/public_plus_experimental_1.0.0-beta10.txt
@@ -71,6 +71,10 @@
     enum_constant public static final androidx.compose.material.BackdropValue Revealed;
   }
 
+  public final class BadgeKt {
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BadgeBox-X41NPXw(optional androidx.compose.ui.Modifier modifier, optional long backgroundColor, optional long contentColor, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? badgeContent, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+  }
+
   @androidx.compose.material.ExperimentalMaterialApi public final class BottomDrawerState extends androidx.compose.material.SwipeableState<androidx.compose.material.BottomDrawerValue> {
     ctor public BottomDrawerState(androidx.compose.material.BottomDrawerValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomDrawerValue,java.lang.Boolean> confirmStateChange);
     method public suspend Object? close(kotlin.coroutines.Continuation<? super kotlin.Unit> p);
diff --git a/compose/material/material/api/public_plus_experimental_current.txt b/compose/material/material/api/public_plus_experimental_current.txt
index aa65079..6631c34a 100644
--- a/compose/material/material/api/public_plus_experimental_current.txt
+++ b/compose/material/material/api/public_plus_experimental_current.txt
@@ -71,6 +71,10 @@
     enum_constant public static final androidx.compose.material.BackdropValue Revealed;
   }
 
+  public final class BadgeKt {
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BadgeBox-X41NPXw(optional androidx.compose.ui.Modifier modifier, optional long backgroundColor, optional long contentColor, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? badgeContent, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+  }
+
   @androidx.compose.material.ExperimentalMaterialApi public final class BottomDrawerState extends androidx.compose.material.SwipeableState<androidx.compose.material.BottomDrawerValue> {
     ctor public BottomDrawerState(androidx.compose.material.BottomDrawerValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomDrawerValue,java.lang.Boolean> confirmStateChange);
     method public suspend Object? close(kotlin.coroutines.Continuation<? super kotlin.Unit> p);
diff --git a/compose/material/material/api/restricted_1.0.0-beta10.txt b/compose/material/material/api/restricted_1.0.0-beta10.txt
index b6e8f4d..80547b2 100644
--- a/compose/material/material/api/restricted_1.0.0-beta10.txt
+++ b/compose/material/material/api/restricted_1.0.0-beta10.txt
@@ -47,6 +47,9 @@
   public final class BackdropScaffoldKt {
   }
 
+  public final class BadgeKt {
+  }
+
   public final class BottomNavigationDefaults {
     method public float getElevation-D9Ej5fM();
     property public final float Elevation;
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index b6e8f4d..80547b2 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -47,6 +47,9 @@
   public final class BackdropScaffoldKt {
   }
 
+  public final class BadgeKt {
+  }
+
   public final class BottomNavigationDefaults {
     method public float getElevation-D9Ej5fM();
     property public final float Elevation;
diff --git a/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/tasks/IconGenerationTask.kt b/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/tasks/IconGenerationTask.kt
index 9daa0c4..8ae4eb6 100644
--- a/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/tasks/IconGenerationTask.kt
+++ b/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/tasks/IconGenerationTask.kt
@@ -150,8 +150,8 @@
 
         /**
          * Registers the extended [project]. The core project contains all icons except for the
-         * icons defined in [androidx.compose.material.icons.generator.CoreIcons], as well as a bitmap comparison
-         * test for every icon in both the core and extended project.
+         * icons defined in [androidx.compose.material.icons.generator.CoreIcons], as well as a
+         * bitmap comparison test for every icon in both the core and extended project.
          */
         @JvmStatic
         fun registerExtendedIconThemeProject(
diff --git a/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/tasks/IconSourceTasks.kt b/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/tasks/IconSourceTasks.kt
index a3e4971..f031253 100644
--- a/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/tasks/IconSourceTasks.kt
+++ b/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/tasks/IconSourceTasks.kt
@@ -22,6 +22,7 @@
 import org.gradle.api.Project
 import org.gradle.api.tasks.CacheableTask
 import org.gradle.api.tasks.TaskProvider
+import org.gradle.api.tasks.bundling.Jar
 import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
 import java.io.File
 
@@ -46,13 +47,9 @@
             // Multiplatform
             if (variant == null) {
                 registerIconGenerationTask(project, task, buildDirectory)
-                project.afterEvaluate {
-                    // Workaround: https://github.com/gradle/gradle/issues/17250
-                    project.getTasksByName("sourceJarRelease", false).single().dependsOn(task)
-                }
             }
             // AGP
-            else variant.registerIconGenerationTask(task, buildDirectory)
+            else variant.registerIconGenerationTask(project, task, buildDirectory)
         }
     }
 }
@@ -78,13 +75,29 @@
             // Multiplatform
             if (variant == null) {
                 registerIconGenerationTask(project, task, buildDirectory)
-                project.afterEvaluate {
-                    // Workaround: https://github.com/gradle/gradle/issues/17250
-                    project.getTasksByName("sourceJarRelease", false).single().dependsOn(task)
-                }
             }
             // AGP
-            else variant.registerIconGenerationTask(task, buildDirectory)
+            else variant.registerIconGenerationTask(project, task, buildDirectory)
+        }
+
+        /**
+         * Registers the icon generation task just for source jar generation, and not for
+         * compilation. This is temporarily needed since we manually parallelize compilation in
+         * material-icons-extended for the AGP build. When we remove that parallelization code,
+         * we can remove this too.
+         */
+        @JvmStatic
+        fun registerSourceJarOnly(project: Project, variant: BaseVariant) {
+            // Setup the source jar task if this is the release variant
+            if (variant.name == "release") {
+                val (task, buildDirectory) = project.registerGenerationTask(
+                    "generateExtendedIcons",
+                    ExtendedIconGenerationTask::class.java,
+                    variant
+                )
+                val generatedSrcMainDirectory = buildDirectory.resolve(GeneratedSrcMain)
+                project.addToSourceJar(generatedSrcMainDirectory, task)
+            }
         }
     }
 }
@@ -101,15 +114,41 @@
     val sourceSet = project.getMultiplatformSourceSet(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME)
     val generatedSrcMainDirectory = buildDirectory.resolve(IconGenerationTask.GeneratedSrcMain)
     sourceSet.kotlin.srcDir(project.files(generatedSrcMainDirectory).builtBy(task))
+    project.addToSourceJar(generatedSrcMainDirectory, task)
 }
 
 /**
  * Helper to register [task] as the java source generating task that outputs to [buildDirectory].
  */
 private fun BaseVariant.registerIconGenerationTask(
+    project: Project,
     task: TaskProvider<*>,
     buildDirectory: File
 ) {
     val generatedSrcMainDirectory = buildDirectory.resolve(IconGenerationTask.GeneratedSrcMain)
     registerJavaGeneratingTask(task, generatedSrcMainDirectory)
+    // Setup the source jar task if this is the release variant
+    if (name == "release") {
+        project.addToSourceJar(generatedSrcMainDirectory, task)
+    }
+}
+
+/**
+ * Adds the contents of [buildDirectory] to the source jar generated for this [Project] by [task]
+ */
+// TODO: b/191485164 remove when AGP lets us get generated sources from a TestedExtension or
+// similar, then we can just add generated sources in SourceJarTaskHelper for all projects,
+// instead of needing one-off support here.
+private fun Project.addToSourceJar(buildDirectory: File, task: TaskProvider<*>) {
+    afterEvaluate {
+        val sourceJar = tasks.named("sourceJarRelease", Jar::class.java)
+        sourceJar.configure {
+            // Generating source jars requires the generation task to run first. This shouldn't
+            // be needed for the MPP build because we use builtBy to set up the dependency
+            // (https://github.com/gradle/gradle/issues/17250) but the path is different for AGP,
+            // so we will still need this for the AGP build.
+            it.dependsOn(task)
+            it.from(buildDirectory)
+        }
+    }
 }
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/BadgeDemo.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/BadgeDemo.kt
new file mode 100644
index 0000000..7d39a3b
--- /dev/null
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/BadgeDemo.kt
@@ -0,0 +1,365 @@
+/*
+ * 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.material.demos
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.BadgeBox
+import androidx.compose.material.BottomNavigation
+import androidx.compose.material.BottomNavigationItem
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.LeadingIconTab
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Tab
+import androidx.compose.material.TabRow
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material.icons.filled.Menu
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+
+@OptIn(ExperimentalMaterialApi::class)
+@Composable
+fun BadgeDemo() {
+    Column(Modifier.verticalScroll(rememberScrollState())) {
+        var badgeCount by remember { mutableStateOf(8) }
+        Spacer(Modifier.requiredHeight(24.dp))
+        TopAppBarWithBadge(
+            { badgeCount = 0 },
+            badgeCount
+        )
+        Spacer(Modifier.requiredHeight(24.dp))
+        BottomNavigationWithBadge(
+            { badgeCount = 0 },
+            artistsBadgeCount = badgeCount
+        )
+        Spacer(Modifier.requiredHeight(24.dp))
+        TextTabsWithBadge(
+            { badgeCount = 0 },
+            tab1BadgeCount = badgeCount
+        )
+        Spacer(Modifier.requiredHeight(24.dp))
+        LeadingIconTabsWithBadge(
+            { badgeCount = 0 },
+            tab1BadgeCount = badgeCount
+        )
+        Spacer(Modifier.requiredHeight(24.dp))
+        Button(
+            modifier = Modifier.align(Alignment.CenterHorizontally),
+            onClick = {
+                badgeCount++
+            },
+            colors = ButtonDefaults.buttonColors(backgroundColor = Color.Cyan)
+        ) {
+            Text("+ badge number")
+        }
+        Spacer(Modifier.height(50.dp))
+    }
+}
+
+@Composable
+fun TopAppBarWithBadge(
+    onActionIcon1BadgeClick: () -> Unit,
+    actionIcon1BadgeCount: Int,
+) {
+    var showNavigationIconBadge by remember { mutableStateOf(true) }
+    var showActionIcon2Badge by remember { mutableStateOf(true) }
+    TopAppBar(
+        title = { Text("Simple TopAppBar") },
+        navigationIcon = {
+            IconButton(onClick = { showNavigationIconBadge = false }) {
+                if (showNavigationIconBadge) {
+                    DemoBadgeBox(null) {
+                        Icon(Icons.Filled.Menu, contentDescription = "Localized description")
+                    }
+                } else {
+                    Icon(Icons.Filled.Menu, contentDescription = "Localized description")
+                }
+            }
+        },
+        actions = {
+            // RowScope here, so these icons will be placed horizontally
+            IconButton(
+                onClick = onActionIcon1BadgeClick
+            ) {
+                if (actionIcon1BadgeCount > 0) {
+                    DemoBadgeBox(actionIcon1BadgeCount.toString()) {
+                        Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                    }
+                } else {
+                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                }
+            }
+            IconButton(
+                onClick = { showActionIcon2Badge = false }
+            ) {
+                if (showActionIcon2Badge) {
+                    DemoBadgeBox("99+") {
+                        Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                    }
+                } else {
+                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                }
+            }
+        }
+    )
+}
+
+private const val initialSelectedIndex = 0
+
+@Composable
+fun BottomNavigationWithBadge(
+    onArtistsBadgeClick: () -> Unit,
+    artistsBadgeCount: Int
+) {
+    var selectedItem by remember { mutableStateOf(initialSelectedIndex) }
+    val items = listOf("Songs", "Artists", "Playlists", "Something else")
+
+    var showSongsBadge by remember { mutableStateOf(true) }
+    var showPlaylistsBadge by remember { mutableStateOf(true) }
+
+    Column {
+        BottomNavigation {
+            items.forEachIndexed { index, item ->
+                val showBadge = when (index) {
+                    0 -> showSongsBadge
+                    1 -> artistsBadgeCount > 0
+                    2 -> showPlaylistsBadge
+                    else -> false
+                }
+                BottomNavigationItem(
+                    icon = {
+                        if (!showBadge) {
+                            Icon(
+                                Icons.Filled.Favorite,
+                                contentDescription = "Localized description"
+                            )
+                        } else {
+                            when (item) {
+                                "Artists" -> {
+                                    DemoBadgeBox(artistsBadgeCount.toString()) {
+                                        Icon(
+                                            Icons.Filled.Favorite,
+                                            contentDescription = "Localized description"
+                                        )
+                                    }
+                                }
+                                else -> {
+                                    DemoBadgeBox(
+                                        when (index) {
+                                            2 -> "99+"
+                                            else -> null
+                                        }
+                                    ) {
+                                        Icon(
+                                            Icons.Filled.Favorite,
+                                            contentDescription = "Localized description"
+                                        )
+                                    }
+                                }
+                            }
+                        }
+                    },
+                    label = { Text(item) },
+                    selected = selectedItem == index,
+                    onClick = {
+                        selectedItem = index
+                        when (item) {
+                            "Songs" -> showSongsBadge = false
+                            "Artists" -> onArtistsBadgeClick()
+                            "Playlists" -> showPlaylistsBadge = false
+                        }
+                    },
+                    alwaysShowLabel = false
+                )
+            }
+        }
+
+        Text(
+            modifier = Modifier.align(Alignment.CenterHorizontally),
+            text = "Bottom nav with badge: ${selectedItem + 1} selected",
+            style = MaterialTheme.typography.body1
+        )
+    }
+}
+
+@Composable
+fun TextTabsWithBadge(
+    onTab1BadgeClick: () -> Unit,
+    tab1BadgeCount: Int
+) {
+    var state by remember { mutableStateOf(initialSelectedIndex) }
+    val titles = listOf("TAB 1", "TAB 2", "TAB 3 WITH LOTS OF TEXT")
+    val showTabBadgeList = remember { mutableStateListOf(true, true) }
+
+    Column {
+        TabRow(selectedTabIndex = state) {
+            titles.forEachIndexed { index, title ->
+                val showBadge: Boolean = when (index) {
+                    0 -> showTabBadgeList[0]
+                    1 -> tab1BadgeCount > 0
+                    2 -> showTabBadgeList[1]
+                    else -> false
+                }
+                Tab(
+                    text = {
+                        if (!showBadge) {
+                            Text(title)
+                        } else {
+                            DemoBadgeBox(
+                                when (index) {
+                                    1 -> tab1BadgeCount.toString()
+                                    2 -> "99+"
+                                    else -> null
+                                }
+                            ) {
+                                Text(title)
+                            }
+                        }
+                    },
+                    selected = state == index,
+                    onClick = {
+                        state = index
+                        when (index) {
+                            0 -> showTabBadgeList[0] = false
+                            1 -> onTab1BadgeClick()
+                            2 -> showTabBadgeList[1] = false
+                        }
+                    }
+                )
+            }
+        }
+
+        Text(
+            modifier = Modifier.align(Alignment.CenterHorizontally),
+            text = "Icon tab with badge: ${state + 1} selected",
+            style = MaterialTheme.typography.body1
+        )
+    }
+}
+
+@OptIn(ExperimentalMaterialApi::class)
+@Composable
+fun LeadingIconTabsWithBadge(
+    onTab1BadgeClick: () -> Unit,
+    tab1BadgeCount: Int,
+) {
+    var state by remember { mutableStateOf(0) }
+    val titlesAndIcons = listOf(
+        "TAB" to Icons.Filled.Favorite,
+        "TAB & ICON" to Icons.Filled.Favorite,
+        "TAB 3 WITH LOTS OF TEXT" to Icons.Filled.Favorite
+    )
+    val showTabBadgeList = remember { mutableStateListOf(true, true) }
+
+    Column {
+        TabRow(selectedTabIndex = state) {
+            titlesAndIcons.forEachIndexed { index, (title, icon) ->
+                val showBadge: Boolean = when (index) {
+                    0 -> showTabBadgeList[0]
+                    1 -> tab1BadgeCount > 0
+                    2 -> showTabBadgeList[1]
+                    else -> false
+                }
+                LeadingIconTab(
+                    text = { Text(title) },
+                    icon = {
+                        if (!showBadge) {
+                            Icon(
+                                icon,
+                                contentDescription = "Localized description"
+                            )
+                        } else {
+                            DemoBadgeBox(
+                                when (index) {
+                                    1 -> tab1BadgeCount.toString()
+                                    2 -> "99+"
+                                    else -> null
+                                }
+                            ) {
+                                Icon(
+                                    Icons.Filled.Favorite,
+                                    contentDescription = "Localized description"
+                                )
+                            }
+                        }
+                    },
+                    selected = state == index,
+                    onClick = {
+                        state = index
+                        when (index) {
+                            0 -> showTabBadgeList[0] = false
+                            1 -> onTab1BadgeClick()
+                            2 -> showTabBadgeList[1] = false
+                        }
+                    }
+                )
+            }
+        }
+        Text(
+            modifier = Modifier.align(Alignment.CenterHorizontally),
+            text = "Leading icon tab ${state + 1} selected",
+            style = MaterialTheme.typography.body1
+        )
+    }
+}
+
+@OptIn(ExperimentalMaterialApi::class)
+@Composable
+private fun DemoBadgeBox(
+    badgeText: String?,
+    content: @Composable () -> Unit
+) {
+    if (badgeText.isNullOrEmpty()) {
+        BadgeBox { content() }
+    } else {
+        BadgeBox(
+            badgeContent = {
+                Text(
+                    badgeText,
+                    textAlign = TextAlign.Center,
+                    modifier = Modifier.semantics {
+                        this.contentDescription = "$badgeText notifications"
+                    }
+                )
+            }
+        ) {
+            content()
+        }
+    }
+}
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
index aeb2681..91459aa 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
@@ -44,6 +44,7 @@
         ),
         ComposableDemo("App Bars") { AppBarDemo() },
         ComposableDemo("Backdrop") { BackdropScaffoldSample() },
+        ComposableDemo("Badge") { BadgeDemo() },
         ComposableDemo("Bottom Navigation") { BottomNavigationDemo() },
         DemoCategory(
             "Bottom Sheets",
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BadgeSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BadgeSamples.kt
new file mode 100644
index 0000000..bdcda98
--- /dev/null
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BadgeSamples.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.material.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.material.BadgeBox
+import androidx.compose.material.BottomNavigation
+import androidx.compose.material.BottomNavigationItem
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.Icon
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.runtime.Composable
+
+@OptIn(ExperimentalMaterialApi::class)
+@Sampled
+@Composable
+fun BottomNavigationItemWithBadge() {
+    BottomNavigation {
+        BottomNavigationItem(
+            icon = {
+                BadgeBox(badgeContent = { Text("8") }) {
+                    Icon(
+                        Icons.Filled.Favorite,
+                        contentDescription = "Favorite"
+                    )
+                }
+            },
+            selected = false,
+            onClick = {}
+        )
+    }
+}
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeScreenshotTest.kt
new file mode 100644
index 0000000..4bb77c2
--- /dev/null
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeScreenshotTest.kt
@@ -0,0 +1,286 @@
+/*
+ * 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.material
+
+import android.os.Build
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@OptIn(ExperimentalMaterialApi::class, ExperimentalTestApi::class)
+class BadgeScreenshotTest {
+
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @get:Rule
+    val screenshotRule = AndroidXScreenshotTestRule(GOLDEN_MATERIAL)
+
+    @Test
+    fun darkTheme_withContent() {
+        composeTestRule.setContent {
+            MaterialTheme(darkColors()) {
+                Box(
+                    Modifier.size(56.dp).semantics(mergeDescendants = true) {}.testTag(TestTag),
+                    contentAlignment = Alignment.Center
+                ) {
+                    BadgeBox(badgeContent = { Text("8") }) {
+                        Icon(Icons.Filled.Favorite, null)
+                    }
+                }
+            }
+        }
+
+        assertBadgeAgainstGolden(
+            goldenIdentifier = "badge_darkTheme_withContent"
+        )
+    }
+
+    @Test
+    fun lightTheme_noContent_bottomNavigation() {
+        composeTestRule.setContent {
+            MaterialTheme(lightColors()) {
+                BottomNavigation(
+                    modifier = Modifier.semantics(mergeDescendants = true) {}.testTag(TestTag)
+                ) {
+                    BottomNavigationItem(
+                        icon = {
+                            BadgeBox {
+                                Icon(Icons.Filled.Favorite, null)
+                            }
+                        },
+                        selected = true,
+                        onClick = {},
+                    )
+                }
+            }
+        }
+
+        assertBadgeAgainstGolden(
+            goldenIdentifier = "badge_lightTheme_noContent_bottomNavigation"
+        )
+    }
+
+    @Test
+    fun lightTheme_shortContent_bottomNavigation() {
+        composeTestRule.setContent {
+            MaterialTheme(lightColors()) {
+                BottomNavigation(
+                    modifier = Modifier.semantics(mergeDescendants = true) {}.testTag(TestTag)
+                ) {
+                    BottomNavigationItem(
+                        icon = {
+                            BadgeBox(
+                                badgeContent = {
+                                    Text(
+                                        "8",
+                                        textAlign = TextAlign.Center
+                                    )
+                                }
+                            ) {
+                                Icon(Icons.Filled.Favorite, null)
+                            }
+                        },
+                        selected = false,
+                        onClick = {}
+                    )
+                }
+            }
+        }
+
+        assertBadgeAgainstGolden(
+            goldenIdentifier = "badge_lightTheme_shortContent_bottomNavigation"
+        )
+    }
+
+    @Test
+    fun lightTheme_longContent_bottomNavigation() {
+        composeTestRule.setContent {
+            MaterialTheme(lightColors()) {
+                BottomNavigation(
+                    modifier = Modifier.semantics(mergeDescendants = true) {}.testTag(TestTag)
+                ) {
+                    BottomNavigationItem(
+                        icon = {
+                            BadgeBox(
+                                badgeContent = {
+                                    Text(
+                                        "99+",
+                                        textAlign = TextAlign.Center
+                                    )
+                                }
+                            ) {
+                                Icon(Icons.Filled.Favorite, null)
+                            }
+                        },
+                        selected = false,
+                        onClick = {}
+                    )
+                }
+            }
+        }
+
+        assertBadgeAgainstGolden(
+            goldenIdentifier = "badge_lightTheme_longContent_bottomNavigation"
+        )
+    }
+
+    @Test
+    fun lightTheme_badge_noContent_tab() {
+        composeTestRule.setContent {
+            MaterialTheme(lightColors()) {
+                Tab(
+                    text = {
+                        BadgeBox {
+                            Text("TAB")
+                        }
+                    },
+                    selected = true,
+                    onClick = {},
+                    modifier = Modifier.semantics(mergeDescendants = true) {}.testTag(TestTag)
+                )
+            }
+        }
+
+        assertBadgeAgainstGolden(
+            goldenIdentifier = "badge_lightTheme_noContent_tab"
+        )
+    }
+
+    @Test
+    fun lightTheme_badge_shortContent_tab() {
+        composeTestRule.setContent {
+            MaterialTheme(lightColors()) {
+                // A round badge with the text `8` attached to a tab.
+                Tab(
+                    text = {
+                        BadgeBox(
+                            badgeContent = {
+                                Text(
+                                    "8",
+                                    textAlign = TextAlign.Center
+                                )
+                            }
+                        ) {
+                            Text("TAB")
+                        }
+                    },
+                    selected = true,
+                    onClick = {},
+                    modifier = Modifier.semantics(mergeDescendants = true) {}.testTag(TestTag)
+                )
+            }
+        }
+
+        assertBadgeAgainstGolden(
+            goldenIdentifier = "badge_lightTheme_shortContent_tab"
+        )
+    }
+
+    @Test
+    fun lightTheme_badge_longContent_tab() {
+        composeTestRule.setContent {
+            MaterialTheme(lightColors()) {
+                // Tab with a pilled shape badge with the text `99+`.
+                Tab(
+                    text = {
+                        BadgeBox(
+                            badgeContent = {
+                                Text(
+                                    "99+",
+                                    textAlign = TextAlign.Center
+                                )
+                            }
+                        ) {
+                            Text("TAB")
+                        }
+                    },
+                    selected = true,
+                    onClick = {},
+                    modifier = Modifier.semantics(mergeDescendants = true) {}.testTag(TestTag)
+                )
+            }
+        }
+
+        assertBadgeAgainstGolden(
+            goldenIdentifier = "badge_lightTheme_longContent_tab"
+        )
+    }
+
+    @Test
+    fun lightTheme_badge_shortContent_leadingIconTab() {
+        composeTestRule.setContent {
+            MaterialTheme(lightColors()) {
+                // A round badge with the text `8` attached to a leading icon tab.
+                LeadingIconTab(
+                    icon = {
+                        BadgeBox(
+                            badgeContent = {
+                                Text(
+                                    "8",
+                                    textAlign = TextAlign.Center
+                                )
+                            }
+                        ) {
+                            Icon(Icons.Filled.Favorite, null)
+                        }
+                    },
+                    text = {
+                        Text("TAB")
+                    },
+                    selected = true,
+                    onClick = {},
+                    modifier = Modifier.semantics(mergeDescendants = true) {}.testTag(TestTag)
+                )
+            }
+        }
+
+        assertBadgeAgainstGolden(
+            goldenIdentifier = "badge_lightTheme_shortContent_leadingIconTab"
+        )
+    }
+
+    private fun assertBadgeAgainstGolden(goldenIdentifier: String) {
+        composeTestRule.onNodeWithTag(TestTag)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, goldenIdentifier)
+    }
+}
+
+private const val TestTag = "badge"
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeTest.kt
new file mode 100644
index 0000000..9dade12
--- /dev/null
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeTest.kt
@@ -0,0 +1,290 @@
+/*
+ * 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.material
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.testutils.assertShape
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertContentDescriptionEquals
+import androidx.compose.ui.test.assertHasClickAction
+import androidx.compose.ui.test.assertHasNoClickAction
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertIsSelected
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertWidthIsAtLeast
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onParent
+import androidx.compose.ui.test.onSibling
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.height
+import androidx.compose.ui.unit.max
+import androidx.compose.ui.unit.width
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalMaterialApi::class)
+class BadgeTest {
+
+    private val icon = Icons.Filled.Favorite
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun badge_noContent_size() {
+        rule
+            .setMaterialContentForSizeAssertions {
+                Badge()
+            }
+            .assertHeightIsEqualTo(BadgeRadius * 2)
+            .assertWidthIsEqualTo(BadgeRadius * 2)
+    }
+
+    @Test
+    fun badge_shortContent_size() {
+        rule
+            .setMaterialContentForSizeAssertions {
+                Badge { Text("1") }
+            }
+            .assertHeightIsEqualTo(BadgeWithContentRadius * 2)
+            .assertWidthIsEqualTo(BadgeWithContentRadius * 2)
+    }
+
+    @Test
+    fun badge_longContent_size() {
+        rule
+            .setMaterialContentForSizeAssertions {
+                Badge { Text("999+") }
+            }
+            .assertHeightIsEqualTo(BadgeWithContentRadius * 2)
+            .assertWidthIsAtLeast(BadgeWithContentRadius * 2)
+    }
+
+    @Test
+    fun badge_shortContent_customSizeModifier_size() {
+        val customWidth = 24.dp
+        val customHeight = 6.dp
+        rule
+            .setMaterialContentForSizeAssertions {
+                Badge(modifier = Modifier.size(customWidth, customHeight)) {
+                    Text("1")
+                }
+            }
+            .assertHeightIsEqualTo(customHeight)
+            .assertWidthIsEqualTo(customWidth)
+    }
+
+    @Test
+    fun badge_noContent_shape() {
+        var errorColor = Color.Unspecified
+        rule.setMaterialContent {
+            errorColor = MaterialTheme.colors.error
+            Badge(modifier = Modifier.testTag(TestBadgeTag))
+        }
+
+        rule.onNodeWithTag(TestBadgeTag)
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = CircleShape,
+                shapeColor = errorColor,
+                backgroundColor = Color.White
+            )
+    }
+
+    @Test
+    fun badgeBox_shortContent_position() {
+        rule
+            .setMaterialContent {
+                BadgeBox(badgeContent = { Text("8") }) {
+                    Icon(
+                        icon,
+                        null,
+                        modifier = Modifier.testTag(TestAnchorTag)
+                    )
+                }
+            }
+        val badge = rule.onNodeWithTag(TestAnchorTag).onSibling()
+        val anchorBounds = rule.onNodeWithTag(TestAnchorTag).getUnclippedBoundsInRoot()
+        val badgeBounds = badge.getUnclippedBoundsInRoot()
+        badge.assertPositionInRootIsEqualTo(
+            expectedLeft = anchorBounds.right + BadgeWithContentHorizontalOffset + 4.dp + max
+            (
+                (
+                    BadgeWithContentRadius - badgeBounds.width
+                    ) / 2,
+                0.dp
+            ),
+            expectedTop = -badgeBounds.height / 2
+        )
+    }
+
+    @Test
+    fun badgeBox_longContent_position() {
+        rule
+            .setMaterialContent {
+                BadgeBox(badgeContent = { Text("999+") }) {
+                    Icon(
+                        icon,
+                        null,
+                        modifier = Modifier.testTag(TestAnchorTag)
+                    )
+                }
+            }
+        val badge = rule.onNodeWithTag(TestAnchorTag).onSibling()
+        val anchorBounds = rule.onNodeWithTag(TestAnchorTag).getUnclippedBoundsInRoot()
+        val badgeBounds = badge.getUnclippedBoundsInRoot()
+
+        val totalBadgeHorizontalOffset = BadgeWithContentHorizontalOffset +
+            BadgeWithContentHorizontalPadding
+        badge.assertPositionInRootIsEqualTo(
+            expectedLeft = anchorBounds.right + totalBadgeHorizontalOffset,
+            expectedTop = -badgeBounds.height / 2
+        )
+    }
+
+    @Test
+    fun badge_noClickAction_anchor_clickable() {
+        val count = mutableStateOf(0f)
+        rule.setMaterialContent {
+            Badge(modifier = Modifier.testTag(TestBadgeTag)) {
+                Tab(
+                    text = { Text("Text") },
+                    selected = true,
+                    onClick = { count.value += 1 },
+                    modifier = Modifier
+                        .testTag("tab")
+                )
+            }
+        }
+        rule.onNodeWithTag(TestBadgeTag)
+            .assertHasNoClickAction()
+        Truth.assertThat(count.value).isEqualTo(0)
+        rule.onNodeWithTag("tab")
+            .performClick()
+            .performClick()
+        Truth.assertThat(count.value).isEqualTo(2)
+    }
+
+    @Test
+    fun badge_on_tab_defaultSemantics() {
+        rule.setMaterialContent {
+            TabRow(0) {
+                Tab(
+                    text = { Badge { Text("Text") } },
+                    modifier = Modifier.testTag("tab"),
+                    selected = true,
+                    onClick = {}
+                )
+            }
+        }
+
+        rule.onNodeWithTag("tab")
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Tab))
+            .assertIsSelected()
+            .assertIsEnabled()
+            .assertHasClickAction()
+
+        rule.onNodeWithTag("tab")
+            .onParent()
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsProperties.SelectableGroup))
+    }
+
+    @Test
+    fun badge_on_tab_disabledSemantics() {
+        rule.setMaterialContent {
+            Box {
+                Tab(
+                    enabled = false,
+                    text = { Badge { Text("Text") } },
+                    modifier = Modifier.testTag("tab"),
+                    selected = true,
+                    onClick = {}
+                )
+            }
+        }
+
+        rule.onNodeWithTag("tab")
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Tab))
+            .assertIsSelected()
+            .assertIsNotEnabled()
+            .assertHasClickAction()
+    }
+
+    @Test
+    fun badge_notMergingDescendants_withOwnContentDescription() {
+        rule.setMaterialContent {
+            BadgeBox(
+                modifier = Modifier.testTag(TestBadgeTag).semantics {
+                    this.contentDescription = "more than 99 new email"
+                },
+                badgeContent = {
+                    Text("99+")
+                }
+            ) {
+                Text(
+                    "inbox",
+                    Modifier.semantics {
+                        this.contentDescription = "inbox"
+                    }.testTag(TestAnchorTag)
+                )
+            }
+        }
+
+        rule.onNodeWithTag(TestBadgeTag).assertContentDescriptionEquals("more than 99 new email")
+        rule.onNodeWithTag(TestAnchorTag).assertContentDescriptionEquals("inbox")
+    }
+
+    @Test
+    fun badgeBox_size() {
+        rule.setMaterialContentForSizeAssertions {
+            BadgeBox(badgeContent = { Text("999+") }) {
+                Icon(icon, null)
+            }
+        }
+            .assertWidthIsEqualTo(icon.defaultWidth)
+            .assertHeightIsEqualTo(icon.defaultHeight)
+    }
+}
+
+private const val TestBadgeTag = "badge"
+private const val TestAnchorTag = "anchor"
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt
index 072d276..7624397 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt
@@ -662,6 +662,56 @@
     }
 
     @Test
+    fun tabRowIndicator_animatesWidthChange() {
+        rule.mainClock.autoAdvance = false
+
+        rule.setMaterialContent {
+            var state by remember { mutableStateOf(0) }
+            val titles = listOf("TAB 1", "TAB 2", "TAB 3 WITH LOTS OF TEXT")
+
+            val indicator = @Composable { tabPositions: List<TabPosition> ->
+                TabRowDefaults.Indicator(
+                    Modifier.tabIndicatorOffset(tabPositions[state])
+                        .testTag("indicator")
+                )
+            }
+
+            Box {
+                ScrollableTabRow(
+                    selectedTabIndex = state,
+                    indicator = indicator
+                ) {
+                    titles.forEachIndexed { index, title ->
+                        Tab(
+                            text = { Text(title) },
+                            selected = state == index,
+                            onClick = { state = index }
+                        )
+                    }
+                }
+            }
+        }
+
+        val initialWidth = rule.onNodeWithTag("indicator").getUnclippedBoundsInRoot().width
+
+        // Click the third tab, which is wider than the first
+        rule.onAllNodes(isSelectable())[2].performClick()
+
+        // Ensure animation starts
+        rule.mainClock.advanceTimeBy(50)
+
+        val midAnimationWidth = rule.onNodeWithTag("indicator").getUnclippedBoundsInRoot().width
+        assertThat(initialWidth).isLessThan(midAnimationWidth)
+
+        // Allow animation to complete
+        rule.mainClock.autoAdvance = true
+        rule.waitForIdle()
+
+        val finalWidth = rule.onNodeWithTag("indicator").getUnclippedBoundsInRoot().width
+        assertThat(midAnimationWidth).isLessThan(finalWidth)
+    }
+
+    @Test
     fun testInspectorValue() {
         val pos = TabPosition(10.0.dp, 200.0.dp)
         rule.setContent {
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Badge.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Badge.kt
new file mode 100644
index 0000000..185fa4e
--- /dev/null
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Badge.kt
@@ -0,0 +1,186 @@
+/*
+ * 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.material
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.FirstBaseline
+import androidx.compose.ui.layout.LastBaseline
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+/**
+ * A BadgeBox is used to decorate [content] with a badge that can contain dynamic information, such
+ * as the presence of a new notification or a number of pending requests. Badges can be icon only
+ * or contain short text.
+ *
+ * A common use case is to display a badge in the upper right corner of bottom navigation items.
+ * For more information, see [Bottom Navigation](https://material.io/components/bottom-navigation#behavior)
+ *
+ * A simple icon with badge example looks like:
+ * @sample androidx.compose.material.samples.BottomNavigationItemWithBadge
+ *
+ * @param modifier optional [Modifier] for this item
+ * @param backgroundColor the background color for the badge
+ * @param contentColor the color of label text rendered in the badge
+ * @param badgeContent optional content to be rendered inside the badge
+ * @param content the anchor to which this badge will be positioned
+ *
+ */
+@ExperimentalMaterialApi
+@Composable
+fun BadgeBox(
+    modifier: Modifier = Modifier,
+    backgroundColor: Color = MaterialTheme.colors.error,
+    contentColor: Color = contentColorFor(backgroundColor),
+    badgeContent: @Composable (RowScope.() -> Unit)? = null,
+    content: @Composable BoxScope.() -> Unit,
+) {
+    val badgeHorizontalOffset =
+        if (badgeContent != null) BadgeWithContentHorizontalOffset else BadgeHorizontalOffset
+    Layout(
+        {
+            Box(
+                modifier = Modifier.layoutId("anchor"),
+                contentAlignment = Alignment.Center,
+                content = content
+            )
+            Badge(
+                modifier = Modifier.layoutId("badge"),
+                backgroundColor = backgroundColor,
+                contentColor = contentColor,
+                content = badgeContent
+            )
+        },
+        modifier = modifier
+    ) { measurables, constraints ->
+
+        val badgePlaceable = measurables.first { it.layoutId == "badge" }.measure(
+            // Measure with loose constraints for height as we don't want the text to take up more
+            // space than it needs.
+            constraints.copy(minHeight = 0)
+        )
+
+        val anchorPlaceable = measurables.first { it.layoutId == "anchor" }.measure(constraints)
+
+        val firstBaseline = anchorPlaceable[FirstBaseline]
+        val lastBaseline = anchorPlaceable[LastBaseline]
+        val totalWidth = anchorPlaceable.width
+        val totalHeight = anchorPlaceable.height
+
+        layout(
+            totalWidth,
+            totalHeight,
+            // Provide custom baselines based only on the anchor content to avoid default baseline
+            // calculations from including by any badge content.
+            mapOf(
+                FirstBaseline to firstBaseline,
+                LastBaseline to lastBaseline
+            )
+        ) {
+            anchorPlaceable.placeRelative(0, 0)
+            val badgeX = anchorPlaceable.width + badgeHorizontalOffset.roundToPx()
+            val badgeY = -badgePlaceable.height / 2
+            badgePlaceable.placeRelative(badgeX, badgeY)
+        }
+    }
+}
+
+/**
+ * Internal badge implementation to help [BadgeBox].
+ *
+ * @param modifier optional [Modifier] for this item
+ * @param backgroundColor the background color for the badge
+ * @param contentColor the color of label text rendered in the badge
+ * @param content optional content to be rendered inside the badge
+ *
+ * @see BadgeBox
+ */
+@ExperimentalMaterialApi
+@Composable
+internal fun Badge(
+    modifier: Modifier = Modifier,
+    backgroundColor: Color = MaterialTheme.colors.error,
+    contentColor: Color = contentColorFor(backgroundColor),
+    content: @Composable (RowScope.() -> Unit)? = null,
+) {
+    val radius = if (content != null) BadgeWithContentRadius else BadgeRadius
+    val shape = RoundedCornerShape(radius)
+
+    // Draw badge container.
+    Row(
+        modifier = modifier
+            .defaultMinSize(minWidth = radius * 2, minHeight = radius * 2)
+            .background(
+                color = backgroundColor,
+                shape = shape
+            )
+            .clip(shape)
+            .padding(
+                horizontal = BadgeWithContentHorizontalPadding
+            ),
+        verticalAlignment = Alignment.CenterVertically,
+        horizontalArrangement = Arrangement.Center
+    ) {
+        if (content != null) {
+            CompositionLocalProvider(
+                LocalContentColor provides contentColor
+            ) {
+                val style = MaterialTheme.typography.button.copy(fontSize = BadgeContentFontSize)
+                ProvideTextStyle(
+                    value = style,
+                    content = { content() }
+                )
+            }
+        }
+    }
+}
+
+/*@VisibleForTesting*/
+internal val BadgeRadius = 4.dp
+
+/*@VisibleForTesting*/
+internal val BadgeWithContentRadius = 8.dp
+private val BadgeContentFontSize = 10.sp
+
+/*@VisibleForTesting*/
+// Leading and trailing text padding when a badge is displaying text that is too long to fit in
+// a circular badge, e.g. if badge number is greater than 9.
+internal val BadgeWithContentHorizontalPadding = 4.dp
+
+/*@VisibleForTesting*/
+// Horizontally align start/end of text badge 6dp from the end/start edge of its anchor
+internal val BadgeWithContentHorizontalOffset = -6.dp
+
+/*@VisibleForTesting*/
+// Horizontally align start/end of icon only badge 4dp from the end/start edge of anchor
+internal val BadgeHorizontalOffset = -4.dp
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TabRow.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TabRow.kt
index dd3d145..e28ed8a 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TabRow.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TabRow.kt
@@ -399,9 +399,10 @@
             value = currentTabPosition
         }
     ) {
-        // TODO: should we animate the width of the indicator as it moves between tabs of different
-        // sizes inside a scrollable tab row?
-        val currentTabWidth = currentTabPosition.width
+        val currentTabWidth by animateDpAsState(
+            targetValue = currentTabPosition.width,
+            animationSpec = tween(durationMillis = 250, easing = FastOutSlowInEasing)
+        )
         val indicatorOffset by animateDpAsState(
             targetValue = currentTabPosition.left,
             animationSpec = tween(durationMillis = 250, easing = FastOutSlowInEasing)
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
index bf2ce17..58952df 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
@@ -1353,7 +1353,7 @@
     @Suppress("UNUSED")
     override fun <T> createNode(factory: () -> T) {
         validateNodeExpected()
-        check(inserting) { "createNode() can only be called when inserting" }
+        runtimeCheck(inserting) { "createNode() can only be called when inserting" }
         val insertIndex = nodeIndexStack.peek()
         val groupAnchor = writer.anchor(writer.parent)
         groupNodeCount++
@@ -1380,7 +1380,7 @@
     @OptIn(InternalComposeApi::class)
     override fun useNode() {
         validateNodeExpected()
-        check(!inserting) { "useNode() called while inserting" }
+        runtimeCheck(!inserting) { "useNode() called while inserting" }
         recordDown(reader.node)
     }
 
@@ -2412,7 +2412,9 @@
      */
     @ComposeCompilerApi
     override fun skipToGroupEnd() {
-        check(groupNodeCount == 0) { "No nodes can be emitted before calling skipAndEndGroup" }
+        runtimeCheck(groupNodeCount == 0) {
+            "No nodes can be emitted before calling skipAndEndGroup"
+        }
         currentRecomposeScope?.scopeSkipped()
         if (invalidations.isEmpty()) {
             skipReaderToGroupEnd()
@@ -2511,12 +2513,12 @@
         invalidationsRequested: IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>,
         content: @Composable () -> Unit
     ) {
-        check(changes.isEmpty()) { "Expected applyChanges() to have been called" }
+        runtimeCheck(changes.isEmpty()) { "Expected applyChanges() to have been called" }
         doCompose(invalidationsRequested, content)
     }
 
     internal fun prepareCompose(block: () -> Unit) {
-        check(!isComposing) { "Preparing a composition while composing is not supported" }
+        runtimeCheck(!isComposing) { "Preparing a composition while composing is not supported" }
         isComposing = true
         try {
             block()
@@ -2531,7 +2533,7 @@
     internal fun recompose(
         invalidationsRequested: IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>
     ): Boolean {
-        check(changes.isEmpty()) { "Expected applyChanges() to have been called" }
+        runtimeCheck(changes.isEmpty()) { "Expected applyChanges() to have been called" }
         // even if invalidationsRequested is empty we still need to recompose if the Composer has
         // some invalidations scheduled already. it can happen when during some parent composition
         // there were a change for a state which was used by the child composition. such changes
@@ -2547,7 +2549,7 @@
         invalidationsRequested: IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>,
         content: (@Composable () -> Unit)?
     ) {
-        check(!isComposing) { "Reentrant composition is not supported" }
+        runtimeCheck(!isComposing) { "Reentrant composition is not supported" }
         trace("Compose:recompose") {
             snapshot = currentSnapshot()
             invalidationsRequested.forEach { scope, set ->
@@ -2596,14 +2598,14 @@
     private fun SlotReader.nodeAt(index: Int) = node(index)
 
     private fun validateNodeExpected() {
-        check(nodeExpected) {
+        runtimeCheck(nodeExpected) {
             "A call to createNode(), emitNode() or useNode() expected was not expected"
         }
         nodeExpected = false
     }
 
     private fun validateNodeNotExpected() {
-        check(!nodeExpected) { "A call to createNode(), emitNode() or useNode() expected" }
+        runtimeCheck(!nodeExpected) { "A call to createNode(), emitNode() or useNode() expected" }
     }
 
     /**
@@ -2825,7 +2827,7 @@
     private fun recordEndGroup() {
         val location = reader.parent
         val currentStartedGroup = startedGroups.peekOr(-1)
-        check(currentStartedGroup <= location) { "Missed recording an endGroup" }
+        runtimeCheck(currentStartedGroup <= location) { "Missed recording an endGroup" }
         if (startedGroups.peekOr(-1) == location) {
             startedGroups.pop()
             recordSlotTableOperation(change = endGroupInstance)
@@ -2841,8 +2843,8 @@
 
     private fun finalizeCompose() {
         realizeUps()
-        check(pendingStack.isEmpty()) { "Start/end imbalance" }
-        check(startedGroups.isEmpty()) { "Missed recording an endGroup()" }
+        runtimeCheck(pendingStack.isEmpty()) { "Start/end imbalance" }
+        runtimeCheck(startedGroups.isEmpty()) { "Missed recording an endGroup()" }
         cleanUpCompose()
     }
 
@@ -2866,7 +2868,7 @@
 
     private fun recordRemoveNode(nodeIndex: Int, count: Int) {
         if (count > 0) {
-            check(nodeIndex >= 0) { "Invalid remove index $nodeIndex" }
+            runtimeCheck(nodeIndex >= 0) { "Invalid remove index $nodeIndex" }
             if (previousRemove == nodeIndex) previousCount += count
             else {
                 realizeMovement()
@@ -3435,3 +3437,20 @@
 
 @PublishedApi
 internal const val reuseKey = 207
+
+internal inline fun runtimeCheck(value: Boolean, lazyMessage: () -> Any) {
+    if (!value) {
+        val message = lazyMessage()
+        composeRuntimeError(message.toString())
+    }
+}
+
+internal fun runtimeCheck(value: Boolean) = runtimeCheck(value) { "Check failed" }
+
+internal fun composeRuntimeError(message: String): Nothing {
+    error(
+        "Compose Runtime internal error. Unexpected or incorrect use of the Compose " +
+            "internal runtime API ($message). Please report to Google or use " +
+            "https://goo.gle/compose-feedback"
+    )
+}
\ No newline at end of file
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
index 8e8a725..10ffa2c 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
@@ -186,8 +186,8 @@
      * @see SlotWriter
      */
     fun openWriter(): SlotWriter {
-        check(!writer) { "Cannot start a writer when another writer is pending" }
-        check(readers <= 0) { "Cannot start a writer when a reader is pending" }
+        runtimeCheck(!writer) { "Cannot start a writer when another writer is pending" }
+        runtimeCheck(readers <= 0) { "Cannot start a writer when a reader is pending" }
         writer = true
         version++
         return SlotWriter(this)
@@ -200,7 +200,7 @@
      * might be affected by the modifications being performed by the [SlotWriter].
      */
     fun anchorIndex(anchor: Anchor): Int {
-        check(!writer) { "Use active SlotWriter to determine anchor location instead" }
+        runtimeCheck(!writer) { "Use active SlotWriter to determine anchor location instead" }
         require(anchor.valid) { "Anchor refers to a group that was removed" }
         return anchor.location
     }
@@ -1120,7 +1120,7 @@
      */
     fun updateAux(value: Any?) {
         val address = groupIndexToAddress(currentGroup)
-        check(groups.hasAux(address)) {
+        runtimeCheck(groups.hasAux(address)) {
             "Updating the data of a group that was not created with a data slot"
         }
         slots[dataIndexToDataAddress(groups.auxIndex(address))] = value
@@ -1133,10 +1133,10 @@
      * the group.
      */
     fun insertAux(value: Any?) {
-        check(insertCount >= 0) { "Cannot insert auxiliary data when not inserting" }
+        runtimeCheck(insertCount >= 0) { "Cannot insert auxiliary data when not inserting" }
         val parent = parent
         val parentGroupAddress = groupIndexToAddress(parent)
-        check(!groups.hasAux(parentGroupAddress)) { "Group already has auxiliary data" }
+        runtimeCheck(!groups.hasAux(parentGroupAddress)) { "Group already has auxiliary data" }
         insertSlots(1, parent)
         val auxIndex = groups.auxIndex(parentGroupAddress)
         val auxAddress = dataIndexToDataAddress(auxIndex)
@@ -1176,7 +1176,7 @@
      * Set the value at the groups current data slot
      */
     fun set(value: Any?) {
-        check(currentSlot <= currentSlotEnd) {
+        runtimeCheck(currentSlot <= currentSlotEnd) {
             "Writing to an invalid slot"
         }
         slots[dataIndexToDataAddress(currentSlot - 1)] = value
@@ -1191,7 +1191,7 @@
         val slotsEnd = groups.dataIndex(groupIndexToAddress(currentGroup + 1))
         val slotsIndex = slotsStart + index
         @Suppress("ConvertTwoComparisonsToRangeCheck")
-        check(slotsIndex >= slotsStart && slotsIndex < slotsEnd) {
+        runtimeCheck(slotsIndex >= slotsStart && slotsIndex < slotsEnd) {
             "Write to an invalid slot index $index for group $currentGroup"
         }
         val slotAddress = dataIndexToDataAddress(slotsIndex)
@@ -1220,7 +1220,7 @@
         check(insertCount <= 0) { "Cannot call seek() while inserting" }
         val index = currentGroup + amount
         @Suppress("ConvertTwoComparisonsToRangeCheck")
-        check(index >= parent && index <= currentGroupEnd) {
+        runtimeCheck(index >= parent && index <= currentGroupEnd) {
             "Cannot seek outside the current group ($parent-$currentGroupEnd)"
         }
         this.currentGroup = index
@@ -1260,7 +1260,7 @@
     fun endInsert() {
         check(insertCount > 0) { "Unbalanced begin/end insert" }
         if (--insertCount == 0) {
-            check(nodeCountStack.size == startStack.size) {
+            runtimeCheck(nodeCountStack.size == startStack.size) {
                 "startGroup/endGroup mismatch while inserting"
             }
             restoreCurrentGroupEnd()
@@ -1639,7 +1639,7 @@
 
         //  7) remove the old groups
         val anchorsRemoved = removeGroups(groupToMove + moveLen, moveLen)
-        check(!anchorsRemoved) { "Unexpectedly removed anchors" }
+        runtimeCheck(!anchorsRemoved) { "Unexpectedly removed anchors" }
 
         //  8) fix parent anchors
         fixParentAnchorsFor(parent, currentGroupEnd, current)
@@ -1793,7 +1793,7 @@
             }
 
             // Ensure we correctly transplanted the correct groups.
-            check(!anchorsRemoved) { "Unexpectedly removed anchors" }
+            runtimeCheck(!anchorsRemoved) { "Unexpectedly removed anchors" }
 
             // Update the node count.
             nodeCount += if (groups.isNode(currentGroup)) 1 else groups.nodeCount(currentGroup)
@@ -1895,7 +1895,7 @@
             // anchors that refer to these groups must be updated.
             var groupAddress = if (index < gapStart) index + gapLen else gapStart
             val capacity = capacity
-            check(groupAddress < capacity)
+            runtimeCheck(groupAddress < capacity)
             while (groupAddress < capacity) {
                 val oldAnchor = groups.parentAnchor(groupAddress)
                 val oldIndex = parentAnchorToIndex(oldAnchor)
@@ -1951,7 +1951,7 @@
                 val groupGapStart = groupGapStart
                 while (updateAddress < stopUpdateAddress) {
                     val anchor = groups.dataAnchor(updateAddress)
-                    check(anchor >= 0) {
+                    runtimeCheck(anchor >= 0) {
                         "Unexpected anchor value, expected a positive anchor"
                     }
                     groups.updateDataAnchor(updateAddress, -(slotsSize - anchor + 1))
@@ -1963,7 +1963,7 @@
                 val stopUpdateAddress = groupIndexToAddress(newSlotsGapOwner)
                 while (updateAddress < stopUpdateAddress) {
                     val anchor = groups.dataAnchor(updateAddress)
-                    check(anchor < 0) {
+                    runtimeCheck(anchor < 0) {
                         "Unexpected anchor value, expected a negative anchor"
                     }
                     groups.updateDataAnchor(updateAddress, slotsSize + anchor + 1)
@@ -2148,7 +2148,7 @@
      */
     private fun updateNodeOfGroup(index: Int, value: Any?) {
         val address = groupIndexToAddress(index)
-        check(address < groups.size && groups.isNode(address)) {
+        runtimeCheck(address < groups.size && groups.isNode(address)) {
             "Updating the node of a group at $index that was not created with as a node group"
         }
         slots[dataIndexToDataAddress(groups.nodeIndex(address))] = value
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionTests.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionTests.kt
index feb751b..2ad6df1 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionTests.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionTests.kt
@@ -3003,6 +3003,15 @@
             assertEquals(1, nodes[1].changeCount, "node 1 recomposition changeCount")
         }
     }
+
+    @Test
+    fun internalErrorsAreReportedAsInternal() = compositionTest {
+        expectError("internal") {
+            compose {
+                currentComposer.createNode { null }
+            }
+        }
+    }
 }
 
 var stateA by mutableStateOf(1000)
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/SlotTableTests.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/SlotTableTests.kt
index f5498d2..b68170b 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/SlotTableTests.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/SlotTableTests.kt
@@ -3228,6 +3228,14 @@
             reader.endGroup()
         }
     }
+
+    @Test
+    fun incorrectUsageReportsInternalException() = expectError("internal") {
+        val table = SlotTable()
+        table.write {
+            table.write { }
+        }
+    }
 }
 
 @OptIn(InternalComposeApi::class)
@@ -3479,4 +3487,21 @@
         list.add(next())
     }
     return list
+}
+
+internal fun expectError(message: String, block: () -> Unit) {
+    var exceptionThrown = false
+    try {
+        block()
+    } catch (e: Throwable) {
+        exceptionThrown = true
+        assertTrue(
+            e.message?.contains(message) == true,
+            "Expected \"${e.message}\" to contain \"$message\""
+        )
+    }
+    assertTrue(
+        exceptionThrown,
+        "Expected test to throw an exception containing \"$message\""
+    )
 }
\ No newline at end of file
diff --git a/compose/ui/ui/api/public_plus_experimental_1.0.0-beta10.txt b/compose/ui/ui/api/public_plus_experimental_1.0.0-beta10.txt
index e9f571b..409ec8a 100644
--- a/compose/ui/ui/api/public_plus_experimental_1.0.0-beta10.txt
+++ b/compose/ui/ui/api/public_plus_experimental_1.0.0-beta10.txt
@@ -343,8 +343,6 @@
   public interface FocusManager {
     method public void clearFocus(optional boolean force);
     method public boolean moveFocus-3ESFkO8(int focusDirection);
-    method @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public default boolean moveFocusIn();
-    method @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public default boolean moveFocusOut();
   }
 
   public final class FocusModifierKt {
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index e9f571b..409ec8a 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -343,8 +343,6 @@
   public interface FocusManager {
     method public void clearFocus(optional boolean force);
     method public boolean moveFocus-3ESFkO8(int focusDirection);
-    method @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public default boolean moveFocusIn();
-    method @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public default boolean moveFocusOut();
   }
 
   public final class FocusModifierKt {
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt
index 8d4b434..5e56360 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt
@@ -20,6 +20,7 @@
 import androidx.compose.integration.demos.common.ComposableDemo
 import androidx.compose.integration.demos.common.DemoCategory
 import androidx.compose.ui.demos.autofill.ExplicitAutofillTypesDemo
+import androidx.compose.ui.demos.focus.CaptureFocusDemo
 import androidx.compose.ui.demos.focus.CustomFocusOrderDemo
 import androidx.compose.ui.demos.focus.FocusInDialogDemo
 import androidx.compose.ui.demos.focus.FocusInPopupDemo
@@ -118,7 +119,8 @@
         ComposableDemo("Reuse Focus Requester") { ReuseFocusRequesterDemo() },
         ComposableDemo("Focus Search") { FocusSearchDemo() },
         ComposableDemo("Custom Focus Order") { CustomFocusOrderDemo() },
-        ComposableDemo("FocusManager.moveFocus()") { FocusManagerMoveFocusDemo() }
+        ComposableDemo("FocusManager.moveFocus()") { FocusManagerMoveFocusDemo() },
+        ComposableDemo("Capture/Free Focus") { CaptureFocusDemo() }
     )
 )
 
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CaptureFocusDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CaptureFocusDemo.kt
new file mode 100644
index 0000000..fc4646b
--- /dev/null
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CaptureFocusDemo.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.demos.focus
+
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.material.Text
+import androidx.compose.material.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.Color.Companion.Red
+import androidx.compose.ui.graphics.Color.Companion.Transparent
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun CaptureFocusDemo() {
+    Column {
+        Text(
+            "This demo demonstrates how a component can capture focus when it is in an " +
+                "invalidated state."
+        )
+
+        Spacer(Modifier.height(30.dp))
+
+        Text("Enter a word that is 5 characters or shorter")
+        val shortWord = remember { FocusRequester() }
+        var shortString by remember { mutableStateOf("apple") }
+        var shortStringBorder by remember { mutableStateOf(Transparent) }
+        TextField(
+            value = shortString,
+            onValueChange = {
+                shortString = it
+                if (shortString.length > 5) shortWord.captureFocus() else shortWord.freeFocus()
+            },
+            modifier = Modifier
+                .border(2.dp, shortStringBorder)
+                .focusRequester(shortWord)
+                .onFocusChanged { shortStringBorder = if (it.isCaptured) Red else Transparent }
+        )
+
+        Spacer(Modifier.height(30.dp))
+
+        Text("Enter a word that is longer than 5 characters")
+        val longWord = remember { FocusRequester() }
+        var longString by remember { mutableStateOf("pineapple") }
+        var longStringBorder by remember { mutableStateOf(Transparent) }
+
+        TextField(
+            value = longString,
+            onValueChange = {
+                longString = it
+                if (longString.length < 5) longWord.captureFocus() else longWord.freeFocus()
+            },
+            modifier = Modifier
+                .border(2.dp, longStringBorder)
+                .focusRequester(longWord)
+                .onFocusChanged { longStringBorder = if (it.isCaptured) Red else Transparent }
+        )
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CustomFocusOrderDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CustomFocusOrderDemo.kt
index f31814e..e0e0f56 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CustomFocusOrderDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CustomFocusOrderDemo.kt
@@ -56,7 +56,7 @@
             )
         }
         Column(Modifier.fillMaxSize(), SpaceEvenly) {
-            val (item1, item2, item3, item4) = FocusRequester.createRefs()
+            val (item1, item2, item3, item4) = remember { FocusRequester.createRefs() }
             Row(Modifier.fillMaxWidth(), SpaceEvenly) {
                 FocusableText(
                     text = "1",
diff --git a/compose/ui/ui/src/proguard-rules.pro b/compose/ui/ui/proguard-rules.pro
similarity index 81%
rename from compose/ui/ui/src/proguard-rules.pro
rename to compose/ui/ui/proguard-rules.pro
index 45df3a1..0e89990 100644
--- a/compose/ui/ui/src/proguard-rules.pro
+++ b/compose/ui/ui/proguard-rules.pro
@@ -18,6 +18,10 @@
 -dontwarn android.view.RenderNode
 -dontwarn android.view.DisplayListCanvas
 
--keepclassmember class androidx.compose.ui.platform.ViewLayerContainer {
+-keepclassmembers class androidx.compose.ui.platform.ViewLayerContainer {
     protected void dispatchGetDisplayList();
 }
+
+-keepclassmembers class androidx.compose.ui.platform.AndroidComposeView {
+    android.view.View findViewByAccessibilityIdTraversal(int);
+}
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
index f01d362..8f0da4a 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
@@ -17,16 +17,112 @@
 package androidx.compose.ui.samples
 
 import androidx.annotation.Sampled
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
 import androidx.compose.material.Button
 import androidx.compose.material.Text
+import androidx.compose.material.TextField
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusDirection
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusOrder
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.focusTarget
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.Color.Companion.Black
+import androidx.compose.ui.graphics.Color.Companion.Green
+import androidx.compose.ui.graphics.Color.Companion.Red
+import androidx.compose.ui.graphics.Color.Companion.Transparent
 import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.unit.dp
+
+@Sampled
+@Composable
+fun FocusableSample() {
+    var color by remember { mutableStateOf(Black) }
+    Box(
+        Modifier
+            .border(2.dp, color)
+            // The onFocusChanged should be added BEFORE the focusable that is being observed.
+            .onFocusChanged { color = if (it.isFocused) Green else Black }
+            .focusable()
+    )
+}
+
+@Sampled
+@Composable
+fun FocusableSampleUsingLowerLevelFocusTarget() {
+    var color by remember { mutableStateOf(Black) }
+    Box(
+        Modifier
+            .border(2.dp, color)
+            // The onFocusChanged should be added BEFORE the focusTarget that is being observed.
+            .onFocusChanged { color = if (it.isFocused) Green else Black }
+            .focusTarget()
+    )
+}
+
+@Sampled
+@Composable
+fun CaptureFocusSample() {
+    val focusRequester = remember { FocusRequester() }
+    var value by remember { mutableStateOf("apple") }
+    var borderColor by remember { mutableStateOf(Transparent) }
+    TextField(
+        value = value,
+        onValueChange = {
+            value = it.apply {
+                if (length > 5) focusRequester.captureFocus() else focusRequester.freeFocus()
+            }
+        },
+        modifier = Modifier
+            .border(2.dp, borderColor)
+            .focusRequester(focusRequester)
+            .onFocusChanged { borderColor = if (it.isCaptured) Red else Transparent }
+    )
+}
+
+@Sampled
+@Composable
+fun RequestFocusSample() {
+    val focusRequester = remember { FocusRequester() }
+    var color by remember { mutableStateOf(Black) }
+    Box(
+        Modifier
+            .clickable { focusRequester.requestFocus() }
+            .border(2.dp, color)
+            // The focusRequester should be added BEFORE the focusable.
+            .focusRequester(focusRequester)
+            // The onFocusChanged should be added BEFORE the focusable that is being observed.
+            .onFocusChanged { color = if (it.isFocused) Green else Black }
+            .focusable()
+    )
+}
+
+@Sampled
+@Composable
+fun ClearFocusSample() {
+    val focusManager = LocalFocusManager.current
+    Column(Modifier.clickable { focusManager.clearFocus() }) {
+        Box(Modifier.focusable().size(100.dp))
+        Box(Modifier.focusable().size(100.dp))
+        Box(Modifier.focusable().size(100.dp))
+    }
+}
 
 @Sampled
 @Composable
@@ -46,4 +142,66 @@
         Button(onClick = { focusManager.moveFocus(FocusDirection.Up) }) { Text("Up") }
         Button(onClick = { focusManager.moveFocus(FocusDirection.Down) }) { Text("Down") }
     }
-}
\ No newline at end of file
+}
+
+@ExperimentalComposeUiApi
+@Sampled
+@Composable
+fun CreateFocusRequesterRefsSample() {
+    val (item1, item2, item3, item4) = remember { FocusRequester.createRefs() }
+    Column {
+        Box(Modifier.focusRequester(item1).focusable())
+        Box(Modifier.focusRequester(item2).focusable())
+        Box(Modifier.focusRequester(item3).focusable())
+        Box(Modifier.focusRequester(item4).focusable())
+    }
+}
+
+@ExperimentalComposeUiApi
+@Sampled
+@Composable
+fun CustomFocusOrderSample() {
+    Column(Modifier.fillMaxSize(), Arrangement.SpaceEvenly) {
+        val (item1, item2, item3, item4) = remember { FocusRequester.createRefs() }
+        Row(Modifier.fillMaxWidth(), Arrangement.SpaceEvenly) {
+            Box(
+                Modifier
+                    .focusOrder(item1) {
+                        next = item2
+                        right = item2
+                        down = item3
+                        previous = item4
+                    }
+                    .focusable()
+            )
+            Box(
+                Modifier
+                    .focusOrder(item2) {
+                        next = item3
+                        right = item1
+                        down = item4
+                        previous = item1
+                    }
+                    .focusable()
+            )
+        }
+        Row(Modifier.fillMaxWidth(), Arrangement.SpaceEvenly) {
+            Box(
+                Modifier.focusOrder(item3) {
+                    next = item4
+                    right = item4
+                    up = item1
+                    previous = item2
+                }
+            )
+            Box(
+                Modifier.focusOrder(item4) {
+                    next = item1
+                    left = item3
+                    up = item2
+                    previous = item3
+                }
+            )
+        }
+    }
+}
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/KeyInputSamples.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/KeyInputSamples.kt
new file mode 100644
index 0000000..d8a498a
--- /dev/null
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/KeyInputSamples.kt
@@ -0,0 +1,147 @@
+/*
+ * 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.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyUp
+import androidx.compose.ui.input.key.KeyEventType.Companion.Unknown
+import androidx.compose.ui.input.key.isAltPressed
+import androidx.compose.ui.input.key.isCtrlPressed
+import androidx.compose.ui.input.key.isMetaPressed
+import androidx.compose.ui.input.key.isShiftPressed
+import androidx.compose.ui.input.key.key
+import androidx.compose.ui.input.key.onKeyEvent
+import androidx.compose.ui.input.key.onPreviewKeyEvent
+import androidx.compose.ui.input.key.type
+
+@Suppress("UNUSED_ANONYMOUS_PARAMETER")
+@Sampled
+@Composable
+fun KeyEventSample() {
+    // When the inner Box is focused, and the user presses a key, the key goes down the hierarchy
+    // and then back up to the parent. At any stage you can stop the propagation by returning
+    // true to indicate that you consumed the event.
+    Box(
+        Modifier
+            .onPreviewKeyEvent { keyEvent1 -> false }
+            .onKeyEvent { keyEvent4 -> false }
+    ) {
+        Box(
+            Modifier
+                .onPreviewKeyEvent { keyEvent2 -> false }
+                .onKeyEvent { keyEvent3 -> false }
+                .focusable()
+        )
+    }
+}
+
+@Sampled
+@Composable
+fun KeyEventTypeSample() {
+    Box(
+        Modifier
+            .onKeyEvent {
+                when (it.type) {
+                    KeyUp -> println(" KeyUp Pressed")
+                    KeyDown -> println(" KeyUp Pressed")
+                    Unknown -> println("Unknown key type")
+                    else -> println("New KeyTpe (For Future Use)")
+                }
+                false
+            }
+            .focusable()
+    )
+}
+
+@ExperimentalComposeUiApi
+@Sampled
+@Composable
+fun KeyEventIsAltPressedSample() {
+    Box(
+        Modifier
+            .onKeyEvent {
+                if (it.isAltPressed && it.key == Key.A) {
+                    println("Alt + A is pressed")
+                    true
+                } else {
+                    false
+                }
+            }
+            .focusable()
+    )
+}
+
+@ExperimentalComposeUiApi
+@Sampled
+@Composable
+fun KeyEventIsCtrlPressedSample() {
+    Box(
+        Modifier
+            .onKeyEvent {
+                if (it.isCtrlPressed && it.key == Key.A) {
+                    println("Ctrl + A is pressed")
+                    true
+                } else {
+                    false
+                }
+            }
+            .focusable()
+    )
+}
+
+@ExperimentalComposeUiApi
+@Sampled
+@Composable
+fun KeyEventIsMetaPressedSample() {
+    Box(
+        Modifier
+            .onKeyEvent {
+                if (it.isMetaPressed && it.key == Key.A) {
+                    println("Meta + A is pressed")
+                    true
+                } else {
+                    false
+                }
+            }
+            .focusable()
+    )
+}
+
+@ExperimentalComposeUiApi
+@Sampled
+@Composable
+fun KeyEventIsShiftPressedSample() {
+    Box(
+        Modifier
+            .onKeyEvent {
+                if (it.isShiftPressed && it.key == Key.A) {
+                    println("Shift + A is pressed")
+                    true
+                } else {
+                    false
+                }
+            }
+            .focusable()
+    )
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
index c50261e..e4157fd 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
@@ -291,7 +291,7 @@
         assertEquals("Selected", stateDescription)
         assertFalse(accessibilityNodeInfo.isClickable)
         assertTrue(accessibilityNodeInfo.isVisibleToUser)
-        assertFalse(accessibilityNodeInfo.isCheckable)
+        assertTrue(accessibilityNodeInfo.isCheckable)
         assertFalse(
             accessibilityNodeInfo.actionList.contains(
                 AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, null)
@@ -881,6 +881,46 @@
     }
 
     @Test
+    fun sendViewSelectedEvent_whenSelectedChange_forTab() {
+        val tag = "Tab"
+        container.setContent {
+            var selected by remember { mutableStateOf(false) }
+            Box(
+                Modifier
+                    .selectable(selected = selected, onClick = { selected = true }, role = Role.Tab)
+                    .testTag(tag)
+            ) {
+                BasicText("Text")
+            }
+        }
+
+        rule.onNodeWithTag(tag)
+            .assertIsDisplayed()
+            .assertIsNotSelected()
+
+        waitForSubtreeEventToSend()
+        rule.onNodeWithTag(tag)
+            .performClick()
+            .assertIsSelected()
+
+        val node = rule.onNodeWithTag(tag)
+            .fetchSemanticsNode("couldn't find node with tag $tag")
+        rule.runOnIdle {
+            verify(container, times(1)).requestSendAccessibilityEvent(
+                eq(androidComposeView),
+                argThat(
+                    ArgumentMatcher {
+                        getAccessibilityEventSourceSemanticsNodeId(it) == node.id &&
+                            it.eventType == AccessibilityEvent.TYPE_VIEW_SELECTED &&
+                            it.text.size == 1 &&
+                            it.text[0].toString() == "Text"
+                    }
+                )
+            )
+        }
+    }
+
+    @Test
     fun sendStateChangeEvent_whenRangeInfoChange() {
         val tag = "Progress"
         var current by mutableStateOf(0.5f)
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
index c2064a1..8a98667 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
@@ -28,6 +28,8 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.runtime.structuralEqualityPolicy
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.toAndroidRect
 import androidx.compose.ui.node.InnerPlaceable
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.platform.AndroidComposeView
@@ -77,6 +79,7 @@
 import androidx.compose.ui.text.TextRange
 import androidx.core.view.ViewCompat
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
+import androidx.core.view.accessibility.AccessibilityNodeProviderCompat
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.platform.app.InstrumentationRegistry
@@ -865,7 +868,7 @@
     }
 
     @Test
-    fun testNotPlacedNodesAreNotIncluded() {
+    fun testUncoveredNodes_notPlacedNodes_notIncluded() {
         val nodes = SemanticsOwner(
             LayoutNode().also {
                 it.modifier = SemanticsModifierCore(
@@ -880,6 +883,18 @@
     }
 
     @Test
+    fun testUncoveredNodes_zeroBoundsRoot_included() {
+        val nodes = SemanticsOwner(androidComposeView.root).getAllUncoveredSemanticsNodesToMap()
+
+        assertEquals(1, nodes.size)
+        assertEquals(AccessibilityNodeProviderCompat.HOST_VIEW_ID, nodes.keys.first())
+        assertEquals(
+            Rect.Zero.toAndroidRect(),
+            nodes[AccessibilityNodeProviderCompat.HOST_VIEW_ID]!!.adjustedBounds
+        )
+    }
+
+    @Test
     fun testContentDescriptionCastSuccess() {
         val oldSemanticsNode = createSemanticsNodeWithProperties(1, true) {
         }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
index cfa4814..c949496 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
@@ -21,9 +21,11 @@
 import android.view.View
 import android.view.ViewGroup
 import androidx.activity.ComponentActivity
+import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -40,9 +42,11 @@
 import androidx.compose.ui.gesture.PointerCoords
 import androidx.compose.ui.gesture.PointerProperties
 import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.layout
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.AndroidComposeView
 import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.viewinterop.AndroidView
@@ -526,6 +530,54 @@
         assertTrue(tapLatch.await(1, TimeUnit.SECONDS))
     }
 
+    /**
+     * There are times that getLocationOnScreen() returns (0, 0). Touch input should still arrive
+     * at the correct place even if getLocationOnScreen() gives a different result than the
+     * rawX, rawY indicate.
+     */
+    @Test
+    fun badGetLocationOnScreen() {
+        val tapLatch = CountDownLatch(1)
+        val layoutLatch = CountDownLatch(1)
+        rule.runOnUiThread {
+            container.setContent {
+                with(LocalDensity.current) {
+                    Box(
+                        Modifier
+                            .size(250.toDp())
+                            .layout { measurable, constraints ->
+                                val p = measurable.measure(constraints)
+                                layout(p.width, p.height) {
+                                    p.place(0, 0)
+                                    layoutLatch.countDown()
+                                }
+                            }
+                    ) {
+                        Box(
+                            Modifier
+                                .align(AbsoluteAlignment.TopLeft)
+                                .pointerInput(Unit) {
+                                    awaitPointerEventScope {
+                                        awaitFirstDown()
+                                        tapLatch.countDown()
+                                    }
+                                }.size(10.toDp())
+                        )
+                    }
+                }
+            }
+        }
+        assertTrue(layoutLatch.await(1, TimeUnit.SECONDS))
+        rule.runOnUiThread { }
+
+        val down = createPointerEventAt(0, MotionEvent.ACTION_DOWN, intArrayOf(105, 205))
+        down.offsetLocation(-100f, -200f)
+        val composeView = findAndroidComposeView(container) as AndroidComposeView
+        composeView.dispatchTouchEvent(down)
+
+        assertTrue(tapLatch.await(1, TimeUnit.SECONDS))
+    }
+
     private fun createPointerEventAt(eventTime: Int, action: Int, locationInWindow: IntArray) =
         MotionEvent(
             eventTime,
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/AlignmentLineTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/AlignmentLineTest.kt
index 9c76455..b3b3736 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/AlignmentLineTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/AlignmentLineTest.kt
@@ -30,6 +30,7 @@
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import org.junit.Assert.assertEquals
@@ -1143,6 +1144,39 @@
         }
     }
 
+    @Test
+    fun notMeasuredChildIsNotCrashingWhenGrandParentQueriesAlignments() {
+        var emit by mutableStateOf(false)
+
+        rule.setContent {
+            Layout(
+                content = {
+                    Layout(
+                        content = {
+                            if (emit) {
+                                Box(Modifier.size(10.dp))
+                            }
+                        }
+                    ) { _, constraints ->
+                        layout(constraints.maxWidth, constraints.maxHeight) {}
+                    }
+                }
+            ) { measurables, constraints ->
+                val placeable = measurables.first().measure(constraints)
+                placeable[FirstBaseline]
+                layout(placeable.width, placeable.height) {
+                    placeable.place(0, 0)
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            emit = true
+        }
+
+        rule.runOnIdle {}
+    }
+
     private var linePosition = 10
     private var linePositionState by mutableStateOf(10)
     private fun changeLinePosition() {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/Key.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/Key.android.kt
index 4650948..f18af37 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/Key.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/Key.android.kt
@@ -17,8 +17,8 @@
 package androidx.compose.ui.input.key
 
 import android.view.KeyEvent
-import android.view.KeyEvent.KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP
 import android.view.KeyEvent.KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN
+import android.view.KeyEvent.KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.input.key.Key.Companion.Number
 import androidx.compose.ui.util.packInts
@@ -28,6 +28,8 @@
  * Actual implementation of [Key] for Android.
  *
  * @param keyCode an integer code representing the key pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsAltPressedSample
  */
 @Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
 actual inline class Key(val keyCode: Long) {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/KeyEvent.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/KeyEvent.android.kt
index e0a35a3..18e9190 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/KeyEvent.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/KeyEvent.android.kt
@@ -29,6 +29,8 @@
 
 /**
  * The key that was pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsAltPressedSample
  */
 actual val KeyEvent.key: Key
     get() = Key(nativeKeyEvent.keyCode)
@@ -54,6 +56,8 @@
 
 /**
  * The [type][KeyEventType] of key event.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventTypeSample
  */
 actual val KeyEvent.type: KeyEventType
     get() = when (nativeKeyEvent.action) {
@@ -64,24 +68,32 @@
 
 /**
  * Indicates whether the Alt key is pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsAltPressedSample
  */
 actual val KeyEvent.isAltPressed: Boolean
     get() = nativeKeyEvent.isAltPressed
 
 /**
  * Indicates whether the Ctrl key is pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsCtrlPressedSample
  */
 actual val KeyEvent.isCtrlPressed: Boolean
     get() = nativeKeyEvent.isCtrlPressed
 
 /**
  * Indicates whether the Meta key is pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsMetaPressedSample
  */
 actual val KeyEvent.isMetaPressed: Boolean
     get() = nativeKeyEvent.isMetaPressed
 
 /**
  * Indicates whether the Shift key is pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsShiftPressedSample
  */
 actual val KeyEvent.isShiftPressed: Boolean
     get() = nativeKeyEvent.isShiftPressed
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index d45093d..7bcb60a 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -278,6 +278,7 @@
     private val tmpCalculationMatrix = Matrix()
     @VisibleForTesting
     internal var lastMatrixRecalculationAnimationTime = -1L
+    private var forceUseMatrixCache = false
 
     /**
      * On some devices, the `getLocationOnScreen()` returns `(0, 0)` even when the Window
@@ -848,25 +849,32 @@
         ) {
             return false // Bad MotionEvent. Don't handle it.
         }
-        measureAndLayout()
-        val processResult = trace("AndroidOwner:onTouch") {
-            val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent, this)
-            if (pointerInputEvent != null) {
-                pointerInputEventProcessor.process(pointerInputEvent, this)
-            } else {
-                pointerInputEventProcessor.processCancel()
-                ProcessResult(
-                    dispatchedToAPointerInputModifier = false,
-                    anyMovementConsumed = false
-                )
+        try {
+            recalculateWindowPosition(motionEvent)
+            forceUseMatrixCache = true
+            measureAndLayout()
+            val processResult = trace("AndroidOwner:onTouch") {
+                val pointerInputEvent =
+                    motionEventAdapter.convertToPointerInputEvent(motionEvent, this)
+                if (pointerInputEvent != null) {
+                    pointerInputEventProcessor.process(pointerInputEvent, this)
+                } else {
+                    pointerInputEventProcessor.processCancel()
+                    ProcessResult(
+                        dispatchedToAPointerInputModifier = false,
+                        anyMovementConsumed = false
+                    )
+                }
             }
-        }
 
-        if (processResult.anyMovementConsumed) {
-            parent.requestDisallowInterceptTouchEvent(true)
-        }
+            if (processResult.anyMovementConsumed) {
+                parent.requestDisallowInterceptTouchEvent(true)
+            }
 
-        return processResult.dispatchedToAPointerInputModifier
+            return processResult.dispatchedToAPointerInputModifier
+        } finally {
+            forceUseMatrixCache = false
+        }
     }
 
     override fun localToScreen(localPosition: Offset): Offset {
@@ -886,26 +894,45 @@
     }
 
     private fun recalculateWindowPosition() {
-        val animationTime = AnimationUtils.currentAnimationTimeMillis()
-        if (animationTime != lastMatrixRecalculationAnimationTime) {
-            lastMatrixRecalculationAnimationTime = animationTime
-            recalculateWindowViewTransforms()
-            var viewParent = parent
-            var view: View = this
-            while (viewParent is ViewGroup) {
-                view = viewParent
-                viewParent = view.parent
+        if (!forceUseMatrixCache) {
+            val animationTime = AnimationUtils.currentAnimationTimeMillis()
+            if (animationTime != lastMatrixRecalculationAnimationTime) {
+                lastMatrixRecalculationAnimationTime = animationTime
+                recalculateWindowViewTransforms()
+                var viewParent = parent
+                var view: View = this
+                while (viewParent is ViewGroup) {
+                    view = viewParent
+                    viewParent = view.parent
+                }
+                view.getLocationOnScreen(tmpPositionArray)
+                val screenX = tmpPositionArray[0].toFloat()
+                val screenY = tmpPositionArray[1].toFloat()
+                view.getLocationInWindow(tmpPositionArray)
+                val windowX = tmpPositionArray[0].toFloat()
+                val windowY = tmpPositionArray[1].toFloat()
+                windowPosition = Offset(screenX - windowX, screenY - windowY)
             }
-            view.getLocationOnScreen(tmpPositionArray)
-            val screenX = tmpPositionArray[0].toFloat()
-            val screenY = tmpPositionArray[1].toFloat()
-            view.getLocationInWindow(tmpPositionArray)
-            val windowX = tmpPositionArray[0].toFloat()
-            val windowY = tmpPositionArray[1].toFloat()
-            windowPosition = Offset(screenX - windowX, screenY - windowY)
         }
     }
 
+    /**
+     * Recalculates the window position based on the [motionEvent]'s coordinates and
+     * screen coordinates. Some devices give false positions for [getLocationOnScreen] in
+     * some unusual circumstances, so a different mechanism must be used to determine the
+     * actual position.
+     */
+    private fun recalculateWindowPosition(motionEvent: MotionEvent) {
+        lastMatrixRecalculationAnimationTime = AnimationUtils.currentAnimationTimeMillis()
+        recalculateWindowViewTransforms()
+        val positionInWindow = viewToWindowMatrix.map(Offset(motionEvent.x, motionEvent.y))
+
+        windowPosition = Offset(
+            motionEvent.rawX - positionInWindow.x,
+            motionEvent.rawY - positionInWindow.y
+        )
+    }
+
     private fun recalculateWindowViewTransforms() {
         viewToWindowMatrix.reset()
         transformMatrixToWindow(this, viewToWindowMatrix)
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
index b633bed..bc515e3 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
@@ -412,9 +412,7 @@
                 // Tab in native android uses selected property
                 info.isSelected = it
             } else {
-                // Make a workaround here so talkback doesn't say "double tap to toggle" for
-                // selected items(this will be different from native android).
-                info.isCheckable = !it
+                info.isCheckable = true
                 info.isChecked = it
                 if (info.stateDescription == null) {
                     // If a radio entry (radio button + text) is selectable, it won't have the role
@@ -1672,13 +1670,51 @@
                         }
                     }
                     SemanticsProperties.StateDescription, SemanticsProperties.ToggleableState,
-                    SemanticsProperties.Selected, SemanticsProperties.ProgressBarRangeInfo -> {
+                    SemanticsProperties.ProgressBarRangeInfo -> {
                         sendEventForVirtualView(
                             semanticsNodeIdToAccessibilityVirtualNodeId(id),
                             AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
                             AccessibilityEventCompat.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION
                         )
                     }
+                    SemanticsProperties.Selected -> {
+                        // The assumption is among widgets using SemanticsProperties.Selected, only
+                        // Tab is using AccessibilityNodeInfo#isSelected, and all others are using
+                        // AccessibilityNodeInfo#isChekable and setting
+                        // AccessibilityNodeInfo#stateDescription in this delegate.
+                        if (newNode.config.getOrNull(SemanticsProperties.Role) == Role.Tab) {
+                            if (newNode.config.getOrNull(SemanticsProperties.Selected) == true) {
+                                val event = createEvent(
+                                    semanticsNodeIdToAccessibilityVirtualNodeId(id),
+                                    AccessibilityEvent.TYPE_VIEW_SELECTED
+                                )
+                                // Here we use the merged node
+                                val mergedNode =
+                                    SemanticsNode(newNode.outerSemanticsNodeWrapper, true)
+                                val contentDescription = mergedNode.config.getOrNull(
+                                    SemanticsProperties.ContentDescription
+                                )?.fastJoinToString(",")
+                                val text = mergedNode.config.getOrNull(SemanticsProperties.Text)
+                                    ?.fastJoinToString(",")
+                                contentDescription?.let { event.contentDescription = it }
+                                text?.let { event.text.add(it) }
+                                sendEvent(event)
+                            } else {
+                                // Send this event to match View.java.
+                                sendEventForVirtualView(
+                                    semanticsNodeIdToAccessibilityVirtualNodeId(id),
+                                    AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
+                                    AccessibilityEventCompat.CONTENT_CHANGE_TYPE_UNDEFINED
+                                )
+                            }
+                        } else {
+                            sendEventForVirtualView(
+                                semanticsNodeIdToAccessibilityVirtualNodeId(id),
+                                AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
+                                AccessibilityEventCompat.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION
+                            )
+                        }
+                    }
                     SemanticsProperties.ContentDescription -> {
                         sendEventForVirtualView(
                             semanticsNodeIdToAccessibilityVirtualNodeId(id),
@@ -2340,17 +2376,19 @@
     val unaccountedSpace = Region().also { it.set(root.boundsInRoot.toAndroidRect()) }
 
     fun findAllSemanticNodesRecursive(currentNode: SemanticsNode) {
-        if (unaccountedSpace.isEmpty || (!currentNode.layoutNode.isPlaced && !currentNode.isFake)) {
+        if ((unaccountedSpace.isEmpty && currentNode.id != root.id) ||
+            (!currentNode.layoutNode.isPlaced && !currentNode.isFake)
+        ) {
             return
         }
-        val rect = currentNode.boundsInRoot.toAndroidRect()
-        val region = Region().also { it.set(rect) }
+        val boundsInRoot = currentNode.boundsInRoot.toAndroidRect()
+        val region = Region().also { it.set(boundsInRoot) }
+        val virtualViewId = if (currentNode.id == root.id) {
+            AccessibilityNodeProviderCompat.HOST_VIEW_ID
+        } else {
+            currentNode.id
+        }
         if (region.op(unaccountedSpace, region, Region.Op.INTERSECT)) {
-            val virtualViewId = if (currentNode.id == root.id) {
-                AccessibilityNodeProviderCompat.HOST_VIEW_ID
-            } else {
-                currentNode.id
-            }
             nodes[virtualViewId] = SemanticsNodeWithAdjustedBounds(currentNode, region.bounds)
             // Children could be drawn outside of parent, but we are using clipped bounds for
             // accessibility now, so let's put the children recursion inside of this if. If later
@@ -2360,14 +2398,20 @@
             for (i in children.size - 1 downTo 0) {
                 findAllSemanticNodesRecursive(children[i])
             }
-            unaccountedSpace.op(rect, unaccountedSpace, Region.Op.REVERSE_DIFFERENCE)
+            unaccountedSpace.op(boundsInRoot, unaccountedSpace, Region.Op.REVERSE_DIFFERENCE)
         } else {
             if (currentNode.isFake) {
-                nodes[currentNode.id] = SemanticsNodeWithAdjustedBounds(
+                nodes[virtualViewId] = SemanticsNodeWithAdjustedBounds(
                     currentNode,
                     // provide some non-zero size as otherwise it will be ignored
                     Rect(0f, 0f, 10f, 10f).toAndroidRect()
                 )
+            } else if (virtualViewId == AccessibilityNodeProviderCompat.HOST_VIEW_ID) {
+                // Root view might have WRAP_CONTENT layout params in which case it will have zero
+                // bounds if there is no other content with semantics. But we need to always send the
+                // root view info as there are some other apps (e.g. Google Assistant) that depend
+                // on accessibility info
+                nodes[virtualViewId] = SemanticsNodeWithAdjustedBounds(currentNode, region.bounds)
             }
         }
     }
diff --git a/compose/ui/ui/src/androidMain/res/values-gu/strings.xml b/compose/ui/ui/src/androidMain/res/values-gu/strings.xml
new file mode 100644
index 0000000..34113d4d
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/res/values-gu/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="indeterminate" msgid="2486394087603402002">"ન ચેક કરેલું કે ન અનચેક કરેલું"</string>
+    <string name="on" msgid="8655164131929253426">"ચાલુ છે"</string>
+    <string name="off" msgid="875452955155264703">"બંધ છે"</string>
+    <string name="selected" msgid="6043586758067023">"પસંદગી કરી"</string>
+    <string name="not_selected" msgid="6610465462668679431">"પસંદગી કરી નથી"</string>
+    <string name="template_percent" msgid="5946805113151406391">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> ટકા."</string>
+    <string name="in_progress" msgid="6827826412747255547">"પ્રક્રિયામાં છે"</string>
+    <string name="tab" msgid="1672349317127674378">"ટૅબ"</string>
+    <string name="navigation_menu" msgid="542007171693138492">"નૅવિગેશન મેનૂ"</string>
+    <string name="close_drawer" msgid="406453423630273620">"નૅવિગેશન મેનૂ બંધ કરો"</string>
+    <string name="close_sheet" msgid="7573152094250666567">"શીટ બંધ કરો"</string>
+    <string name="default_error_message" msgid="8038256446254964252">"અમાન્ય ઇનપુટ"</string>
+</resources>
diff --git a/compose/ui/ui/src/androidMain/res/values-hy/strings.xml b/compose/ui/ui/src/androidMain/res/values-hy/strings.xml
new file mode 100644
index 0000000..7590844
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/res/values-hy/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="indeterminate" msgid="2486394087603402002">"Նշումը ո՛չ դրված է, ո՛չ էլ հանված"</string>
+    <string name="on" msgid="8655164131929253426">"Միացված է"</string>
+    <string name="off" msgid="875452955155264703">"Անջատված է"</string>
+    <string name="selected" msgid="6043586758067023">"Ընտրված է"</string>
+    <string name="not_selected" msgid="6610465462668679431">"Ընտրված չէ"</string>
+    <string name="template_percent" msgid="5946805113151406391">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> տոկոս։"</string>
+    <string name="in_progress" msgid="6827826412747255547">"Ընթացքում է"</string>
+    <string name="tab" msgid="1672349317127674378">"Ներդիր"</string>
+    <string name="navigation_menu" msgid="542007171693138492">"Նավիգացիայի ընտրացանկ"</string>
+    <string name="close_drawer" msgid="406453423630273620">"Փակել նավիգացիայի ընտրացանկը"</string>
+    <string name="close_sheet" msgid="7573152094250666567">"Փակել թերթը"</string>
+    <string name="default_error_message" msgid="8038256446254964252">"Սխալ ներածում"</string>
+</resources>
diff --git a/compose/ui/ui/src/androidMain/res/values-iw/strings.xml b/compose/ui/ui/src/androidMain/res/values-iw/strings.xml
new file mode 100644
index 0000000..08f49b0
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/res/values-iw/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="indeterminate" msgid="2486394087603402002">"עם סימון ובלי סימון"</string>
+    <string name="on" msgid="8655164131929253426">"פועל"</string>
+    <string name="off" msgid="875452955155264703">"כבוי"</string>
+    <string name="selected" msgid="6043586758067023">"נבחר"</string>
+    <string name="not_selected" msgid="6610465462668679431">"לא נבחר"</string>
+    <string name="template_percent" msgid="5946805113151406391">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> אחוזים."</string>
+    <string name="in_progress" msgid="6827826412747255547">"בתהליך"</string>
+    <string name="tab" msgid="1672349317127674378">"‏מקש Tab"</string>
+    <string name="navigation_menu" msgid="542007171693138492">"תפריט הניווט"</string>
+    <string name="close_drawer" msgid="406453423630273620">"סגירת תפריט הניווט"</string>
+    <string name="close_sheet" msgid="7573152094250666567">"סגירת הגיליון"</string>
+    <string name="default_error_message" msgid="8038256446254964252">"הקלט לא תקין"</string>
+</resources>
diff --git a/compose/ui/ui/src/androidMain/res/values-lo/strings.xml b/compose/ui/ui/src/androidMain/res/values-lo/strings.xml
new file mode 100644
index 0000000..92fdb82
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/res/values-lo/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="indeterminate" msgid="2486394087603402002">"ບໍ່ໄດ້ເລືອກ ຫຼື ຍົກເລີກການເລືອກແລ້ວ"</string>
+    <string name="on" msgid="8655164131929253426">"ເປີດ"</string>
+    <string name="off" msgid="875452955155264703">"ປິດ"</string>
+    <string name="selected" msgid="6043586758067023">"ເລືອກແລ້ວ"</string>
+    <string name="not_selected" msgid="6610465462668679431">"ບໍ່ໄດ້ເລືອກ"</string>
+    <string name="template_percent" msgid="5946805113151406391">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> ເປີ​ເຊັນ."</string>
+    <string name="in_progress" msgid="6827826412747255547">"ກຳ​ລັງ​ດຳ​ເນີນ​ການ"</string>
+    <string name="tab" msgid="1672349317127674378">"ແຖບ"</string>
+    <string name="navigation_menu" msgid="542007171693138492">"ເມນູການນຳທາງ"</string>
+    <string name="close_drawer" msgid="406453423630273620">"ປິດ​ເມ​ນູການ​ນຳ​ທາງ"</string>
+    <string name="close_sheet" msgid="7573152094250666567">"ປິດຊີດ"</string>
+    <string name="default_error_message" msgid="8038256446254964252">"ຂໍ້ມູນທີ່ປ້ອນເຂົ້າບໍ່ຖືກຕ້ອງ"</string>
+</resources>
diff --git a/compose/ui/ui/src/androidMain/res/values-ml/strings.xml b/compose/ui/ui/src/androidMain/res/values-ml/strings.xml
new file mode 100644
index 0000000..879cf8e
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/res/values-ml/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="indeterminate" msgid="2486394087603402002">"ചെക്ക് മാർക്കിടുകയോ അൺചെക്ക് ചെയ്യുകയോ ചെയ്തിട്ടില്ല"</string>
+    <string name="on" msgid="8655164131929253426">"ഓണാണ്"</string>
+    <string name="off" msgid="875452955155264703">"ഓഫാണ്"</string>
+    <string name="selected" msgid="6043586758067023">"തിരഞ്ഞെടുത്തു"</string>
+    <string name="not_selected" msgid="6610465462668679431">"തിരഞ്ഞെടുത്തില്ല"</string>
+    <string name="template_percent" msgid="5946805113151406391">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> ശതമാനം."</string>
+    <string name="in_progress" msgid="6827826412747255547">"പുരോഗമിക്കുന്നു"</string>
+    <string name="tab" msgid="1672349317127674378">"ടാബ്"</string>
+    <string name="navigation_menu" msgid="542007171693138492">"നാവിഗേഷൻ മെനു"</string>
+    <string name="close_drawer" msgid="406453423630273620">"നാവിഗേഷൻ മെനു അടയ്‌ക്കുക"</string>
+    <string name="close_sheet" msgid="7573152094250666567">"ഷീറ്റ് അടയ്ക്കുക"</string>
+    <string name="default_error_message" msgid="8038256446254964252">"ഇൻപുട്ട് അസാധുവാണ്"</string>
+</resources>
diff --git a/compose/ui/ui/src/androidMain/res/values-or/strings.xml b/compose/ui/ui/src/androidMain/res/values-or/strings.xml
new file mode 100644
index 0000000..d0545a0
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/res/values-or/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="indeterminate" msgid="2486394087603402002">"ଚେକ୍ କରାଯାଇନାହିଁ କି ଅନଚେକ୍ କରାଯାଇନାହିଁ"</string>
+    <string name="on" msgid="8655164131929253426">"ଚାଲୁ ଅଛି"</string>
+    <string name="off" msgid="875452955155264703">"ବନ୍ଦ ଅଛି"</string>
+    <string name="selected" msgid="6043586758067023">"ଚୟନିତ"</string>
+    <string name="not_selected" msgid="6610465462668679431">"ଚୟନ କରାଯାଇନାହିଁ"</string>
+    <string name="template_percent" msgid="5946805113151406391">"ଶତକଡ଼ା <xliff:g id="PERCENTAGE">%1$d</xliff:g>।"</string>
+    <string name="in_progress" msgid="6827826412747255547">"ଚାଲିଛି"</string>
+    <string name="tab" msgid="1672349317127674378">"ଟାବ୍"</string>
+    <string name="navigation_menu" msgid="542007171693138492">"ନାଭିଗେସନ୍ ମେନୁ"</string>
+    <string name="close_drawer" msgid="406453423630273620">"ନାଭିଗେସନ୍ ମେନୁ ବନ୍ଦ କରନ୍ତୁ"</string>
+    <string name="close_sheet" msgid="7573152094250666567">"ସିଟ୍ ବନ୍ଦ କରନ୍ତୁ"</string>
+    <string name="default_error_message" msgid="8038256446254964252">"ଅବୈଧ ଇନପୁଟ୍"</string>
+</resources>
diff --git a/compose/ui/ui/src/androidMain/res/values-sq/strings.xml b/compose/ui/ui/src/androidMain/res/values-sq/strings.xml
new file mode 100644
index 0000000..2ce3f5b
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/res/values-sq/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="indeterminate" msgid="2486394087603402002">"As e zgjedhur as e pazgjedhur"</string>
+    <string name="on" msgid="8655164131929253426">"Aktiv"</string>
+    <string name="off" msgid="875452955155264703">"Joaktiv"</string>
+    <string name="selected" msgid="6043586758067023">"Zgjedhur"</string>
+    <string name="not_selected" msgid="6610465462668679431">"Nuk është zgjedhur"</string>
+    <string name="template_percent" msgid="5946805113151406391">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> për qind."</string>
+    <string name="in_progress" msgid="6827826412747255547">"Në vazhdim"</string>
+    <string name="tab" msgid="1672349317127674378">"Tab"</string>
+    <string name="navigation_menu" msgid="542007171693138492">"Menyja e navigimit"</string>
+    <string name="close_drawer" msgid="406453423630273620">"Mbyll menynë e navigimit"</string>
+    <string name="close_sheet" msgid="7573152094250666567">"Mbyll fletën"</string>
+    <string name="default_error_message" msgid="8038256446254964252">"Hyrje e pavlefshme"</string>
+</resources>
diff --git a/compose/ui/ui/src/androidMain/res/values-te/strings.xml b/compose/ui/ui/src/androidMain/res/values-te/strings.xml
new file mode 100644
index 0000000..2629e0b
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/res/values-te/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="indeterminate" msgid="2486394087603402002">"చెక్ చేయబడలేదు అలాగే చెక్ చేయబడదు"</string>
+    <string name="on" msgid="8655164131929253426">"ఆన్‌లో ఉంది"</string>
+    <string name="off" msgid="875452955155264703">"ఆఫ్‌లో ఉంది"</string>
+    <string name="selected" msgid="6043586758067023">"ఎంచుకోబడింది"</string>
+    <string name="not_selected" msgid="6610465462668679431">"ఎంచుకోబడలేదు"</string>
+    <string name="template_percent" msgid="5946805113151406391">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> శాతం."</string>
+    <string name="in_progress" msgid="6827826412747255547">"ప్రోగ్రెస్‌లో ఉంది"</string>
+    <string name="tab" msgid="1672349317127674378">"ట్యాబ్"</string>
+    <string name="navigation_menu" msgid="542007171693138492">"నావిగేషన్ మెనూ"</string>
+    <string name="close_drawer" msgid="406453423630273620">"నావిగేషన్ మెనూను మూసివేయి"</string>
+    <string name="close_sheet" msgid="7573152094250666567">"షీట్‌ను మూసివేయి"</string>
+    <string name="default_error_message" msgid="8038256446254964252">"ఇన్‌పుట్ చెల్లదు"</string>
+</resources>
diff --git a/compose/ui/ui/src/androidMain/res/values-ur/strings.xml b/compose/ui/ui/src/androidMain/res/values-ur/strings.xml
new file mode 100644
index 0000000..d87cc6f
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/res/values-ur/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="indeterminate" msgid="2486394087603402002">"نہ تو نشان زد کیا گیا نہ ہی نشان ہٹایا گیا"</string>
+    <string name="on" msgid="8655164131929253426">"آن ہے"</string>
+    <string name="off" msgid="875452955155264703">"آف ہے"</string>
+    <string name="selected" msgid="6043586758067023">"منتخب کردہ"</string>
+    <string name="not_selected" msgid="6610465462668679431">"غیر منتخب کردہ"</string>
+    <string name="template_percent" msgid="5946805113151406391">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> فیصد۔"</string>
+    <string name="in_progress" msgid="6827826412747255547">"پیشرفت میں"</string>
+    <string name="tab" msgid="1672349317127674378">"ٹیب"</string>
+    <string name="navigation_menu" msgid="542007171693138492">"نیویگیشن مینو"</string>
+    <string name="close_drawer" msgid="406453423630273620">"نیویگیشن مینو بند کریں"</string>
+    <string name="close_sheet" msgid="7573152094250666567">"شیٹ بند کریں"</string>
+    <string name="default_error_message" msgid="8038256446254964252">"غلط ان پٹ"</string>
+</resources>
diff --git a/compose/ui/ui/src/androidMain/res/values-uz/strings.xml b/compose/ui/ui/src/androidMain/res/values-uz/strings.xml
new file mode 100644
index 0000000..05400da
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/res/values-uz/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="indeterminate" msgid="2486394087603402002">"Belgilanmagan va belgi olib tashlanmagan"</string>
+    <string name="on" msgid="8655164131929253426">"Yoniq"</string>
+    <string name="off" msgid="875452955155264703">"Oʻchiq"</string>
+    <string name="selected" msgid="6043586758067023">"Tanlangan"</string>
+    <string name="not_selected" msgid="6610465462668679431">"Tanlanmagan"</string>
+    <string name="template_percent" msgid="5946805113151406391">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> foiz."</string>
+    <string name="in_progress" msgid="6827826412747255547">"Bajarilmoqda"</string>
+    <string name="tab" msgid="1672349317127674378">"Varaq"</string>
+    <string name="navigation_menu" msgid="542007171693138492">"Navigatsiya menyusi"</string>
+    <string name="close_drawer" msgid="406453423630273620">"Navigatsiya menyusini yopish"</string>
+    <string name="close_sheet" msgid="7573152094250666567">"Varaqni yopish"</string>
+    <string name="default_error_message" msgid="8038256446254964252">"Kiritilgan axborot xato"</string>
+</resources>
diff --git a/compose/ui/ui/src/androidMain/res/values-zu/strings.xml b/compose/ui/ui/src/androidMain/res/values-zu/strings.xml
new file mode 100644
index 0000000..9501f8a
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/res/values-zu/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="indeterminate" msgid="2486394087603402002">"Lutho oluhloliwe nolungahlolwanga"</string>
+    <string name="on" msgid="8655164131929253426">"Vuliwe"</string>
+    <string name="off" msgid="875452955155264703">"Valiwe"</string>
+    <string name="selected" msgid="6043586758067023">"Okukhethiwe"</string>
+    <string name="not_selected" msgid="6610465462668679431">"Ayikhethiwe"</string>
+    <string name="template_percent" msgid="5946805113151406391">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> amaphesenti."</string>
+    <string name="in_progress" msgid="6827826412747255547">"Kuyaqhubeka"</string>
+    <string name="tab" msgid="1672349317127674378">"Ithebhu"</string>
+    <string name="navigation_menu" msgid="542007171693138492">"Imenyu yokuzulazula"</string>
+    <string name="close_drawer" msgid="406453423630273620">"Vala imenyu yokuzulazula"</string>
+    <string name="close_sheet" msgid="7573152094250666567">"Vala ishidi"</string>
+    <string name="default_error_message" msgid="8038256446254964252">"Okufakwayo okungalungile"</string>
+</resources>
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusChangedModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusChangedModifier.kt
index cc14f53..9440c6b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusChangedModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusChangedModifier.kt
@@ -25,9 +25,12 @@
 
 /**
  * Add this modifier to a component to observe focus state events. [onFocusChanged] is invoked
- * only when the focus state changes.
+ * when the focus state changes. The [onFocusChanged] modifier listens to the state of the first
+ * [focusTarget] following this modifier.
  *
- * If you want to be notified every time the internal focus state is written to (even if it
+ * @sample androidx.compose.ui.samples.FocusableSample
+ *
+ * Note: If you want to be notified every time the internal focus state is written to (even if it
  * hasn't changed), use [onFocusEvent] instead.
  */
 fun Modifier.onFocusChanged(onFocusChanged: (FocusState) -> Unit): Modifier =
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
index a6cc7df..782bdb4 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.focus
 
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
@@ -31,6 +30,8 @@
      *
      *  @param force: Whether we should forcefully clear focus regardless of whether we have
      *  any components that have Captured focus.
+     *
+     *  @sample androidx.compose.ui.samples.ClearFocusSample
      */
     fun clearFocus(force: Boolean = false)
 
@@ -41,42 +42,10 @@
      * [Modifier.focusOrder()][focusOrder].
      *
      * @return true if focus was moved successfully. false if the focused item is unchanged.
+     *
+     * @sample androidx.compose.ui.samples.MoveFocusSample
      */
     fun moveFocus(focusDirection: FocusDirection): Boolean
-
-    /**
-     * Moves focus to one of the children of the currently focused item.
-     *
-     * This function is deprecated. Use FocusManager.moveFocus(FocusDirection.In) instead.
-     *
-     * @return true if focus was moved successfully.
-     */
-    @ExperimentalComposeUiApi
-    @Deprecated(
-        message = "Use FocusManager.moveFocus(FocusDirection.In) instead",
-        ReplaceWith(
-            "moveFocus(In)",
-            "androidx.compose.ui.focus.FocusDirection.Companion.In"
-        )
-    )
-    fun moveFocusIn(): Boolean = false
-
-    /**
-     * Moves focus to the nearest focusable parent of the currently focused item.
-     *
-     *  This function is deprecated. Use FocusManager.moveFocus(FocusDirection.Out) instead.
-     *
-     * @return true if focus was moved successfully.
-     */
-    @ExperimentalComposeUiApi
-    @Deprecated(
-        message = "Use FocusManager.moveFocus(FocusDirection.Out) instead",
-        ReplaceWith(
-            "moveFocus(Out)",
-            "androidx.compose.ui.focus.FocusDirection.Companion.Out"
-        )
-    )
-    fun moveFocusOut(): Boolean = false
 }
 
 /**
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusModifier.kt
index 5c53268..f2718af 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusModifier.kt
@@ -50,6 +50,16 @@
 
 /**
  * Add this modifier to a component to make it focusable.
+ *
+ * Focus state is stored within this modifier. The bounds of this modifier reflect the bounds of
+ * the focus box.
+ *
+ * Note: This is a low level modifier. Before using this consider using
+ * [Modifier.focusable()][androidx.compose.foundation.focusable]. It uses a [focusTarget] in
+ * its implementation. [Modifier.focusable()][androidx.compose.foundation.focusable] adds semantics
+ * that are needed for accessibility.
+ *
+ * @sample androidx.compose.ui.samples.FocusableSampleUsingLowerLevelFocusTarget
  */
 fun Modifier.focusTarget(): Modifier = composed(debugInspectorInfo { name = "focusTarget" }) {
     remember { FocusModifier(Inactive) }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOrderModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOrderModifier.kt
index dbd306d..d258b41 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOrderModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOrderModifier.kt
@@ -23,6 +23,8 @@
 
 /**
  * A [modifier][Modifier.Element] that can be used to set a custom focus traversal order.
+ *
+ * @see Modifier.focusOrder
  */
 interface FocusOrderModifier : Modifier.Element {
 
@@ -36,48 +38,66 @@
 
 /**
  * Specifies custom focus destinations that are used instead of the default focus traversal order.
+ *
+ * @sample androidx.compose.ui.samples.CustomFocusOrderSample
  */
 class FocusOrder {
 
     /**
-     * A custom item to be used when the user moves focus to the "next" item.
+     * A custom item to be used when the user requests a focus moves to the "next" item.
+     *
+     * @sample androidx.compose.ui.samples.CustomFocusOrderSample
      */
     var next: FocusRequester = FocusRequester.Default
 
     /**
-     * A custom item to be used when the user moves focus to the "previous" item.
+     * A custom item to be used when the user requests a focus moves to the "previous" item.
+     *
+     * @sample androidx.compose.ui.samples.CustomFocusOrderSample
      */
     var previous: FocusRequester = FocusRequester.Default
 
     /**
      *  A custom item to be used when the user moves focus "up".
+     *
+     *  @sample androidx.compose.ui.samples.CustomFocusOrderSample
      */
     var up: FocusRequester = FocusRequester.Default
 
     /**
      *  A custom item to be used when the user moves focus "down".
+     *
+     *  @sample androidx.compose.ui.samples.CustomFocusOrderSample
      */
     var down: FocusRequester = FocusRequester.Default
 
     /**
-     * A custom item to be used when the user moves focus to the "left" item.
+     * A custom item to be used when the user requests a focus moves to the "left" item.
+     *
+     * @sample androidx.compose.ui.samples.CustomFocusOrderSample
      */
     var left: FocusRequester = FocusRequester.Default
 
     /**
-     * A custom item to be used when the user moves focus to the "right" item.
+     * A custom item to be used when the user requests a focus moves to the "right" item.
+     *
+     * @sample androidx.compose.ui.samples.CustomFocusOrderSample
      */
     var right: FocusRequester = FocusRequester.Default
 
     /**
-     * A custom item to be used when the user moves focus to the "left" in LTR mode and "right"
-     * in RTL mode.
+     * A custom item to be used when the user requests a focus moves to the "left" in LTR mode and
+     * "right" in RTL mode.
+     *
+     * @sample androidx.compose.ui.samples.CustomFocusOrderSample
      */
     var start: FocusRequester = FocusRequester.Default
 
     /**
-     * A custom item to be used when the user moves focus to the "right" in LTR mode and "left"
-     * in RTL mode.
+     * A custom item to be used when the user requests a focus moves to the "right" in LTR mode
+     * and "left" in RTL mode.
+     *
+     * @sample androidx.compose.ui.samples.CustomFocusOrderSample
      */
     var end: FocusRequester = FocusRequester.Default
 }
@@ -98,6 +118,8 @@
  * to move the current focus to the [next][FocusOrder.next] item, or wants to move
  * focus [left][FocusOrder.left], [right][FocusOrder.right], [up][FocusOrder.up] or
  * [down][FocusOrder.down].
+ *
+ * @sample androidx.compose.ui.samples.CustomFocusOrderSample
  */
 fun Modifier.focusOrder(focusOrderReceiver: FocusOrder.() -> Unit): Modifier {
     return this.then(
@@ -114,12 +136,16 @@
 /**
  * A modifier that lets you specify a [FocusRequester] for the current composable so that this
  * [focusRequester] can be used by another composable to specify a custom focus order.
+ *
+ * @sample androidx.compose.ui.samples.CustomFocusOrderSample
  */
 fun Modifier.focusOrder(focusRequester: FocusRequester): Modifier = focusRequester(focusRequester)
 
 /**
  * A modifier that lets you specify a [FocusRequester] for the current composable along with
  * [focusOrder].
+ *
+ * @sample androidx.compose.ui.samples.CustomFocusOrderSample
  */
 fun Modifier.focusOrder(
     focusRequester: FocusRequester,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
index 0dded38..7f0d6f9 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
@@ -21,14 +21,21 @@
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.node.ModifiedFocusRequesterNode
 
-private const val focusRequesterNotInitialized = "FocusRequester is not initialized. One reason " +
-    "for this is that you requesting focus changes during composition. Focus requesters should " +
-    "not be made during composition, but should be made in response to some event."
+private const val focusRequesterNotInitialized = """
+   FocusRequester is not initialized. Here are some possible fixes:
+
+   1. Remember the FocusRequester: val focusRequester = remember { FocusRequester() }
+   2. Did you forget to add a Modifier.focusRequester() ?
+   3. Are you attempting to request focus during composition? Focus requests should be made in
+   response to some event. Eg Modifier.clickable { focusRequester.requestFocus() }
+"""
 
 /**
  * The [FocusRequester] is used in conjunction with
- * [Modifier.focusRequester][androidx.compose.ui.focus.focusRequester] to send requests for focus
- * state change.
+ * [Modifier.focusRequester][androidx.compose.ui.focus.focusRequester] to send requests to
+ * change focus.
+ *
+ * @sample androidx.compose.ui.samples.RequestFocusSample
  *
  * @see androidx.compose.ui.focus.focusRequester
  */
@@ -38,8 +45,10 @@
 
     /**
      * Use this function to request focus. If the system grants focus to a component associated
-     * with this [FocusRequester], its [state][FocusState] will be set to
-     * [Active][FocusState.Active].
+     * with this [FocusRequester], its [onFocusChanged] modifiers will receive a [FocusState] object
+     * where [FocusState.isFocused] is true.
+     *
+     * @sample androidx.compose.ui.samples.RequestFocusSample
      */
     fun requestFocus() {
         check(focusRequesterNodes.isNotEmpty()) { focusRequesterNotInitialized }
@@ -49,15 +58,17 @@
     /**
      * Deny requests to clear focus.
      *
-     * Use this function to send a request to capture the focus. If a component is captured,
-     * its [state][FocusState] will be set to [Captured][FocusState.Captured]. When a
-     * component is in this state, it holds onto focus until [freeFocus] is called. When a
-     * component is in the [Captured][FocusState.Captured] state, all focus requests from
-     * other components are declined.
+     * Use this function to send a request to capture focus. If a component captures focus,
+     * it will send a [FocusState] object to its associated [onFocusChanged]
+     * modifiers where [FocusState.isCaptured]() == true.
+     *
+     * When a component is in a Captured state, all focus requests from other components are
+     * declined.
      *
      * @return true if the focus was successfully captured by one of the
-     * [focus][androidx.compose.ui.focus] modifiers associated with this [FocusRequester].
-     * false otherwise.
+     * [focus][focusTarget] modifiers associated with this [FocusRequester]. False otherwise.
+     *
+     * @sample androidx.compose.ui.samples.CaptureFocusSample
      */
     fun captureFocus(): Boolean {
         check(focusRequesterNodes.isNotEmpty()) { focusRequesterNotInitialized }
@@ -73,19 +84,18 @@
     }
 
     /**
-     * Use this function to send a request to release focus when one of the components associated
-     * with this [FocusRequester] is in a [Captured][FocusState.Captured] state.
+     * Use this function to send a request to free focus when one of the components associated
+     * with this [FocusRequester] is in a Captured state. If a component frees focus,
+     * it will send a [FocusState] object to its associated [onFocusChanged]
+     * modifiers where [FocusState.isCaptured]() == false.
      *
-     * When the node is in the [Captured][FocusState.Captured] state, it rejects all requests to
-     * clear focus. Calling
-     * [freeFocus] puts the node in the [Active][FocusState.Active] state, where it is no longer
-     * preventing other
-     * nodes from requesting focus.
+     * When a component is in a Captured state, all focus requests from other components are
+     * declined.
+     *.
+     * @return true if the captured focus was successfully released. i.e. At the end of this
+     * operation, one of the components associated with this [focusRequester] freed focus.
      *
-     * @return true if the focus was successfully released. i.e. At the end of this operation,
-     * one of the components associated with this
-     * [focusRequester][androidx.compose.ui.focus.focusRequester] is in the
-     * [Active][FocusState.Active] state. false otherwise.
+     * @sample androidx.compose.ui.samples.CaptureFocusSample
      */
     fun freeFocus(): Boolean {
         check(focusRequesterNodes.isNotEmpty()) { focusRequesterNotInitialized }
@@ -103,13 +113,15 @@
     companion object {
         /**
          * Default [focusRequester], which when used in [Modifier.focusOrder][focusOrder] implies
-         * that we want to use the default system focus order, that is based on the location on
+         * that we want to use the default system focus order, that is based on the position of the
          * items on the screen.
          */
         val Default = FocusRequester()
 
         /**
          * Convenient way to create multiple [FocusRequester] instances.
+         *
+         * @sample androidx.compose.ui.samples.CreateFocusRequesterRefsSample
          */
         @ExperimentalComposeUiApi
         object FocusRequesterFactory {
@@ -134,6 +146,8 @@
         /**
          * Convenient way to create multiple [FocusRequester]s, which can to be used to request
          * focus, or to specify a focus traversal order.
+         *
+         * @sample androidx.compose.ui.samples.CreateFocusRequesterRefsSample
          */
         @ExperimentalComposeUiApi
         fun createRefs() = FocusRequesterFactory
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequesterModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequesterModifier.kt
index 739030d..8f193ed 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequesterModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequesterModifier.kt
@@ -22,14 +22,19 @@
 import androidx.compose.ui.platform.debugInspectorInfo
 
 /**
- * A [modifier][Modifier.Element] that can be used to pass in a [FocusRequester] that can be used
- * to request focus state changes.
+ * A [modifier][Modifier.Element] that is used to pass in a [FocusRequester] that can be used to
+ * request focus state changes.
+ *
+ * @sample androidx.compose.ui.samples.RequestFocusSample
  *
  * @see FocusRequester
+ * @see Modifier.focusRequester
  */
 interface FocusRequesterModifier : Modifier.Element {
     /**
      * An instance of [FocusRequester], that can be used to request focus state changes.
+     *
+     * @sample androidx.compose.ui.samples.RequestFocusSample
      */
     val focusRequester: FocusRequester
 }
@@ -40,7 +45,9 @@
 ) : FocusRequesterModifier, InspectorValueInfo(inspectorInfo)
 
 /**
- * Add this modifier to a component to observe changes to focus state.
+ * Add this modifier to a component to request changes to focus.
+ *
+ * @sample androidx.compose.ui.samples.RequestFocusSample
  */
 fun Modifier.focusRequester(focusRequester: FocusRequester): Modifier {
     return this.then(
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusState.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusState.kt
index 5f2f046..390eba2 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusState.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusState.kt
@@ -19,14 +19,19 @@
 /**
  * The focus state of a [FocusModifier]. Use [onFocusChanged] or [onFocusEvent] modifiers to
  * access [FocusState].
+ *
+ * @sample androidx.compose.ui.samples.FocusableSample
  */
 interface FocusState {
     /**
      * Whether the component is focused or not.
      *
+     * @sample androidx.compose.ui.samples.FocusableSample
+     *
      * @return true if the component is focused, false otherwise.
      */
     val isFocused: Boolean
+
     /**
      * Whether the focus modifier associated with this [FocusState] has a child that is focused.
      *
@@ -36,13 +41,16 @@
 
     /**
      * Whether focus is captured or not. A focusable component is in a captured state when it
-     * wants to hold onto focus. (Eg. when a text field has an invalid phone number]. When we are
-     * in a captured state, clicking outside the focused item does not clear focus.
+     * wants to hold onto focus. (Eg. when a text field has an invalid phone number). When we are
+     * in a captured state, clicking on other focusable items does not clear focus from the
+     * currently focused item.
      *
      * You can capture focus by calling [focusRequester.captureFocus()][captureFocus] and free
      * focus by calling [focusRequester.freeFocus()][freeFocus].
      *
      *  @return true if focus is captured, false otherwise.
+     *
+     *  @sample androidx.compose.ui.samples.CaptureFocusSample
      */
     val isCaptured: Boolean
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
index fdd5444..94a823b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
@@ -39,8 +39,8 @@
 private const val invalidFocusDirection = "Invalid FocusDirection"
 
 /**
- * The [FocusDirection] is used to specify direction when a focus change request is made via
- * [moveFocus].
+ * The [FocusDirection] is used to specify the direction for a [FocusManager.moveFocus]
+ * request.
  *
  * @sample androidx.compose.ui.samples.MoveFocusSample
  */
@@ -67,36 +67,48 @@
         /**
          *  Direction used in [moveFocus] to indicate that you are searching for the next
          *  focusable item.
+         *
+         *  @sample androidx.compose.ui.samples.MoveFocusSample
          */
         val Next: FocusDirection = FocusDirection(1)
 
         /**
          *  Direction used in [moveFocus] to indicate that you are searching for the previous
          *  focusable item.
+         *
+         *  @sample androidx.compose.ui.samples.MoveFocusSample
          */
         val Previous: FocusDirection = FocusDirection(2)
 
         /**
          *  Direction used in [moveFocus] to indicate that you are searching for the next
          *  focusable item to the left of the currently focused item.
+         *
+         *  @sample androidx.compose.ui.samples.MoveFocusSample
          */
         val Left: FocusDirection = FocusDirection(3)
 
         /**
          *  Direction used in [moveFocus] to indicate that you are searching for the next
          *  focusable item to the right of the currently focused item.
+         *
+         *  @sample androidx.compose.ui.samples.MoveFocusSample
          */
         val Right: FocusDirection = FocusDirection(4)
 
         /**
          *  Direction used in [moveFocus] to indicate that you are searching for the next
          *  focusable item that is above the currently focused item.
+         *
+         *  @sample androidx.compose.ui.samples.MoveFocusSample
          */
         val Up: FocusDirection = FocusDirection(5)
 
         /**
          *  Direction used in [moveFocus] to indicate that you are searching for the next
          *  focusable item that is below the currently focused item.
+         *
+         *  @sample androidx.compose.ui.samples.MoveFocusSample
          */
         val Down: FocusDirection = FocusDirection(6)
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/Key.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/Key.kt
index c420f17..9612a0d 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/Key.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/Key.kt
@@ -23,6 +23,8 @@
  *
  * @param keyCode a Long value representing the key pressed. Note: This keycode can be used to
  * uniquely identify a hardware key. It is different from the native keycode.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsAltPressedSample
  */
 @Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
 expect inline class Key(val keyCode: Long) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyEvent.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyEvent.kt
index 48845cfb..efd3ce9 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyEvent.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyEvent.kt
@@ -22,14 +22,21 @@
 expect class NativeKeyEvent
 
 /**
- * When a user presses a key on a hardware keyboard, a [KeyEvent] is sent to the
- * [KeyInputModifier] that is currently active.
+ * When a user presses a key on a hardware keyboard, a [KeyEvent] is sent to the item that is
+ * currently focused. Any parent composable can intercept this [key event][KeyEvent] on its way to
+ * the focused item by using [Modifier.onPreviewKeyEvent()]][onPreviewKeyEvent]. If the item is
+ * not consumed, it returns back to each parent and can be intercepted by using
+ * [Modifier.onKeyEvent()]][onKeyEvent].
+ *
+ * @sample androidx.compose.ui.samples.KeyEventSample
  */
 @Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
 inline class KeyEvent(val nativeKeyEvent: NativeKeyEvent)
 
 /**
  * The key that was pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsAltPressedSample
  */
 expect val KeyEvent.key: Key
 
@@ -53,31 +60,43 @@
 
 /**
  * The [type][KeyEventType] of key event.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventTypeSample
  */
 expect val KeyEvent.type: KeyEventType
 
 /**
  * Indicates whether the Alt key is pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsAltPressedSample
  */
 expect val KeyEvent.isAltPressed: Boolean
 
 /**
  * Indicates whether the Ctrl key is pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsCtrlPressedSample
  */
 expect val KeyEvent.isCtrlPressed: Boolean
 
 /**
  * Indicates whether the Meta key is pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsMetaPressedSample
  */
 expect val KeyEvent.isMetaPressed: Boolean
 
 /**
  * Indicates whether the Shift key is pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsShiftPressedSample
  */
 expect val KeyEvent.isShiftPressed: Boolean
 
 /**
  * The type of Key Event.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventTypeSample
  */
 @Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
 inline class KeyEventType internal constructor(@Suppress("unused") private val value: Int) {
@@ -94,16 +113,22 @@
     companion object {
         /**
          * Unknown key event.
+         *
+         * @sample androidx.compose.ui.samples.KeyEventTypeSample
          */
         val Unknown: KeyEventType = KeyEventType(0)
 
         /**
          * Type of KeyEvent sent when the user lifts their finger off a key on the keyboard.
+         *
+         * @sample androidx.compose.ui.samples.KeyEventTypeSample
          */
         val KeyUp: KeyEventType = KeyEventType(1)
 
         /**
          * Type of KeyEvent sent when the user presses down their finger on a key on the keyboard.
+         *
+         * @sample androidx.compose.ui.samples.KeyEventTypeSample
          */
         val KeyDown: KeyEventType = KeyEventType(2)
     }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyInputModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyInputModifier.kt
index 4519562..7aeab72 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyInputModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyInputModifier.kt
@@ -24,11 +24,13 @@
 
 /**
  * Adding this [modifier][Modifier] to the [modifier][Modifier] parameter of a component will
- * allow it to intercept hardware key events.
+ * allow it to intercept hardware key events when it (or one of its children) is focused.
  *
  * @param onKeyEvent This callback is invoked when the user interacts with the hardware keyboard.
  * While implementing this callback, return true to stop propagation of this event. If you return
  * false, the key event will be sent to this [onKeyEvent]'s parent.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventSample
  */
 // TODO: b/191017532 remove Modifier.composed
 @Suppress("UnnecessaryComposedModifier")
@@ -43,13 +45,15 @@
 
 /**
  * Adding this [modifier][Modifier] to the [modifier][Modifier] parameter of a component will
- * allow it to intercept hardware key events.
+ * allow it to intercept hardware key events when it (or one of its children) is focused.
  *
  * @param onPreviewKeyEvent This callback is invoked when the user interacts with the hardware
  * keyboard. It gives ancestors of a focused component the chance to intercept a [KeyEvent].
  * Return true to stop propagation of this event. If you return false, the key event will be sent
  * to this [onPreviewKeyEvent]'s child. If none of the children consume the event, it will be
  * sent back up to the root [KeyInputModifier] using the onKeyEvent callback.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventSample
  */
 // TODO: b/191017532 remove Modifier.composed
 @Suppress("UnnecessaryComposedModifier")
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index d5a8036..736c14c 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -1230,8 +1230,14 @@
      * Return true if the measured size has been changed
      */
     internal fun remeasure(
-        constraints: Constraints = outerMeasurablePlaceable.lastConstraints
-    ) = outerMeasurablePlaceable.remeasure(constraints)
+        constraints: Constraints? = outerMeasurablePlaceable.lastConstraints
+    ): Boolean {
+        return if (constraints != null) {
+            outerMeasurablePlaceable.remeasure(constraints)
+        } else {
+            false
+        }
+    }
 
     override val parentData: Any? get() = outerMeasurablePlaceable.parentData
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OuterMeasurablePlaceable.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OuterMeasurablePlaceable.kt
index 6ca6b5f..35f700e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OuterMeasurablePlaceable.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OuterMeasurablePlaceable.kt
@@ -32,10 +32,12 @@
 
     private var measuredOnce = false
     private var placedOnce = false
-    val lastConstraints: Constraints get() {
-        check(measuredOnce)
-        return measurementConstraints
-    }
+    val lastConstraints: Constraints?
+        get() = if (measuredOnce) {
+            measurementConstraints
+        } else {
+            null
+        }
     internal var duringAlignmentLinesQuery = false
 
     private var lastPosition: IntOffset = IntOffset.Zero
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 37d5008..ff80609 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -739,6 +739,14 @@
 Transforming pages\: *[0-9]+
 Rendering\: *[0-9]+
 WARNING\: unable to find what is referred to by
+@param _value
+in DClass ObservableWatchData
+@param currentUserStyleRepository,
+@param watchState
+in DClass Renderer
+@param bounds
+in DClass Complication
+in DClass ComplicationSlot
 @param maxSlotsToRetainForReuse
 in DClass SubcomposeLayoutState
 @param initVal
@@ -880,6 +888,12 @@
 @param downChange
 in DClass ConsumedData
 @param id,
+@param canvasComplicationFactory,
+@param supportedTypes,
+@param defaultProviderPolicy,
+@param boundsType,
+@param bounds,
+@param complicationTapFilter
 @param uptimeMillis,
 @param position,
 @param pressed,
diff --git a/documentfile/OWNERS b/documentfile/OWNERS
index 910c8de..f63fcb8 100644
--- a/documentfile/OWNERS
+++ b/documentfile/OWNERS
@@ -1 +1,2 @@
 [email protected]
[email protected]
diff --git a/emoji2/emoji2-views-helper/api/current.txt b/emoji2/emoji2-views-helper/api/current.txt
index 35a9171..30a6feb 100644
--- a/emoji2/emoji2-views-helper/api/current.txt
+++ b/emoji2/emoji2-views-helper/api/current.txt
@@ -4,7 +4,7 @@
   public final class EmojiEditTextHelper {
     ctor public EmojiEditTextHelper(android.widget.EditText);
     ctor public EmojiEditTextHelper(android.widget.EditText, boolean);
-    method public android.text.method.KeyListener getKeyListener(android.text.method.KeyListener);
+    method public android.text.method.KeyListener? getKeyListener(android.text.method.KeyListener?);
     method public int getMaxEmojiCount();
     method public boolean isEnabled();
     method public android.view.inputmethod.InputConnection? onCreateInputConnection(android.view.inputmethod.InputConnection?, android.view.inputmethod.EditorInfo);
diff --git a/emoji2/emoji2-views-helper/api/public_plus_experimental_current.txt b/emoji2/emoji2-views-helper/api/public_plus_experimental_current.txt
index 35a9171..30a6feb 100644
--- a/emoji2/emoji2-views-helper/api/public_plus_experimental_current.txt
+++ b/emoji2/emoji2-views-helper/api/public_plus_experimental_current.txt
@@ -4,7 +4,7 @@
   public final class EmojiEditTextHelper {
     ctor public EmojiEditTextHelper(android.widget.EditText);
     ctor public EmojiEditTextHelper(android.widget.EditText, boolean);
-    method public android.text.method.KeyListener getKeyListener(android.text.method.KeyListener);
+    method public android.text.method.KeyListener? getKeyListener(android.text.method.KeyListener?);
     method public int getMaxEmojiCount();
     method public boolean isEnabled();
     method public android.view.inputmethod.InputConnection? onCreateInputConnection(android.view.inputmethod.InputConnection?, android.view.inputmethod.EditorInfo);
diff --git a/emoji2/emoji2-views-helper/api/restricted_current.txt b/emoji2/emoji2-views-helper/api/restricted_current.txt
index 35a9171..30a6feb 100644
--- a/emoji2/emoji2-views-helper/api/restricted_current.txt
+++ b/emoji2/emoji2-views-helper/api/restricted_current.txt
@@ -4,7 +4,7 @@
   public final class EmojiEditTextHelper {
     ctor public EmojiEditTextHelper(android.widget.EditText);
     ctor public EmojiEditTextHelper(android.widget.EditText, boolean);
-    method public android.text.method.KeyListener getKeyListener(android.text.method.KeyListener);
+    method public android.text.method.KeyListener? getKeyListener(android.text.method.KeyListener?);
     method public int getMaxEmojiCount();
     method public boolean isEnabled();
     method public android.view.inputmethod.InputConnection? onCreateInputConnection(android.view.inputmethod.InputConnection?, android.view.inputmethod.EditorInfo);
diff --git a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiEditTextHelperTest.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiEditTextHelperTest.java
index 1210a11..9f46572 100644
--- a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiEditTextHelperTest.java
+++ b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiEditTextHelperTest.java
@@ -59,9 +59,9 @@
         mEmojiEditTextHelper = new EmojiEditTextHelper(mEditText);
     }
 
-    @Test(expected = NullPointerException.class)
-    public void testGetKeyListener_withNull_throwsException() {
-        mEmojiEditTextHelper.getKeyListener(null);
+    @Test
+    public void testGetKeyListener_withNull_returnsNull() {
+        assertNull(mEmojiEditTextHelper.getKeyListener(null));
     }
 
     @Test
diff --git a/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/viewsintegration/EmojiEditTextHelper.java b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/viewsintegration/EmojiEditTextHelper.java
index 0c482b3..5136889 100644
--- a/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/viewsintegration/EmojiEditTextHelper.java
+++ b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/viewsintegration/EmojiEditTextHelper.java
@@ -146,12 +146,11 @@
      *
      * @param keyListener KeyListener passed into {@link TextView#setKeyListener(KeyListener)}
      *
-     * @return a new KeyListener instance that wraps {@code keyListener}.
+     * @return a new KeyListener instance that wraps {@code keyListener}, or null if passed null.
      */
     @SuppressWarnings("ExecutorRegistration")
-    @NonNull
-    public KeyListener getKeyListener(@NonNull final KeyListener keyListener) {
-        Preconditions.checkNotNull(keyListener, "keyListener cannot be null");
+    @Nullable
+    public KeyListener getKeyListener(@Nullable final KeyListener keyListener) {
         return mHelper.getKeyListener(keyListener);
     }
 
@@ -230,7 +229,8 @@
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     static class HelperInternal {
 
-        KeyListener getKeyListener(@NonNull KeyListener keyListener) {
+        @Nullable
+        KeyListener getKeyListener(@Nullable KeyListener keyListener) {
             return keyListener;
         }
 
@@ -279,10 +279,16 @@
         }
 
         @Override
-        KeyListener getKeyListener(@NonNull final KeyListener keyListener) {
+        KeyListener getKeyListener(@Nullable final KeyListener keyListener) {
             if (keyListener instanceof EmojiKeyListener) {
                 return keyListener;
             }
+            if (keyListener == null) {
+                // don't wrap null key listener, as developer has explicitly request that editing
+                // be disabled (this causes keyboard and soft keyboard interactions to not be
+                // possible, and the EmojiKeyListener is not required)
+                return null;
+            }
             // make a KeyListener as it's always correct even if disabled
             return new EmojiKeyListener(keyListener);
         }
diff --git a/emoji2/emoji2/build.gradle b/emoji2/emoji2/build.gradle
index 73c2a4d..218741c 100644
--- a/emoji2/emoji2/build.gradle
+++ b/emoji2/emoji2/build.gradle
@@ -20,6 +20,7 @@
 )
 
 dependencies {
+    implementation project(path: ':annotation:annotation')
     bundleInside(project(":noto-emoji-compat-flatbuffers"))
 
     api("androidx.core:core:1.3.0")
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompatInitializer.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompatInitializer.java
index 69fd6904..4f64647 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompatInitializer.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompatInitializer.java
@@ -22,7 +22,9 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Process;
+import android.view.Choreographer;
 
+import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -79,21 +81,37 @@
     @Override
     public Boolean create(@NonNull Context context) {
         if (Build.VERSION.SDK_INT >= 19) {
-            final Handler mainHandler;
-            if (Build.VERSION.SDK_INT >= 28) {
-                mainHandler = Handler28Impl.createAsync(Looper.getMainLooper());
-            } else {
-                mainHandler = new Handler(Looper.getMainLooper());
-            }
             EmojiCompat.init(new BackgroundDefaultConfig(context));
-            mainHandler.postDelayed(new LoadEmojiCompatRunnable(),
-                    STARTUP_THREAD_CREATION_DELAY_MS);
+            delayAfterFirstFrame();
             return true;
         }
         return false;
     }
 
     /**
+     * Wait until the first frame of the application to do anything.
+     *
+     * This allows startup code to run before the delay is scheduled.
+     */
+    @RequiresApi(19)
+    void delayAfterFirstFrame() {
+        // schedule delay after first frame callback
+        Choreographer16Impl.postFrameCallback(this::loadEmojiCompatAfterDelay);
+    }
+
+    @RequiresApi(19)
+    private void loadEmojiCompatAfterDelay() {
+        final Handler mainHandler;
+        if (Build.VERSION.SDK_INT >= 28) {
+            mainHandler = Handler28Impl.createAsync(Looper.getMainLooper());
+        } else {
+            mainHandler = new Handler(Looper.getMainLooper());
+        }
+        mainHandler.postDelayed(new LoadEmojiCompatRunnable(),
+                STARTUP_THREAD_CREATION_DELAY_MS);
+    }
+
+    /**
      * No dependencies
      */
     @NonNull
@@ -195,13 +213,25 @@
         }
     }
 
+    @RequiresApi(16)
+    private static class Choreographer16Impl {
+        private Choreographer16Impl() {
+            // Non-instantiable.
+        }
+
+        @DoNotInline
+        public static void postFrameCallback(Runnable r) {
+            Choreographer.getInstance().postFrameCallback(frameTimeNanos -> r.run());
+        }
+    }
+
     @RequiresApi(28)
     private static class Handler28Impl {
         private Handler28Impl() {
             // Non-instantiable.
         }
 
-        // avoid aligning with vsync when available (API 28+)
+        @DoNotInline
         public static Handler createAsync(Looper looper) {
             return Handler.createAsync(looper);
         }
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt
index cff45a6..68affee 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt
@@ -358,6 +358,47 @@
         assertThat(fragment2.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
     }
 
+    @Suppress("DEPRECATION")
+    @Test
+    @UiThreadTest
+    fun removeFragmentAfterOnSaveInstanceStateCycle() {
+        val viewModelStore = ViewModelStore()
+        val fc = activityRule.startupFragmentController(viewModelStore)
+
+        val fm = fc.supportFragmentManager
+
+        val fragment1 = StrictViewFragment()
+        fm.beginTransaction()
+            .add(android.R.id.content, fragment1)
+            .commitNow()
+
+        val fragment2 = StrictViewFragment()
+        fm.beginTransaction()
+            .replace(android.R.id.content, fragment2)
+            .addToBackStack(null)
+            .commit()
+        fm.executePendingTransactions()
+
+        // Now go through a cycle of onSaveInstanceState
+        fc.dispatchPause()
+        fc.dispatchStop()
+        fc.saveAllState()
+        fc.noteStateNotSaved()
+        fc.dispatchStart()
+        fc.dispatchResume()
+
+        // Now remove fragment2, which will clear out all of its state
+        fm.popBackStackImmediate()
+
+        // And go through a full Fragment restart
+        val fc2 = fc.restart(activityRule, viewModelStore, false)
+        val fm2 = fc2.supportFragmentManager
+
+        // All saved state should have been re-associated with the remaining
+        // fragments, with no state saved for fragments that have been popped
+        assertThat(fm2.fragmentStore.allSavedState).isEmpty()
+    }
+
     @Test
     @UiThreadTest
     fun addChildFragmentInAttach() {
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/SaveRestoreBackStackTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/SaveRestoreBackStackTest.kt
index f14d650..8403993 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/SaveRestoreBackStackTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/SaveRestoreBackStackTest.kt
@@ -39,7 +39,7 @@
             val fm = withActivity {
                 supportFragmentManager
             }
-            val fragmentBase = StrictFragment()
+            val fragmentBase = StrictViewFragment()
             val fragmentReplacement = StateSaveFragment()
 
             fm.beginTransaction()
@@ -89,8 +89,8 @@
             val fm = withActivity {
                 supportFragmentManager
             }
-            val fragmentBase = StrictFragment()
-            val fragmentReplacement = StrictFragment()
+            val fragmentBase = StrictViewFragment()
+            val fragmentReplacement = StrictViewFragment()
 
             fm.beginTransaction()
                 .add(R.id.content, fragmentBase)
@@ -128,8 +128,8 @@
             val fm = withActivity {
                 supportFragmentManager
             }
-            val fragmentBase = StrictFragment()
-            val fragmentReplacement = StrictFragment()
+            val fragmentBase = StrictViewFragment()
+            val fragmentReplacement = StrictViewFragment()
 
             fm.beginTransaction()
                 .add(R.id.content, fragmentBase)
@@ -167,8 +167,8 @@
             val fm = withActivity {
                 supportFragmentManager
             }
-            val fragmentBase = StrictFragment()
-            val fragmentReplacement = StrictFragment()
+            val fragmentBase = StrictViewFragment()
+            val fragmentReplacement = StrictViewFragment()
             fragmentReplacement.retainInstance = true
 
             fm.beginTransaction()
@@ -207,8 +207,8 @@
             val fm = withActivity {
                 supportFragmentManager
             }
-            val fragmentBase = StrictFragment()
-            val fragmentReplacement = StrictFragment()
+            val fragmentBase = StrictViewFragment()
+            val fragmentReplacement = StrictViewFragment()
 
             fm.beginTransaction()
                 .add(R.id.content, fragmentBase)
@@ -250,7 +250,7 @@
             val fm = withActivity {
                 supportFragmentManager
             }
-            val fragmentBase = StrictFragment()
+            val fragmentBase = StrictViewFragment()
             val fragmentReplacement = StateSaveFragment("saved", "unsaved")
 
             fm.beginTransaction()
@@ -312,7 +312,7 @@
             var fm = withActivity {
                 supportFragmentManager
             }
-            val fragmentBase = StrictFragment()
+            val fragmentBase = StrictViewFragment()
             val fragmentReplacement = StateSaveFragment("saved", "unsaved")
 
             fm.beginTransaction()
@@ -381,7 +381,7 @@
             val fm = withActivity {
                 supportFragmentManager
             }
-            val fragmentBase = StrictFragment()
+            val fragmentBase = StrictViewFragment()
             val fragmentReplacement = StateSaveFragment("saved", "unsaved")
 
             fm.beginTransaction()
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
index e6f976b..1b34ade 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
@@ -733,6 +733,10 @@
             Log.d(TAG, "movefrom CREATED: " + mFragment);
         }
         boolean beingRemoved = mFragment.mRemoving && !mFragment.isInBackStack();
+        // Clear any previous saved state
+        if (beingRemoved && !mFragment.mBeingSaved) {
+            mFragmentStore.setSavedState(mFragment.mWho, null);
+        }
         boolean shouldDestroy = beingRemoved
                 || mFragmentStore.getNonConfig().shouldDestroy(mFragment);
         if (shouldDestroy) {
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/SetTargetFragmentUsageViolation.java b/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/SetTargetFragmentUsageViolation.java
index ce8aa7f..76962eda 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/SetTargetFragmentUsageViolation.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/SetTargetFragmentUsageViolation.java
@@ -34,11 +34,17 @@
         this.mRequestCode = requestCode;
     }
 
+    /**
+     * Gets the target {@link Fragment} that was being set in the call causing the Violation.
+     */
     @NonNull
     public Fragment getTargetFragment() {
         return mTargetFragment;
     }
 
+    /**
+     * Gets the request code that was passed in the call causing the Violation.
+     */
     public int getRequestCode() {
         return mRequestCode;
     }
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/Violation.java b/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/Violation.java
index 2fef49d..fae9784 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/Violation.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/Violation.java
@@ -29,6 +29,9 @@
         mFragment = fragment;
     }
 
+    /**
+     * Gets the {@link Fragment} causing the Violation.
+     */
     @NonNull
     public Fragment getFragment() {
         return mFragment;
diff --git a/media2/media2-common/api/1.2.0-beta01.txt b/media2/media2-common/api/1.2.0-beta01.txt
new file mode 100644
index 0000000..7992d7b
--- /dev/null
+++ b/media2/media2-common/api/1.2.0-beta01.txt
@@ -0,0 +1,273 @@
+// Signature format: 4.0
+package androidx.media2.common {
+
+  public class CallbackMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
+    method public androidx.media2.common.DataSourceCallback getDataSourceCallback();
+  }
+
+  public static final class CallbackMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
+    ctor public CallbackMediaItem.Builder(androidx.media2.common.DataSourceCallback);
+    method public androidx.media2.common.CallbackMediaItem build();
+    method public androidx.media2.common.CallbackMediaItem.Builder setEndPosition(long);
+    method public androidx.media2.common.CallbackMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
+    method public androidx.media2.common.CallbackMediaItem.Builder setStartPosition(long);
+  }
+
+  public abstract class DataSourceCallback implements java.io.Closeable {
+    ctor public DataSourceCallback();
+    method public abstract long getSize() throws java.io.IOException;
+    method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
+  }
+
+  public class FileMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
+    method public long getFileDescriptorLength();
+    method public long getFileDescriptorOffset();
+    method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
+    field public static final long FD_LENGTH_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
+  }
+
+  public static final class FileMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
+    ctor public FileMediaItem.Builder(android.os.ParcelFileDescriptor);
+    method public androidx.media2.common.FileMediaItem build();
+    method public androidx.media2.common.FileMediaItem.Builder setEndPosition(long);
+    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorLength(long);
+    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorOffset(long);
+    method public androidx.media2.common.FileMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
+    method public androidx.media2.common.FileMediaItem.Builder setStartPosition(long);
+  }
+
+  public class MediaItem implements androidx.versionedparcelable.VersionedParcelable {
+    method public long getEndPosition();
+    method public androidx.media2.common.MediaMetadata? getMetadata();
+    method public long getStartPosition();
+    method public void setMetadata(androidx.media2.common.MediaMetadata?);
+    field public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
+  }
+
+  public static class MediaItem.Builder {
+    ctor public MediaItem.Builder();
+    method public androidx.media2.common.MediaItem build();
+    method public androidx.media2.common.MediaItem.Builder setEndPosition(long);
+    method public androidx.media2.common.MediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
+    method public androidx.media2.common.MediaItem.Builder setStartPosition(long);
+  }
+
+  public final class MediaMetadata implements androidx.versionedparcelable.VersionedParcelable {
+    method public boolean containsKey(String);
+    method public android.graphics.Bitmap? getBitmap(String);
+    method public android.os.Bundle? getExtras();
+    method public float getFloat(String);
+    method public long getLong(String);
+    method public String? getMediaId();
+    method public androidx.media2.common.Rating? getRating(String);
+    method public String? getString(String);
+    method public CharSequence? getText(String);
+    method public java.util.Set<java.lang.String!> keySet();
+    method public int size();
+    field public static final long BROWSABLE_TYPE_ALBUMS = 2L; // 0x2L
+    field public static final long BROWSABLE_TYPE_ARTISTS = 3L; // 0x3L
+    field public static final long BROWSABLE_TYPE_GENRES = 4L; // 0x4L
+    field public static final long BROWSABLE_TYPE_MIXED = 0L; // 0x0L
+    field public static final long BROWSABLE_TYPE_NONE = -1L; // 0xffffffffffffffffL
+    field public static final long BROWSABLE_TYPE_PLAYLISTS = 5L; // 0x5L
+    field public static final long BROWSABLE_TYPE_TITLES = 1L; // 0x1L
+    field public static final long BROWSABLE_TYPE_YEARS = 6L; // 0x6L
+    field public static final String METADATA_KEY_ADVERTISEMENT = "androidx.media2.metadata.ADVERTISEMENT";
+    field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
+    field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
+    field public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+    field public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
+    field public static final String METADATA_KEY_ART = "android.media.metadata.ART";
+    field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
+    field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
+    field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
+    field public static final String METADATA_KEY_BROWSABLE = "androidx.media2.metadata.BROWSABLE";
+    field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
+    field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
+    field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
+    field public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+    field public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
+    field public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
+    field public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
+    field public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
+    field public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
+    field public static final String METADATA_KEY_DOWNLOAD_STATUS = "androidx.media2.metadata.DOWNLOAD_STATUS";
+    field public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
+    field public static final String METADATA_KEY_EXTRAS = "androidx.media2.metadata.EXTRAS";
+    field public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
+    field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
+    field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
+    field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+    field public static final String METADATA_KEY_PLAYABLE = "androidx.media2.metadata.PLAYABLE";
+    field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
+    field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
+    field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+    field public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
+    field public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
+    field public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
+    field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
+    field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
+    field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
+  }
+
+  public static final class MediaMetadata.Builder {
+    ctor public MediaMetadata.Builder();
+    ctor public MediaMetadata.Builder(androidx.media2.common.MediaMetadata);
+    method public androidx.media2.common.MediaMetadata build();
+    method public androidx.media2.common.MediaMetadata.Builder putBitmap(String, android.graphics.Bitmap?);
+    method public androidx.media2.common.MediaMetadata.Builder putFloat(String, float);
+    method public androidx.media2.common.MediaMetadata.Builder putLong(String, long);
+    method public androidx.media2.common.MediaMetadata.Builder putRating(String, androidx.media2.common.Rating?);
+    method public androidx.media2.common.MediaMetadata.Builder putString(String, String?);
+    method public androidx.media2.common.MediaMetadata.Builder putText(String, CharSequence?);
+    method public androidx.media2.common.MediaMetadata.Builder setExtras(android.os.Bundle?);
+  }
+
+  public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
+    method public boolean isRated();
+  }
+
+  public abstract class SessionPlayer implements java.io.Closeable {
+    ctor public SessionPlayer();
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
+    method @CallSuper public void close();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
+    method public abstract androidx.media.AudioAttributesCompat? getAudioAttributes();
+    method public abstract long getBufferedPosition();
+    method public abstract int getBufferingState();
+    method protected final java.util.List<androidx.core.util.Pair<androidx.media2.common.SessionPlayer.PlayerCallback!,java.util.concurrent.Executor!>!> getCallbacks();
+    method public abstract androidx.media2.common.MediaItem? getCurrentMediaItem();
+    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getCurrentMediaItemIndex();
+    method public abstract long getCurrentPosition();
+    method public abstract long getDuration();
+    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getNextMediaItemIndex();
+    method public abstract float getPlaybackSpeed();
+    method public abstract int getPlayerState();
+    method public abstract java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
+    method public abstract androidx.media2.common.MediaMetadata? getPlaylistMetadata();
+    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getPreviousMediaItemIndex();
+    method public abstract int getRepeatMode();
+    method public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(int);
+    method public abstract int getShuffleMode();
+    method public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
+    method public androidx.media2.common.VideoSize getVideoSize();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
+    method public final void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.common.SessionPlayer.PlayerCallback);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(float);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setSurface(android.view.Surface?);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
+    method public final void unregisterPlayerCallback(androidx.media2.common.SessionPlayer.PlayerCallback);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
+    field public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1; // 0x1
+    field public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2; // 0x2
+    field public static final int BUFFERING_STATE_COMPLETE = 3; // 0x3
+    field public static final int BUFFERING_STATE_UNKNOWN = 0; // 0x0
+    field public static final int INVALID_ITEM_INDEX = -1; // 0xffffffff
+    field public static final int PLAYER_STATE_ERROR = 3; // 0x3
+    field public static final int PLAYER_STATE_IDLE = 0; // 0x0
+    field public static final int PLAYER_STATE_PAUSED = 1; // 0x1
+    field public static final int PLAYER_STATE_PLAYING = 2; // 0x2
+    field public static final int REPEAT_MODE_ALL = 2; // 0x2
+    field public static final int REPEAT_MODE_GROUP = 3; // 0x3
+    field public static final int REPEAT_MODE_NONE = 0; // 0x0
+    field public static final int REPEAT_MODE_ONE = 1; // 0x1
+    field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
+    field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
+    field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
+    field public static final long UNKNOWN_TIME = -9223372036854775808L; // 0x8000000000000000L
+  }
+
+  public abstract static class SessionPlayer.PlayerCallback {
+    ctor public SessionPlayer.PlayerCallback();
+    method public void onAudioAttributesChanged(androidx.media2.common.SessionPlayer, androidx.media.AudioAttributesCompat?);
+    method public void onBufferingStateChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?, int);
+    method public void onCurrentMediaItemChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?);
+    method public void onPlaybackCompleted(androidx.media2.common.SessionPlayer);
+    method public void onPlaybackSpeedChanged(androidx.media2.common.SessionPlayer, float);
+    method public void onPlayerStateChanged(androidx.media2.common.SessionPlayer, int);
+    method public void onPlaylistChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
+    method public void onPlaylistMetadataChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaMetadata?);
+    method public void onRepeatModeChanged(androidx.media2.common.SessionPlayer, int);
+    method public void onSeekCompleted(androidx.media2.common.SessionPlayer, long);
+    method public void onShuffleModeChanged(androidx.media2.common.SessionPlayer, int);
+    method public void onSubtitleData(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
+    method public void onTrackDeselected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
+    method public void onTrackSelected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
+    method public void onTracksChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
+    method public void onVideoSizeChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.VideoSize);
+  }
+
+  public static class SessionPlayer.PlayerResult {
+    ctor public SessionPlayer.PlayerResult(int, androidx.media2.common.MediaItem?);
+    method public long getCompletionTime();
+    method public androidx.media2.common.MediaItem? getMediaItem();
+    method public int getResultCode();
+    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
+    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
+    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
+    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
+    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
+    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
+    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
+    field public static final int RESULT_SUCCESS = 0; // 0x0
+  }
+
+  public static class SessionPlayer.TrackInfo implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?);
+    ctor public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?, boolean);
+    method public android.media.MediaFormat? getFormat();
+    method public int getId();
+    method public java.util.Locale getLanguage();
+    method public int getTrackType();
+    method public boolean isSelectable();
+    field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
+    field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
+    field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
+    field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
+    field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
+  }
+
+  public final class SubtitleData implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public SubtitleData(long, long, byte[]);
+    method public byte[] getData();
+    method public long getDurationUs();
+    method public long getStartTimeUs();
+  }
+
+  public class UriMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
+    method public android.net.Uri getUri();
+    method public java.util.List<java.net.HttpCookie!>? getUriCookies();
+    method public java.util.Map<java.lang.String!,java.lang.String!>? getUriHeaders();
+  }
+
+  public static final class UriMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
+    ctor public UriMediaItem.Builder(android.net.Uri);
+    ctor public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String!,java.lang.String!>?, java.util.List<java.net.HttpCookie!>?);
+    method public androidx.media2.common.UriMediaItem build();
+    method public androidx.media2.common.UriMediaItem.Builder setEndPosition(long);
+    method public androidx.media2.common.UriMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
+    method public androidx.media2.common.UriMediaItem.Builder setStartPosition(long);
+  }
+
+  public class VideoSize implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public VideoSize(@IntRange(from=0) int, @IntRange(from=0) int);
+    method @IntRange(from=0) public int getHeight();
+    method @IntRange(from=0) public int getWidth();
+  }
+
+}
+
diff --git a/media2/media2-common/api/public_plus_experimental_1.2.0-beta01.txt b/media2/media2-common/api/public_plus_experimental_1.2.0-beta01.txt
new file mode 100644
index 0000000..7992d7b
--- /dev/null
+++ b/media2/media2-common/api/public_plus_experimental_1.2.0-beta01.txt
@@ -0,0 +1,273 @@
+// Signature format: 4.0
+package androidx.media2.common {
+
+  public class CallbackMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
+    method public androidx.media2.common.DataSourceCallback getDataSourceCallback();
+  }
+
+  public static final class CallbackMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
+    ctor public CallbackMediaItem.Builder(androidx.media2.common.DataSourceCallback);
+    method public androidx.media2.common.CallbackMediaItem build();
+    method public androidx.media2.common.CallbackMediaItem.Builder setEndPosition(long);
+    method public androidx.media2.common.CallbackMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
+    method public androidx.media2.common.CallbackMediaItem.Builder setStartPosition(long);
+  }
+
+  public abstract class DataSourceCallback implements java.io.Closeable {
+    ctor public DataSourceCallback();
+    method public abstract long getSize() throws java.io.IOException;
+    method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
+  }
+
+  public class FileMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
+    method public long getFileDescriptorLength();
+    method public long getFileDescriptorOffset();
+    method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
+    field public static final long FD_LENGTH_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
+  }
+
+  public static final class FileMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
+    ctor public FileMediaItem.Builder(android.os.ParcelFileDescriptor);
+    method public androidx.media2.common.FileMediaItem build();
+    method public androidx.media2.common.FileMediaItem.Builder setEndPosition(long);
+    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorLength(long);
+    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorOffset(long);
+    method public androidx.media2.common.FileMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
+    method public androidx.media2.common.FileMediaItem.Builder setStartPosition(long);
+  }
+
+  public class MediaItem implements androidx.versionedparcelable.VersionedParcelable {
+    method public long getEndPosition();
+    method public androidx.media2.common.MediaMetadata? getMetadata();
+    method public long getStartPosition();
+    method public void setMetadata(androidx.media2.common.MediaMetadata?);
+    field public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
+  }
+
+  public static class MediaItem.Builder {
+    ctor public MediaItem.Builder();
+    method public androidx.media2.common.MediaItem build();
+    method public androidx.media2.common.MediaItem.Builder setEndPosition(long);
+    method public androidx.media2.common.MediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
+    method public androidx.media2.common.MediaItem.Builder setStartPosition(long);
+  }
+
+  public final class MediaMetadata implements androidx.versionedparcelable.VersionedParcelable {
+    method public boolean containsKey(String);
+    method public android.graphics.Bitmap? getBitmap(String);
+    method public android.os.Bundle? getExtras();
+    method public float getFloat(String);
+    method public long getLong(String);
+    method public String? getMediaId();
+    method public androidx.media2.common.Rating? getRating(String);
+    method public String? getString(String);
+    method public CharSequence? getText(String);
+    method public java.util.Set<java.lang.String!> keySet();
+    method public int size();
+    field public static final long BROWSABLE_TYPE_ALBUMS = 2L; // 0x2L
+    field public static final long BROWSABLE_TYPE_ARTISTS = 3L; // 0x3L
+    field public static final long BROWSABLE_TYPE_GENRES = 4L; // 0x4L
+    field public static final long BROWSABLE_TYPE_MIXED = 0L; // 0x0L
+    field public static final long BROWSABLE_TYPE_NONE = -1L; // 0xffffffffffffffffL
+    field public static final long BROWSABLE_TYPE_PLAYLISTS = 5L; // 0x5L
+    field public static final long BROWSABLE_TYPE_TITLES = 1L; // 0x1L
+    field public static final long BROWSABLE_TYPE_YEARS = 6L; // 0x6L
+    field public static final String METADATA_KEY_ADVERTISEMENT = "androidx.media2.metadata.ADVERTISEMENT";
+    field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
+    field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
+    field public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+    field public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
+    field public static final String METADATA_KEY_ART = "android.media.metadata.ART";
+    field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
+    field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
+    field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
+    field public static final String METADATA_KEY_BROWSABLE = "androidx.media2.metadata.BROWSABLE";
+    field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
+    field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
+    field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
+    field public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+    field public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
+    field public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
+    field public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
+    field public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
+    field public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
+    field public static final String METADATA_KEY_DOWNLOAD_STATUS = "androidx.media2.metadata.DOWNLOAD_STATUS";
+    field public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
+    field public static final String METADATA_KEY_EXTRAS = "androidx.media2.metadata.EXTRAS";
+    field public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
+    field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
+    field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
+    field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+    field public static final String METADATA_KEY_PLAYABLE = "androidx.media2.metadata.PLAYABLE";
+    field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
+    field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
+    field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+    field public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
+    field public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
+    field public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
+    field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
+    field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
+    field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
+  }
+
+  public static final class MediaMetadata.Builder {
+    ctor public MediaMetadata.Builder();
+    ctor public MediaMetadata.Builder(androidx.media2.common.MediaMetadata);
+    method public androidx.media2.common.MediaMetadata build();
+    method public androidx.media2.common.MediaMetadata.Builder putBitmap(String, android.graphics.Bitmap?);
+    method public androidx.media2.common.MediaMetadata.Builder putFloat(String, float);
+    method public androidx.media2.common.MediaMetadata.Builder putLong(String, long);
+    method public androidx.media2.common.MediaMetadata.Builder putRating(String, androidx.media2.common.Rating?);
+    method public androidx.media2.common.MediaMetadata.Builder putString(String, String?);
+    method public androidx.media2.common.MediaMetadata.Builder putText(String, CharSequence?);
+    method public androidx.media2.common.MediaMetadata.Builder setExtras(android.os.Bundle?);
+  }
+
+  public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
+    method public boolean isRated();
+  }
+
+  public abstract class SessionPlayer implements java.io.Closeable {
+    ctor public SessionPlayer();
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
+    method @CallSuper public void close();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
+    method public abstract androidx.media.AudioAttributesCompat? getAudioAttributes();
+    method public abstract long getBufferedPosition();
+    method public abstract int getBufferingState();
+    method protected final java.util.List<androidx.core.util.Pair<androidx.media2.common.SessionPlayer.PlayerCallback!,java.util.concurrent.Executor!>!> getCallbacks();
+    method public abstract androidx.media2.common.MediaItem? getCurrentMediaItem();
+    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getCurrentMediaItemIndex();
+    method public abstract long getCurrentPosition();
+    method public abstract long getDuration();
+    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getNextMediaItemIndex();
+    method public abstract float getPlaybackSpeed();
+    method public abstract int getPlayerState();
+    method public abstract java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
+    method public abstract androidx.media2.common.MediaMetadata? getPlaylistMetadata();
+    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getPreviousMediaItemIndex();
+    method public abstract int getRepeatMode();
+    method public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(int);
+    method public abstract int getShuffleMode();
+    method public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
+    method public androidx.media2.common.VideoSize getVideoSize();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
+    method public final void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.common.SessionPlayer.PlayerCallback);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(float);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setSurface(android.view.Surface?);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
+    method public final void unregisterPlayerCallback(androidx.media2.common.SessionPlayer.PlayerCallback);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
+    field public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1; // 0x1
+    field public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2; // 0x2
+    field public static final int BUFFERING_STATE_COMPLETE = 3; // 0x3
+    field public static final int BUFFERING_STATE_UNKNOWN = 0; // 0x0
+    field public static final int INVALID_ITEM_INDEX = -1; // 0xffffffff
+    field public static final int PLAYER_STATE_ERROR = 3; // 0x3
+    field public static final int PLAYER_STATE_IDLE = 0; // 0x0
+    field public static final int PLAYER_STATE_PAUSED = 1; // 0x1
+    field public static final int PLAYER_STATE_PLAYING = 2; // 0x2
+    field public static final int REPEAT_MODE_ALL = 2; // 0x2
+    field public static final int REPEAT_MODE_GROUP = 3; // 0x3
+    field public static final int REPEAT_MODE_NONE = 0; // 0x0
+    field public static final int REPEAT_MODE_ONE = 1; // 0x1
+    field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
+    field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
+    field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
+    field public static final long UNKNOWN_TIME = -9223372036854775808L; // 0x8000000000000000L
+  }
+
+  public abstract static class SessionPlayer.PlayerCallback {
+    ctor public SessionPlayer.PlayerCallback();
+    method public void onAudioAttributesChanged(androidx.media2.common.SessionPlayer, androidx.media.AudioAttributesCompat?);
+    method public void onBufferingStateChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?, int);
+    method public void onCurrentMediaItemChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?);
+    method public void onPlaybackCompleted(androidx.media2.common.SessionPlayer);
+    method public void onPlaybackSpeedChanged(androidx.media2.common.SessionPlayer, float);
+    method public void onPlayerStateChanged(androidx.media2.common.SessionPlayer, int);
+    method public void onPlaylistChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
+    method public void onPlaylistMetadataChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaMetadata?);
+    method public void onRepeatModeChanged(androidx.media2.common.SessionPlayer, int);
+    method public void onSeekCompleted(androidx.media2.common.SessionPlayer, long);
+    method public void onShuffleModeChanged(androidx.media2.common.SessionPlayer, int);
+    method public void onSubtitleData(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
+    method public void onTrackDeselected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
+    method public void onTrackSelected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
+    method public void onTracksChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
+    method public void onVideoSizeChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.VideoSize);
+  }
+
+  public static class SessionPlayer.PlayerResult {
+    ctor public SessionPlayer.PlayerResult(int, androidx.media2.common.MediaItem?);
+    method public long getCompletionTime();
+    method public androidx.media2.common.MediaItem? getMediaItem();
+    method public int getResultCode();
+    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
+    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
+    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
+    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
+    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
+    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
+    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
+    field public static final int RESULT_SUCCESS = 0; // 0x0
+  }
+
+  public static class SessionPlayer.TrackInfo implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?);
+    ctor public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?, boolean);
+    method public android.media.MediaFormat? getFormat();
+    method public int getId();
+    method public java.util.Locale getLanguage();
+    method public int getTrackType();
+    method public boolean isSelectable();
+    field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
+    field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
+    field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
+    field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
+    field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
+  }
+
+  public final class SubtitleData implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public SubtitleData(long, long, byte[]);
+    method public byte[] getData();
+    method public long getDurationUs();
+    method public long getStartTimeUs();
+  }
+
+  public class UriMediaItem extends androidx.media2.common.MediaItem implements androidx.versionedparcelable.VersionedParcelable {
+    method public android.net.Uri getUri();
+    method public java.util.List<java.net.HttpCookie!>? getUriCookies();
+    method public java.util.Map<java.lang.String!,java.lang.String!>? getUriHeaders();
+  }
+
+  public static final class UriMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
+    ctor public UriMediaItem.Builder(android.net.Uri);
+    ctor public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String!,java.lang.String!>?, java.util.List<java.net.HttpCookie!>?);
+    method public androidx.media2.common.UriMediaItem build();
+    method public androidx.media2.common.UriMediaItem.Builder setEndPosition(long);
+    method public androidx.media2.common.UriMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
+    method public androidx.media2.common.UriMediaItem.Builder setStartPosition(long);
+  }
+
+  public class VideoSize implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public VideoSize(@IntRange(from=0) int, @IntRange(from=0) int);
+    method @IntRange(from=0) public int getHeight();
+    method @IntRange(from=0) public int getWidth();
+  }
+
+}
+
diff --git a/media2/media2-common/api/res-1.2.0-beta01.txt b/media2/media2-common/api/res-1.2.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/media2/media2-common/api/res-1.2.0-beta01.txt
diff --git a/media2/media2-common/api/restricted_1.2.0-beta01.txt b/media2/media2-common/api/restricted_1.2.0-beta01.txt
new file mode 100644
index 0000000..097aa96
--- /dev/null
+++ b/media2/media2-common/api/restricted_1.2.0-beta01.txt
@@ -0,0 +1,273 @@
+// Signature format: 4.0
+package androidx.media2.common {
+
+  public class CallbackMediaItem extends androidx.media2.common.MediaItem {
+    method public androidx.media2.common.DataSourceCallback getDataSourceCallback();
+  }
+
+  public static final class CallbackMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
+    ctor public CallbackMediaItem.Builder(androidx.media2.common.DataSourceCallback);
+    method public androidx.media2.common.CallbackMediaItem build();
+    method public androidx.media2.common.CallbackMediaItem.Builder setEndPosition(long);
+    method public androidx.media2.common.CallbackMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
+    method public androidx.media2.common.CallbackMediaItem.Builder setStartPosition(long);
+  }
+
+  public abstract class DataSourceCallback implements java.io.Closeable {
+    ctor public DataSourceCallback();
+    method public abstract long getSize() throws java.io.IOException;
+    method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
+  }
+
+  public class FileMediaItem extends androidx.media2.common.MediaItem {
+    method public long getFileDescriptorLength();
+    method public long getFileDescriptorOffset();
+    method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
+    field public static final long FD_LENGTH_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
+  }
+
+  public static final class FileMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
+    ctor public FileMediaItem.Builder(android.os.ParcelFileDescriptor);
+    method public androidx.media2.common.FileMediaItem build();
+    method public androidx.media2.common.FileMediaItem.Builder setEndPosition(long);
+    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorLength(long);
+    method public androidx.media2.common.FileMediaItem.Builder setFileDescriptorOffset(long);
+    method public androidx.media2.common.FileMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
+    method public androidx.media2.common.FileMediaItem.Builder setStartPosition(long);
+  }
+
+  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class MediaItem extends androidx.versionedparcelable.CustomVersionedParcelable {
+    method public long getEndPosition();
+    method public androidx.media2.common.MediaMetadata? getMetadata();
+    method public long getStartPosition();
+    method public void setMetadata(androidx.media2.common.MediaMetadata?);
+    field public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
+  }
+
+  public static class MediaItem.Builder {
+    ctor public MediaItem.Builder();
+    method public androidx.media2.common.MediaItem build();
+    method public androidx.media2.common.MediaItem.Builder setEndPosition(long);
+    method public androidx.media2.common.MediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
+    method public androidx.media2.common.MediaItem.Builder setStartPosition(long);
+  }
+
+  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public final class MediaMetadata extends androidx.versionedparcelable.CustomVersionedParcelable {
+    method public boolean containsKey(String);
+    method public android.graphics.Bitmap? getBitmap(String);
+    method public android.os.Bundle? getExtras();
+    method public float getFloat(String);
+    method public long getLong(String);
+    method public String? getMediaId();
+    method public androidx.media2.common.Rating? getRating(String);
+    method public String? getString(String);
+    method public CharSequence? getText(String);
+    method public java.util.Set<java.lang.String!> keySet();
+    method public int size();
+    field public static final long BROWSABLE_TYPE_ALBUMS = 2L; // 0x2L
+    field public static final long BROWSABLE_TYPE_ARTISTS = 3L; // 0x3L
+    field public static final long BROWSABLE_TYPE_GENRES = 4L; // 0x4L
+    field public static final long BROWSABLE_TYPE_MIXED = 0L; // 0x0L
+    field public static final long BROWSABLE_TYPE_NONE = -1L; // 0xffffffffffffffffL
+    field public static final long BROWSABLE_TYPE_PLAYLISTS = 5L; // 0x5L
+    field public static final long BROWSABLE_TYPE_TITLES = 1L; // 0x1L
+    field public static final long BROWSABLE_TYPE_YEARS = 6L; // 0x6L
+    field public static final String METADATA_KEY_ADVERTISEMENT = "androidx.media2.metadata.ADVERTISEMENT";
+    field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
+    field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
+    field public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+    field public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
+    field public static final String METADATA_KEY_ART = "android.media.metadata.ART";
+    field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
+    field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
+    field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
+    field public static final String METADATA_KEY_BROWSABLE = "androidx.media2.metadata.BROWSABLE";
+    field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
+    field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
+    field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
+    field public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+    field public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
+    field public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
+    field public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
+    field public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
+    field public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
+    field public static final String METADATA_KEY_DOWNLOAD_STATUS = "androidx.media2.metadata.DOWNLOAD_STATUS";
+    field public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
+    field public static final String METADATA_KEY_EXTRAS = "androidx.media2.metadata.EXTRAS";
+    field public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
+    field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
+    field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
+    field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+    field public static final String METADATA_KEY_PLAYABLE = "androidx.media2.metadata.PLAYABLE";
+    field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
+    field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
+    field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+    field public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
+    field public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
+    field public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
+    field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
+    field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
+    field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
+  }
+
+  public static final class MediaMetadata.Builder {
+    ctor public MediaMetadata.Builder();
+    ctor public MediaMetadata.Builder(androidx.media2.common.MediaMetadata);
+    method public androidx.media2.common.MediaMetadata build();
+    method public androidx.media2.common.MediaMetadata.Builder putBitmap(String, android.graphics.Bitmap?);
+    method public androidx.media2.common.MediaMetadata.Builder putFloat(String, float);
+    method public androidx.media2.common.MediaMetadata.Builder putLong(String, long);
+    method public androidx.media2.common.MediaMetadata.Builder putRating(String, androidx.media2.common.Rating?);
+    method public androidx.media2.common.MediaMetadata.Builder putString(String, String?);
+    method public androidx.media2.common.MediaMetadata.Builder putText(String, CharSequence?);
+    method public androidx.media2.common.MediaMetadata.Builder setExtras(android.os.Bundle?);
+  }
+
+  public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
+    method public boolean isRated();
+  }
+
+  public abstract class SessionPlayer implements java.io.Closeable {
+    ctor public SessionPlayer();
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
+    method @CallSuper public void close();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
+    method public abstract androidx.media.AudioAttributesCompat? getAudioAttributes();
+    method public abstract long getBufferedPosition();
+    method public abstract int getBufferingState();
+    method protected final java.util.List<androidx.core.util.Pair<androidx.media2.common.SessionPlayer.PlayerCallback!,java.util.concurrent.Executor!>!> getCallbacks();
+    method public abstract androidx.media2.common.MediaItem? getCurrentMediaItem();
+    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getCurrentMediaItemIndex();
+    method public abstract long getCurrentPosition();
+    method public abstract long getDuration();
+    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getNextMediaItemIndex();
+    method public abstract float getPlaybackSpeed();
+    method public abstract int getPlayerState();
+    method public abstract java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
+    method public abstract androidx.media2.common.MediaMetadata? getPlaylistMetadata();
+    method @IntRange(from=androidx.media2.common.SessionPlayer.INVALID_ITEM_INDEX) public abstract int getPreviousMediaItemIndex();
+    method public abstract int getRepeatMode();
+    method public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(int);
+    method public abstract int getShuffleMode();
+    method public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
+    method public androidx.media2.common.VideoSize getVideoSize();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
+    method public final void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.common.SessionPlayer.PlayerCallback);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(float);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setSurface(android.view.Surface?);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
+    method public final void unregisterPlayerCallback(androidx.media2.common.SessionPlayer.PlayerCallback);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
+    field public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1; // 0x1
+    field public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2; // 0x2
+    field public static final int BUFFERING_STATE_COMPLETE = 3; // 0x3
+    field public static final int BUFFERING_STATE_UNKNOWN = 0; // 0x0
+    field public static final int INVALID_ITEM_INDEX = -1; // 0xffffffff
+    field public static final int PLAYER_STATE_ERROR = 3; // 0x3
+    field public static final int PLAYER_STATE_IDLE = 0; // 0x0
+    field public static final int PLAYER_STATE_PAUSED = 1; // 0x1
+    field public static final int PLAYER_STATE_PLAYING = 2; // 0x2
+    field public static final int REPEAT_MODE_ALL = 2; // 0x2
+    field public static final int REPEAT_MODE_GROUP = 3; // 0x3
+    field public static final int REPEAT_MODE_NONE = 0; // 0x0
+    field public static final int REPEAT_MODE_ONE = 1; // 0x1
+    field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
+    field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
+    field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
+    field public static final long UNKNOWN_TIME = -9223372036854775808L; // 0x8000000000000000L
+  }
+
+  public abstract static class SessionPlayer.PlayerCallback {
+    ctor public SessionPlayer.PlayerCallback();
+    method public void onAudioAttributesChanged(androidx.media2.common.SessionPlayer, androidx.media.AudioAttributesCompat?);
+    method public void onBufferingStateChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?, int);
+    method public void onCurrentMediaItemChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem?);
+    method public void onPlaybackCompleted(androidx.media2.common.SessionPlayer);
+    method public void onPlaybackSpeedChanged(androidx.media2.common.SessionPlayer, float);
+    method public void onPlayerStateChanged(androidx.media2.common.SessionPlayer, int);
+    method public void onPlaylistChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
+    method public void onPlaylistMetadataChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaMetadata?);
+    method public void onRepeatModeChanged(androidx.media2.common.SessionPlayer, int);
+    method public void onSeekCompleted(androidx.media2.common.SessionPlayer, long);
+    method public void onShuffleModeChanged(androidx.media2.common.SessionPlayer, int);
+    method public void onSubtitleData(androidx.media2.common.SessionPlayer, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
+    method public void onTrackDeselected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
+    method public void onTrackSelected(androidx.media2.common.SessionPlayer, androidx.media2.common.SessionPlayer.TrackInfo);
+    method public void onTracksChanged(androidx.media2.common.SessionPlayer, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
+    method public void onVideoSizeChanged(androidx.media2.common.SessionPlayer, androidx.media2.common.VideoSize);
+  }
+
+  public static class SessionPlayer.PlayerResult {
+    ctor public SessionPlayer.PlayerResult(int, androidx.media2.common.MediaItem?);
+    method public long getCompletionTime();
+    method public androidx.media2.common.MediaItem? getMediaItem();
+    method public int getResultCode();
+    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
+    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
+    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
+    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
+    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
+    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
+    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
+    field public static final int RESULT_SUCCESS = 0; // 0x0
+  }
+
+  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public static class SessionPlayer.TrackInfo extends androidx.versionedparcelable.CustomVersionedParcelable {
+    ctor public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?);
+    ctor public SessionPlayer.TrackInfo(int, int, android.media.MediaFormat?, boolean);
+    method public android.media.MediaFormat? getFormat();
+    method public int getId();
+    method public java.util.Locale getLanguage();
+    method public int getTrackType();
+    method public boolean isSelectable();
+    field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
+    field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
+    field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
+    field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
+    field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
+  }
+
+  @androidx.versionedparcelable.VersionedParcelize public final class SubtitleData implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public SubtitleData(long, long, byte[]);
+    method public byte[] getData();
+    method public long getDurationUs();
+    method public long getStartTimeUs();
+  }
+
+  public class UriMediaItem extends androidx.media2.common.MediaItem {
+    method public android.net.Uri getUri();
+    method public java.util.List<java.net.HttpCookie!>? getUriCookies();
+    method public java.util.Map<java.lang.String!,java.lang.String!>? getUriHeaders();
+  }
+
+  public static final class UriMediaItem.Builder extends androidx.media2.common.MediaItem.Builder {
+    ctor public UriMediaItem.Builder(android.net.Uri);
+    ctor public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String!,java.lang.String!>?, java.util.List<java.net.HttpCookie!>?);
+    method public androidx.media2.common.UriMediaItem build();
+    method public androidx.media2.common.UriMediaItem.Builder setEndPosition(long);
+    method public androidx.media2.common.UriMediaItem.Builder setMetadata(androidx.media2.common.MediaMetadata?);
+    method public androidx.media2.common.UriMediaItem.Builder setStartPosition(long);
+  }
+
+  @androidx.versionedparcelable.VersionedParcelize public class VideoSize implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public VideoSize(@IntRange(from=0) int, @IntRange(from=0) int);
+    method @IntRange(from=0) public int getHeight();
+    method @IntRange(from=0) public int getWidth();
+  }
+
+}
+
diff --git a/media2/media2-exoplayer/api/1.2.0-beta01.txt b/media2/media2-exoplayer/api/1.2.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/media2/media2-exoplayer/api/1.2.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/media2/media2-exoplayer/api/public_plus_experimental_1.2.0-beta01.txt b/media2/media2-exoplayer/api/public_plus_experimental_1.2.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/media2/media2-exoplayer/api/public_plus_experimental_1.2.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/media2/media2-exoplayer/api/res-1.2.0-beta01.txt b/media2/media2-exoplayer/api/res-1.2.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/media2/media2-exoplayer/api/res-1.2.0-beta01.txt
diff --git a/media2/media2-exoplayer/api/restricted_1.2.0-beta01.txt b/media2/media2-exoplayer/api/restricted_1.2.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/media2/media2-exoplayer/api/restricted_1.2.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/media2/media2-player/api/1.2.0-beta01.txt b/media2/media2-player/api/1.2.0-beta01.txt
new file mode 100644
index 0000000..c5e968d
--- /dev/null
+++ b/media2/media2-player/api/1.2.0-beta01.txt
@@ -0,0 +1,123 @@
+// Signature format: 4.0
+package androidx.media2.player {
+
+  public final class MediaPlayer extends androidx.media2.common.SessionPlayer {
+    ctor public MediaPlayer(android.content.Context);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> attachAuxEffect(int);
+    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
+    method public int getAudioSessionId();
+    method public long getBufferedPosition();
+    method public int getBufferingState();
+    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
+    method public int getCurrentMediaItemIndex();
+    method public long getCurrentPosition();
+    method public long getDuration();
+    method public float getMaxPlayerVolume();
+    method public int getNextMediaItemIndex();
+    method public androidx.media2.player.PlaybackParams getPlaybackParams();
+    method @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
+    method public int getPlayerState();
+    method public float getPlayerVolume();
+    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
+    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
+    method public int getPreviousMediaItemIndex();
+    method public int getRepeatMode();
+    method public androidx.media2.player.MediaPlayer.TrackInfo? getSelectedTrack(int);
+    method public int getShuffleMode();
+    method public androidx.media2.player.MediaTimestamp? getTimestamp();
+    method @Deprecated public java.util.List<androidx.media2.player.MediaPlayer.TrackInfo!> getTrackInfo();
+    method public androidx.media2.player.VideoSize getVideoSize();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
+    method public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer.PlayerCallback);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
+    method public void reset();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long, int);
+    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.player.MediaPlayer.TrackInfo);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioSessionId(int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackParams(androidx.media2.player.PlaybackParams);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlayerVolume(@FloatRange(from=0, to=1) float);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
+    method public void unregisterPlayerCallback(androidx.media2.player.MediaPlayer.PlayerCallback);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
+    field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
+    field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
+    field public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
+    field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
+    field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
+    field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
+    field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
+    field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
+    field @Deprecated public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
+    field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
+    field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
+    field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
+    field public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
+    field public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
+    field public static final int SEEK_CLOSEST = 3; // 0x3
+    field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
+    field public static final int SEEK_NEXT_SYNC = 1; // 0x1
+    field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
+  }
+
+  public abstract static class MediaPlayer.PlayerCallback extends androidx.media2.common.SessionPlayer.PlayerCallback {
+    ctor public MediaPlayer.PlayerCallback();
+    method public void onError(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
+    method public void onInfo(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
+    method public void onMediaTimeDiscontinuity(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.MediaTimestamp);
+    method public void onTimedMetaDataAvailable(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.TimedMetaData);
+    method @Deprecated public void onVideoSizeChanged(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.VideoSize);
+  }
+
+  public static final class MediaPlayer.TrackInfo extends androidx.media2.common.SessionPlayer.TrackInfo implements androidx.versionedparcelable.VersionedParcelable {
+  }
+
+  public final class MediaTimestamp {
+    method public long getAnchorMediaTimeUs();
+    method public long getAnchorSystemNanoTime();
+    method public float getMediaClockRate();
+    field public static final androidx.media2.player.MediaTimestamp TIMESTAMP_UNKNOWN;
+  }
+
+  public final class PlaybackParams {
+    method public Integer? getAudioFallbackMode();
+    method public Float? getPitch();
+    method public Float? getSpeed();
+    field public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
+    field public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
+    field public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
+  }
+
+  public static final class PlaybackParams.Builder {
+    ctor public PlaybackParams.Builder();
+    ctor public PlaybackParams.Builder(androidx.media2.player.PlaybackParams);
+    method public androidx.media2.player.PlaybackParams build();
+    method public androidx.media2.player.PlaybackParams.Builder setAudioFallbackMode(int);
+    method public androidx.media2.player.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
+    method public androidx.media2.player.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
+  }
+
+  public class TimedMetaData {
+    method public byte[]! getMetaData();
+    method public long getTimestamp();
+  }
+
+  public final class VideoSize extends androidx.media2.common.VideoSize {
+    ctor public VideoSize(int, int);
+  }
+
+}
+
diff --git a/media2/media2-player/api/public_plus_experimental_1.2.0-beta01.txt b/media2/media2-player/api/public_plus_experimental_1.2.0-beta01.txt
new file mode 100644
index 0000000..c5e968d
--- /dev/null
+++ b/media2/media2-player/api/public_plus_experimental_1.2.0-beta01.txt
@@ -0,0 +1,123 @@
+// Signature format: 4.0
+package androidx.media2.player {
+
+  public final class MediaPlayer extends androidx.media2.common.SessionPlayer {
+    ctor public MediaPlayer(android.content.Context);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> attachAuxEffect(int);
+    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
+    method public int getAudioSessionId();
+    method public long getBufferedPosition();
+    method public int getBufferingState();
+    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
+    method public int getCurrentMediaItemIndex();
+    method public long getCurrentPosition();
+    method public long getDuration();
+    method public float getMaxPlayerVolume();
+    method public int getNextMediaItemIndex();
+    method public androidx.media2.player.PlaybackParams getPlaybackParams();
+    method @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
+    method public int getPlayerState();
+    method public float getPlayerVolume();
+    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
+    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
+    method public int getPreviousMediaItemIndex();
+    method public int getRepeatMode();
+    method public androidx.media2.player.MediaPlayer.TrackInfo? getSelectedTrack(int);
+    method public int getShuffleMode();
+    method public androidx.media2.player.MediaTimestamp? getTimestamp();
+    method @Deprecated public java.util.List<androidx.media2.player.MediaPlayer.TrackInfo!> getTrackInfo();
+    method public androidx.media2.player.VideoSize getVideoSize();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
+    method public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer.PlayerCallback);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
+    method public void reset();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long, int);
+    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.player.MediaPlayer.TrackInfo);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioSessionId(int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackParams(androidx.media2.player.PlaybackParams);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlayerVolume(@FloatRange(from=0, to=1) float);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
+    method public void unregisterPlayerCallback(androidx.media2.player.MediaPlayer.PlayerCallback);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
+    field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
+    field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
+    field public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
+    field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
+    field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
+    field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
+    field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
+    field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
+    field @Deprecated public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
+    field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
+    field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
+    field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
+    field public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
+    field public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
+    field public static final int SEEK_CLOSEST = 3; // 0x3
+    field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
+    field public static final int SEEK_NEXT_SYNC = 1; // 0x1
+    field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
+  }
+
+  public abstract static class MediaPlayer.PlayerCallback extends androidx.media2.common.SessionPlayer.PlayerCallback {
+    ctor public MediaPlayer.PlayerCallback();
+    method public void onError(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
+    method public void onInfo(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
+    method public void onMediaTimeDiscontinuity(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.MediaTimestamp);
+    method public void onTimedMetaDataAvailable(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.TimedMetaData);
+    method @Deprecated public void onVideoSizeChanged(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.VideoSize);
+  }
+
+  public static final class MediaPlayer.TrackInfo extends androidx.media2.common.SessionPlayer.TrackInfo implements androidx.versionedparcelable.VersionedParcelable {
+  }
+
+  public final class MediaTimestamp {
+    method public long getAnchorMediaTimeUs();
+    method public long getAnchorSystemNanoTime();
+    method public float getMediaClockRate();
+    field public static final androidx.media2.player.MediaTimestamp TIMESTAMP_UNKNOWN;
+  }
+
+  public final class PlaybackParams {
+    method public Integer? getAudioFallbackMode();
+    method public Float? getPitch();
+    method public Float? getSpeed();
+    field public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
+    field public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
+    field public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
+  }
+
+  public static final class PlaybackParams.Builder {
+    ctor public PlaybackParams.Builder();
+    ctor public PlaybackParams.Builder(androidx.media2.player.PlaybackParams);
+    method public androidx.media2.player.PlaybackParams build();
+    method public androidx.media2.player.PlaybackParams.Builder setAudioFallbackMode(int);
+    method public androidx.media2.player.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
+    method public androidx.media2.player.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
+  }
+
+  public class TimedMetaData {
+    method public byte[]! getMetaData();
+    method public long getTimestamp();
+  }
+
+  public final class VideoSize extends androidx.media2.common.VideoSize {
+    ctor public VideoSize(int, int);
+  }
+
+}
+
diff --git a/media2/media2-player/api/res-1.2.0-beta01.txt b/media2/media2-player/api/res-1.2.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/media2/media2-player/api/res-1.2.0-beta01.txt
diff --git a/media2/media2-player/api/restricted_1.2.0-beta01.txt b/media2/media2-player/api/restricted_1.2.0-beta01.txt
new file mode 100644
index 0000000..5ae3ddb
--- /dev/null
+++ b/media2/media2-player/api/restricted_1.2.0-beta01.txt
@@ -0,0 +1,123 @@
+// Signature format: 4.0
+package androidx.media2.player {
+
+  public final class MediaPlayer extends androidx.media2.common.SessionPlayer {
+    ctor public MediaPlayer(android.content.Context);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> addPlaylistItem(int, androidx.media2.common.MediaItem);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> attachAuxEffect(int);
+    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
+    method public int getAudioSessionId();
+    method public long getBufferedPosition();
+    method @androidx.media2.common.SessionPlayer.BuffState public int getBufferingState();
+    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
+    method public int getCurrentMediaItemIndex();
+    method public long getCurrentPosition();
+    method public long getDuration();
+    method public float getMaxPlayerVolume();
+    method public int getNextMediaItemIndex();
+    method public androidx.media2.player.PlaybackParams getPlaybackParams();
+    method @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
+    method @androidx.media2.common.SessionPlayer.PlayerState public int getPlayerState();
+    method public float getPlayerVolume();
+    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
+    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
+    method public int getPreviousMediaItemIndex();
+    method public int getRepeatMode();
+    method public androidx.media2.player.MediaPlayer.TrackInfo? getSelectedTrack(int);
+    method public int getShuffleMode();
+    method public androidx.media2.player.MediaTimestamp? getTimestamp();
+    method @Deprecated public java.util.List<androidx.media2.player.MediaPlayer.TrackInfo!> getTrackInfo();
+    method public androidx.media2.player.VideoSize getVideoSize();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> pause();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> play();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> prepare();
+    method public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.player.MediaPlayer.PlayerCallback);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> removePlaylistItem(@IntRange(from=0) int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> replacePlaylistItem(int, androidx.media2.common.MediaItem);
+    method public void reset();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> seekTo(long, int);
+    method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> selectTrack(androidx.media2.player.MediaPlayer.TrackInfo);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioAttributes(androidx.media.AudioAttributesCompat);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAudioSessionId(int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setMediaItem(androidx.media2.common.MediaItem);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackParams(androidx.media2.player.PlaybackParams);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlayerVolume(@FloatRange(from=0, to=1) float);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setPlaylist(java.util.List<androidx.media2.common.MediaItem!>, androidx.media2.common.MediaMetadata?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setRepeatMode(int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> setShuffleMode(int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToNextPlaylistItem();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPlaylistItem(@IntRange(from=0) int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> skipToPreviousPlaylistItem();
+    method public void unregisterPlayerCallback(androidx.media2.player.MediaPlayer.PlayerCallback);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.common.SessionPlayer.PlayerResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
+    field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
+    field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
+    field public static final int MEDIA_INFO_BUFFERING_UPDATE = 704; // 0x2c0
+    field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
+    field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
+    field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
+    field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
+    field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
+    field @Deprecated public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
+    field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
+    field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
+    field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
+    field public static final int PLAYER_ERROR_UNKNOWN = 1; // 0x1
+    field public static final int PLAYER_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
+    field public static final int SEEK_CLOSEST = 3; // 0x3
+    field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
+    field public static final int SEEK_NEXT_SYNC = 1; // 0x1
+    field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
+  }
+
+  public abstract static class MediaPlayer.PlayerCallback extends androidx.media2.common.SessionPlayer.PlayerCallback {
+    ctor public MediaPlayer.PlayerCallback();
+    method public void onError(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
+    method public void onInfo(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, int, int);
+    method public void onMediaTimeDiscontinuity(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.MediaTimestamp);
+    method public void onTimedMetaDataAvailable(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.TimedMetaData);
+    method @Deprecated public void onVideoSizeChanged(androidx.media2.player.MediaPlayer, androidx.media2.common.MediaItem, androidx.media2.player.VideoSize);
+  }
+
+  public static final class MediaPlayer.TrackInfo extends androidx.media2.common.SessionPlayer.TrackInfo {
+  }
+
+  public final class MediaTimestamp {
+    method public long getAnchorMediaTimeUs();
+    method public long getAnchorSystemNanoTime();
+    method public float getMediaClockRate();
+    field public static final androidx.media2.player.MediaTimestamp TIMESTAMP_UNKNOWN;
+  }
+
+  public final class PlaybackParams {
+    method public Integer? getAudioFallbackMode();
+    method public Float? getPitch();
+    method public Float? getSpeed();
+    field public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
+    field public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
+    field public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
+  }
+
+  public static final class PlaybackParams.Builder {
+    ctor public PlaybackParams.Builder();
+    ctor public PlaybackParams.Builder(androidx.media2.player.PlaybackParams);
+    method public androidx.media2.player.PlaybackParams build();
+    method public androidx.media2.player.PlaybackParams.Builder setAudioFallbackMode(int);
+    method public androidx.media2.player.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
+    method public androidx.media2.player.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
+  }
+
+  public class TimedMetaData {
+    method public byte[]! getMetaData();
+    method public long getTimestamp();
+  }
+
+  public final class VideoSize extends androidx.media2.common.VideoSize {
+    ctor public VideoSize(int, int);
+  }
+
+}
+
diff --git a/media2/media2-session/api/1.2.0-beta01.txt b/media2/media2-session/api/1.2.0-beta01.txt
new file mode 100644
index 0000000..95e39e3
--- /dev/null
+++ b/media2/media2-session/api/1.2.0-beta01.txt
@@ -0,0 +1,449 @@
+// Signature format: 4.0
+package androidx.media2.session {
+
+  public final class HeartRating implements androidx.media2.common.Rating {
+    ctor public HeartRating();
+    ctor public HeartRating(boolean);
+    method public boolean hasHeart();
+    method public boolean isRated();
+  }
+
+  public class LibraryResult implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public LibraryResult(int);
+    ctor public LibraryResult(int, androidx.media2.common.MediaItem?, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    ctor public LibraryResult(int, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public long getCompletionTime();
+    method public androidx.media2.session.MediaLibraryService.LibraryParams? getLibraryParams();
+    method public androidx.media2.common.MediaItem? getMediaItem();
+    method public java.util.List<androidx.media2.common.MediaItem!>? getMediaItems();
+    method public int getResultCode();
+    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
+    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
+    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
+    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
+    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
+    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
+    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
+    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
+    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
+    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
+    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
+    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
+    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
+    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
+    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
+    field public static final int RESULT_SUCCESS = 0; // 0x0
+  }
+
+  public class MediaBrowser extends androidx.media2.session.MediaController {
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getItem(String);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getLibraryRoot(androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> search(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> subscribe(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> unsubscribe(String);
+  }
+
+  public static class MediaBrowser.BrowserCallback extends androidx.media2.session.MediaController.ControllerCallback {
+    ctor public MediaBrowser.BrowserCallback();
+    method public void onChildrenChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public void onSearchResultChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+  }
+
+  public static final class MediaBrowser.Builder {
+    ctor public MediaBrowser.Builder(android.content.Context);
+    method public androidx.media2.session.MediaBrowser build();
+    method public androidx.media2.session.MediaBrowser.Builder setConnectionHints(android.os.Bundle);
+    method public androidx.media2.session.MediaBrowser.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaBrowser.BrowserCallback);
+    method public androidx.media2.session.MediaBrowser.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
+    method public androidx.media2.session.MediaBrowser.Builder setSessionToken(androidx.media2.session.SessionToken);
+  }
+
+  public class MediaConstants {
+    field public static final String MEDIA_URI_AUTHORITY = "media2-session";
+    field public static final String MEDIA_URI_PATH_PLAY_FROM_MEDIA_ID = "playFromMediaId";
+    field public static final String MEDIA_URI_PATH_PLAY_FROM_SEARCH = "playFromSearch";
+    field public static final String MEDIA_URI_PATH_PREPARE_FROM_MEDIA_ID = "prepareFromMediaId";
+    field public static final String MEDIA_URI_PATH_PREPARE_FROM_SEARCH = "prepareFromSearch";
+    field public static final String MEDIA_URI_PATH_SET_MEDIA_URI = "setMediaUri";
+    field public static final String MEDIA_URI_QUERY_ID = "id";
+    field public static final String MEDIA_URI_QUERY_QUERY = "query";
+    field public static final String MEDIA_URI_QUERY_URI = "uri";
+    field public static final String MEDIA_URI_SCHEME = "androidx";
+  }
+
+  public class MediaController implements java.io.Closeable {
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> addPlaylistItem(@IntRange(from=0) int, String);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> adjustVolume(int, int);
+    method public void close();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> fastForward();
+    method public androidx.media2.session.SessionCommandGroup? getAllowedCommands();
+    method public long getBufferedPosition();
+    method public int getBufferingState();
+    method public androidx.media2.session.SessionToken? getConnectedToken();
+    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
+    method public int getCurrentMediaItemIndex();
+    method public long getCurrentPosition();
+    method public long getDuration();
+    method public int getNextMediaItemIndex();
+    method public androidx.media2.session.MediaController.PlaybackInfo? getPlaybackInfo();
+    method public float getPlaybackSpeed();
+    method public int getPlayerState();
+    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
+    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
+    method public int getPreviousMediaItemIndex();
+    method public int getRepeatMode();
+    method public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(int);
+    method public android.app.PendingIntent? getSessionActivity();
+    method public int getShuffleMode();
+    method public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
+    method public androidx.media2.common.VideoSize getVideoSize();
+    method public boolean isConnected();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> pause();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> play();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepare();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> removePlaylistItem(@IntRange(from=0) int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> replacePlaylistItem(@IntRange(from=0) int, String);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> rewind();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> seekTo(long);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaItem(String);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaUri(android.net.Uri, android.os.Bundle?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaybackSpeed(float);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaylist(java.util.List<java.lang.String!>, androidx.media2.common.MediaMetadata?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRating(String, androidx.media2.common.Rating);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRepeatMode(int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setShuffleMode(int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setSurface(android.view.Surface?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setVolumeTo(int, int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipBackward();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipForward();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToNextPlaylistItem();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPlaylistItem(@IntRange(from=0) int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPreviousPlaylistItem();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
+  }
+
+  public static final class MediaController.Builder {
+    ctor public MediaController.Builder(android.content.Context);
+    method public androidx.media2.session.MediaController build();
+    method public androidx.media2.session.MediaController.Builder setConnectionHints(android.os.Bundle);
+    method public androidx.media2.session.MediaController.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaController.ControllerCallback);
+    method public androidx.media2.session.MediaController.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
+    method public androidx.media2.session.MediaController.Builder setSessionToken(androidx.media2.session.SessionToken);
+  }
+
+  public abstract static class MediaController.ControllerCallback {
+    ctor public MediaController.ControllerCallback();
+    method public void onAllowedCommandsChanged(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
+    method public void onBufferingStateChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, int);
+    method public void onConnected(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
+    method public void onCurrentMediaItemChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem?);
+    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaController, androidx.media2.session.SessionCommand, android.os.Bundle?);
+    method public void onDisconnected(androidx.media2.session.MediaController);
+    method public void onPlaybackCompleted(androidx.media2.session.MediaController);
+    method public void onPlaybackInfoChanged(androidx.media2.session.MediaController, androidx.media2.session.MediaController.PlaybackInfo);
+    method public void onPlaybackSpeedChanged(androidx.media2.session.MediaController, float);
+    method public void onPlayerStateChanged(androidx.media2.session.MediaController, int);
+    method public void onPlaylistChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
+    method public void onPlaylistMetadataChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaMetadata?);
+    method public void onRepeatModeChanged(androidx.media2.session.MediaController, int);
+    method public void onSeekCompleted(androidx.media2.session.MediaController, long);
+    method public int onSetCustomLayout(androidx.media2.session.MediaController, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
+    method public void onShuffleModeChanged(androidx.media2.session.MediaController, int);
+    method public void onSubtitleData(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
+    method public void onTrackDeselected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
+    method public void onTrackSelected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
+    method public void onTracksChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
+    method public void onVideoSizeChanged(androidx.media2.session.MediaController, androidx.media2.common.VideoSize);
+  }
+
+  public static final class MediaController.PlaybackInfo implements androidx.versionedparcelable.VersionedParcelable {
+    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
+    method public int getControlType();
+    method public int getCurrentVolume();
+    method public int getMaxVolume();
+    method public int getPlaybackType();
+    field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
+    field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
+  }
+
+  public abstract class MediaLibraryService extends androidx.media2.session.MediaSessionService {
+    ctor public MediaLibraryService();
+    method public abstract androidx.media2.session.MediaLibraryService.MediaLibrarySession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
+    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaLibraryService";
+  }
+
+  public static final class MediaLibraryService.LibraryParams implements androidx.versionedparcelable.VersionedParcelable {
+    method public android.os.Bundle? getExtras();
+    method public boolean isOffline();
+    method public boolean isRecent();
+    method public boolean isSuggested();
+  }
+
+  public static final class MediaLibraryService.LibraryParams.Builder {
+    ctor public MediaLibraryService.LibraryParams.Builder();
+    method public androidx.media2.session.MediaLibraryService.LibraryParams build();
+    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setExtras(android.os.Bundle?);
+    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setOffline(boolean);
+    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setRecent(boolean);
+    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setSuggested(boolean);
+  }
+
+  public static final class MediaLibraryService.MediaLibrarySession extends androidx.media2.session.MediaSession {
+    method public void notifyChildrenChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public void notifyChildrenChanged(String, int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public void notifySearchResultChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+  }
+
+  public static final class MediaLibraryService.MediaLibrarySession.Builder {
+    ctor public MediaLibraryService.MediaLibrarySession.Builder(androidx.media2.session.MediaLibraryService, androidx.media2.common.SessionPlayer, java.util.concurrent.Executor, androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback);
+    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession build();
+    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setExtras(android.os.Bundle);
+    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setId(String);
+    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setSessionActivity(android.app.PendingIntent?);
+  }
+
+  public static class MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback extends androidx.media2.session.MediaSession.SessionCallback {
+    ctor public MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback();
+    method public androidx.media2.session.LibraryResult onGetChildren(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public androidx.media2.session.LibraryResult onGetItem(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
+    method public androidx.media2.session.LibraryResult onGetLibraryRoot(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public androidx.media2.session.LibraryResult onGetSearchResult(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public int onSearch(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public int onSubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public int onUnsubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
+  }
+
+  public class MediaSession implements java.io.Closeable {
+    method public void broadcastCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
+    method public void close();
+    method public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
+    method public String getId();
+    method public androidx.media2.common.SessionPlayer getPlayer();
+    method public android.support.v4.media.session.MediaSessionCompat.Token getSessionCompatToken();
+    method public androidx.media2.session.SessionToken getToken();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
+    method public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setCustomLayout(androidx.media2.session.MediaSession.ControllerInfo, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
+    method public void updatePlayer(androidx.media2.common.SessionPlayer);
+  }
+
+  public static final class MediaSession.Builder {
+    ctor public MediaSession.Builder(android.content.Context, androidx.media2.common.SessionPlayer);
+    method public androidx.media2.session.MediaSession build();
+    method public androidx.media2.session.MediaSession.Builder setExtras(android.os.Bundle);
+    method public androidx.media2.session.MediaSession.Builder setId(String);
+    method public androidx.media2.session.MediaSession.Builder setSessionActivity(android.app.PendingIntent?);
+    method public androidx.media2.session.MediaSession.Builder setSessionCallback(java.util.concurrent.Executor, androidx.media2.session.MediaSession.SessionCallback);
+  }
+
+  public static final class MediaSession.CommandButton implements androidx.versionedparcelable.VersionedParcelable {
+    method public androidx.media2.session.SessionCommand? getCommand();
+    method public CharSequence? getDisplayName();
+    method public android.os.Bundle? getExtras();
+    method public int getIconResId();
+    method public boolean isEnabled();
+  }
+
+  public static final class MediaSession.CommandButton.Builder {
+    ctor public MediaSession.CommandButton.Builder();
+    method public androidx.media2.session.MediaSession.CommandButton build();
+    method public androidx.media2.session.MediaSession.CommandButton.Builder setCommand(androidx.media2.session.SessionCommand?);
+    method public androidx.media2.session.MediaSession.CommandButton.Builder setDisplayName(CharSequence?);
+    method public androidx.media2.session.MediaSession.CommandButton.Builder setEnabled(boolean);
+    method public androidx.media2.session.MediaSession.CommandButton.Builder setExtras(android.os.Bundle?);
+    method public androidx.media2.session.MediaSession.CommandButton.Builder setIconResId(int);
+  }
+
+  public static final class MediaSession.ControllerInfo {
+    method public android.os.Bundle getConnectionHints();
+    method public String getPackageName();
+    method public int getUid();
+  }
+
+  public abstract static class MediaSession.SessionCallback {
+    ctor public MediaSession.SessionCallback();
+    method public int onCommandRequest(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand);
+    method public androidx.media2.session.SessionCommandGroup? onConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+    method public androidx.media2.common.MediaItem? onCreateMediaItem(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String);
+    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
+    method public void onDisconnected(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+    method public int onFastForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+    method public void onPostConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+    method public int onRewind(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+    method public int onSetMediaUri(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, android.net.Uri, android.os.Bundle?);
+    method public int onSetRating(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.common.Rating);
+    method public int onSkipBackward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+    method public int onSkipForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+  }
+
+  public final class MediaSessionManager {
+    method public static androidx.media2.session.MediaSessionManager getInstance(android.content.Context);
+    method public java.util.Set<androidx.media2.session.SessionToken!> getSessionServiceTokens();
+  }
+
+  public abstract class MediaSessionService extends android.app.Service {
+    ctor public MediaSessionService();
+    method public final void addSession(androidx.media2.session.MediaSession);
+    method public final java.util.List<androidx.media2.session.MediaSession!> getSessions();
+    method @CallSuper public android.os.IBinder? onBind(android.content.Intent);
+    method public abstract androidx.media2.session.MediaSession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
+    method public androidx.media2.session.MediaSessionService.MediaNotification? onUpdateNotification(androidx.media2.session.MediaSession);
+    method public final void removeSession(androidx.media2.session.MediaSession);
+    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaSessionService";
+  }
+
+  public static class MediaSessionService.MediaNotification {
+    ctor public MediaSessionService.MediaNotification(int, android.app.Notification);
+    method public android.app.Notification getNotification();
+    method public int getNotificationId();
+  }
+
+  public final class PercentageRating implements androidx.media2.common.Rating {
+    ctor public PercentageRating();
+    ctor public PercentageRating(float);
+    method public float getPercentRating();
+    method public boolean isRated();
+  }
+
+  public abstract class RemoteSessionPlayer extends androidx.media2.common.SessionPlayer {
+    ctor public RemoteSessionPlayer();
+    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> adjustVolume(int);
+    method public abstract int getMaxVolume();
+    method public abstract int getVolume();
+    method public abstract int getVolumeControlType();
+    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> setVolume(int);
+    field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
+    field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
+    field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
+  }
+
+  public static class RemoteSessionPlayer.Callback extends androidx.media2.common.SessionPlayer.PlayerCallback {
+    ctor public RemoteSessionPlayer.Callback();
+    method public void onVolumeChanged(androidx.media2.session.RemoteSessionPlayer, int);
+  }
+
+  public final class SessionCommand implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public SessionCommand(int);
+    ctor public SessionCommand(String, android.os.Bundle?);
+    method public int getCommandCode();
+    method public String? getCustomAction();
+    method public android.os.Bundle? getCustomExtras();
+    field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
+    field public static final int COMMAND_CODE_LIBRARY_GET_CHILDREN = 50003; // 0xc353
+    field public static final int COMMAND_CODE_LIBRARY_GET_ITEM = 50004; // 0xc354
+    field public static final int COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT = 50000; // 0xc350
+    field public static final int COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT = 50006; // 0xc356
+    field public static final int COMMAND_CODE_LIBRARY_SEARCH = 50005; // 0xc355
+    field public static final int COMMAND_CODE_LIBRARY_SUBSCRIBE = 50001; // 0xc351
+    field public static final int COMMAND_CODE_LIBRARY_UNSUBSCRIBE = 50002; // 0xc352
+    field public static final int COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM = 10013; // 0x271d
+    field public static final int COMMAND_CODE_PLAYER_DESELECT_TRACK = 11002; // 0x2afa
+    field public static final int COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM = 10016; // 0x2720
+    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST = 10005; // 0x2715
+    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012; // 0x271c
+    field public static final int COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM = 10019; // 0x2723
+    field public static final int COMMAND_CODE_PLAYER_PAUSE = 10001; // 0x2711
+    field public static final int COMMAND_CODE_PLAYER_PLAY = 10000; // 0x2710
+    field public static final int COMMAND_CODE_PLAYER_PREPARE = 10002; // 0x2712
+    field public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014; // 0x271e
+    field public static final int COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM = 10015; // 0x271f
+    field public static final int COMMAND_CODE_PLAYER_SEEK_TO = 10003; // 0x2713
+    field public static final int COMMAND_CODE_PLAYER_SELECT_TRACK = 11001; // 0x2af9
+    field public static final int COMMAND_CODE_PLAYER_SET_MEDIA_ITEM = 10018; // 0x2722
+    field public static final int COMMAND_CODE_PLAYER_SET_PLAYLIST = 10006; // 0x2716
+    field public static final int COMMAND_CODE_PLAYER_SET_REPEAT_MODE = 10011; // 0x271b
+    field public static final int COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE = 10010; // 0x271a
+    field public static final int COMMAND_CODE_PLAYER_SET_SPEED = 10004; // 0x2714
+    field public static final int COMMAND_CODE_PLAYER_SET_SURFACE = 11000; // 0x2af8
+    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM = 10009; // 0x2719
+    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM = 10007; // 0x2717
+    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM = 10008; // 0x2718
+    field public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017; // 0x2721
+    field public static final int COMMAND_CODE_SESSION_FAST_FORWARD = 40000; // 0x9c40
+    field public static final int COMMAND_CODE_SESSION_REWIND = 40001; // 0x9c41
+    field public static final int COMMAND_CODE_SESSION_SET_MEDIA_URI = 40011; // 0x9c4b
+    field public static final int COMMAND_CODE_SESSION_SET_RATING = 40010; // 0x9c4a
+    field public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003; // 0x9c43
+    field public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002; // 0x9c42
+    field public static final int COMMAND_CODE_VOLUME_ADJUST_VOLUME = 30001; // 0x7531
+    field public static final int COMMAND_CODE_VOLUME_SET_VOLUME = 30000; // 0x7530
+    field public static final int COMMAND_VERSION_1 = 1; // 0x1
+    field public static final int COMMAND_VERSION_2 = 2; // 0x2
+  }
+
+  public final class SessionCommandGroup implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public SessionCommandGroup();
+    ctor public SessionCommandGroup(java.util.Collection<androidx.media2.session.SessionCommand!>?);
+    method public java.util.Set<androidx.media2.session.SessionCommand!> getCommands();
+    method public boolean hasCommand(androidx.media2.session.SessionCommand);
+    method public boolean hasCommand(int);
+  }
+
+  public static final class SessionCommandGroup.Builder {
+    ctor public SessionCommandGroup.Builder();
+    ctor public SessionCommandGroup.Builder(androidx.media2.session.SessionCommandGroup);
+    method public androidx.media2.session.SessionCommandGroup.Builder addAllPredefinedCommands(int);
+    method public androidx.media2.session.SessionCommandGroup.Builder addCommand(androidx.media2.session.SessionCommand);
+    method public androidx.media2.session.SessionCommandGroup build();
+    method public androidx.media2.session.SessionCommandGroup.Builder removeCommand(androidx.media2.session.SessionCommand);
+  }
+
+  public class SessionResult implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public SessionResult(int, android.os.Bundle?);
+    method public long getCompletionTime();
+    method public android.os.Bundle? getCustomCommandResult();
+    method public androidx.media2.common.MediaItem? getMediaItem();
+    method public int getResultCode();
+    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
+    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
+    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
+    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
+    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
+    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
+    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
+    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
+    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
+    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
+    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
+    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
+    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
+    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
+    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
+    field public static final int RESULT_SUCCESS = 0; // 0x0
+  }
+
+  public final class SessionToken implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public SessionToken(android.content.Context, android.content.ComponentName);
+    method public android.os.Bundle getExtras();
+    method public String getPackageName();
+    method public String? getServiceName();
+    method public int getType();
+    method public int getUid();
+    field public static final int TYPE_LIBRARY_SERVICE = 2; // 0x2
+    field public static final int TYPE_SESSION = 0; // 0x0
+    field public static final int TYPE_SESSION_SERVICE = 1; // 0x1
+  }
+
+  public final class StarRating implements androidx.media2.common.Rating {
+    ctor public StarRating(@IntRange(from=1) int);
+    ctor public StarRating(@IntRange(from=1) int, float);
+    method public int getMaxStars();
+    method public float getStarRating();
+    method public boolean isRated();
+  }
+
+  public final class ThumbRating implements androidx.media2.common.Rating {
+    ctor public ThumbRating();
+    ctor public ThumbRating(boolean);
+    method public boolean isRated();
+    method public boolean isThumbUp();
+  }
+
+}
+
diff --git a/media2/media2-session/api/public_plus_experimental_1.2.0-beta01.txt b/media2/media2-session/api/public_plus_experimental_1.2.0-beta01.txt
new file mode 100644
index 0000000..95e39e3
--- /dev/null
+++ b/media2/media2-session/api/public_plus_experimental_1.2.0-beta01.txt
@@ -0,0 +1,449 @@
+// Signature format: 4.0
+package androidx.media2.session {
+
+  public final class HeartRating implements androidx.media2.common.Rating {
+    ctor public HeartRating();
+    ctor public HeartRating(boolean);
+    method public boolean hasHeart();
+    method public boolean isRated();
+  }
+
+  public class LibraryResult implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public LibraryResult(int);
+    ctor public LibraryResult(int, androidx.media2.common.MediaItem?, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    ctor public LibraryResult(int, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public long getCompletionTime();
+    method public androidx.media2.session.MediaLibraryService.LibraryParams? getLibraryParams();
+    method public androidx.media2.common.MediaItem? getMediaItem();
+    method public java.util.List<androidx.media2.common.MediaItem!>? getMediaItems();
+    method public int getResultCode();
+    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
+    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
+    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
+    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
+    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
+    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
+    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
+    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
+    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
+    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
+    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
+    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
+    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
+    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
+    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
+    field public static final int RESULT_SUCCESS = 0; // 0x0
+  }
+
+  public class MediaBrowser extends androidx.media2.session.MediaController {
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getItem(String);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getLibraryRoot(androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> search(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> subscribe(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> unsubscribe(String);
+  }
+
+  public static class MediaBrowser.BrowserCallback extends androidx.media2.session.MediaController.ControllerCallback {
+    ctor public MediaBrowser.BrowserCallback();
+    method public void onChildrenChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public void onSearchResultChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+  }
+
+  public static final class MediaBrowser.Builder {
+    ctor public MediaBrowser.Builder(android.content.Context);
+    method public androidx.media2.session.MediaBrowser build();
+    method public androidx.media2.session.MediaBrowser.Builder setConnectionHints(android.os.Bundle);
+    method public androidx.media2.session.MediaBrowser.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaBrowser.BrowserCallback);
+    method public androidx.media2.session.MediaBrowser.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
+    method public androidx.media2.session.MediaBrowser.Builder setSessionToken(androidx.media2.session.SessionToken);
+  }
+
+  public class MediaConstants {
+    field public static final String MEDIA_URI_AUTHORITY = "media2-session";
+    field public static final String MEDIA_URI_PATH_PLAY_FROM_MEDIA_ID = "playFromMediaId";
+    field public static final String MEDIA_URI_PATH_PLAY_FROM_SEARCH = "playFromSearch";
+    field public static final String MEDIA_URI_PATH_PREPARE_FROM_MEDIA_ID = "prepareFromMediaId";
+    field public static final String MEDIA_URI_PATH_PREPARE_FROM_SEARCH = "prepareFromSearch";
+    field public static final String MEDIA_URI_PATH_SET_MEDIA_URI = "setMediaUri";
+    field public static final String MEDIA_URI_QUERY_ID = "id";
+    field public static final String MEDIA_URI_QUERY_QUERY = "query";
+    field public static final String MEDIA_URI_QUERY_URI = "uri";
+    field public static final String MEDIA_URI_SCHEME = "androidx";
+  }
+
+  public class MediaController implements java.io.Closeable {
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> addPlaylistItem(@IntRange(from=0) int, String);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> adjustVolume(int, int);
+    method public void close();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> fastForward();
+    method public androidx.media2.session.SessionCommandGroup? getAllowedCommands();
+    method public long getBufferedPosition();
+    method public int getBufferingState();
+    method public androidx.media2.session.SessionToken? getConnectedToken();
+    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
+    method public int getCurrentMediaItemIndex();
+    method public long getCurrentPosition();
+    method public long getDuration();
+    method public int getNextMediaItemIndex();
+    method public androidx.media2.session.MediaController.PlaybackInfo? getPlaybackInfo();
+    method public float getPlaybackSpeed();
+    method public int getPlayerState();
+    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
+    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
+    method public int getPreviousMediaItemIndex();
+    method public int getRepeatMode();
+    method public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(int);
+    method public android.app.PendingIntent? getSessionActivity();
+    method public int getShuffleMode();
+    method public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
+    method public androidx.media2.common.VideoSize getVideoSize();
+    method public boolean isConnected();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> pause();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> play();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepare();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> removePlaylistItem(@IntRange(from=0) int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> replacePlaylistItem(@IntRange(from=0) int, String);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> rewind();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> seekTo(long);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaItem(String);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaUri(android.net.Uri, android.os.Bundle?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaybackSpeed(float);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaylist(java.util.List<java.lang.String!>, androidx.media2.common.MediaMetadata?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRating(String, androidx.media2.common.Rating);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRepeatMode(int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setShuffleMode(int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setSurface(android.view.Surface?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setVolumeTo(int, int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipBackward();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipForward();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToNextPlaylistItem();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPlaylistItem(@IntRange(from=0) int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPreviousPlaylistItem();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
+  }
+
+  public static final class MediaController.Builder {
+    ctor public MediaController.Builder(android.content.Context);
+    method public androidx.media2.session.MediaController build();
+    method public androidx.media2.session.MediaController.Builder setConnectionHints(android.os.Bundle);
+    method public androidx.media2.session.MediaController.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaController.ControllerCallback);
+    method public androidx.media2.session.MediaController.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
+    method public androidx.media2.session.MediaController.Builder setSessionToken(androidx.media2.session.SessionToken);
+  }
+
+  public abstract static class MediaController.ControllerCallback {
+    ctor public MediaController.ControllerCallback();
+    method public void onAllowedCommandsChanged(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
+    method public void onBufferingStateChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, int);
+    method public void onConnected(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
+    method public void onCurrentMediaItemChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem?);
+    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaController, androidx.media2.session.SessionCommand, android.os.Bundle?);
+    method public void onDisconnected(androidx.media2.session.MediaController);
+    method public void onPlaybackCompleted(androidx.media2.session.MediaController);
+    method public void onPlaybackInfoChanged(androidx.media2.session.MediaController, androidx.media2.session.MediaController.PlaybackInfo);
+    method public void onPlaybackSpeedChanged(androidx.media2.session.MediaController, float);
+    method public void onPlayerStateChanged(androidx.media2.session.MediaController, int);
+    method public void onPlaylistChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
+    method public void onPlaylistMetadataChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaMetadata?);
+    method public void onRepeatModeChanged(androidx.media2.session.MediaController, int);
+    method public void onSeekCompleted(androidx.media2.session.MediaController, long);
+    method public int onSetCustomLayout(androidx.media2.session.MediaController, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
+    method public void onShuffleModeChanged(androidx.media2.session.MediaController, int);
+    method public void onSubtitleData(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
+    method public void onTrackDeselected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
+    method public void onTrackSelected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
+    method public void onTracksChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
+    method public void onVideoSizeChanged(androidx.media2.session.MediaController, androidx.media2.common.VideoSize);
+  }
+
+  public static final class MediaController.PlaybackInfo implements androidx.versionedparcelable.VersionedParcelable {
+    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
+    method public int getControlType();
+    method public int getCurrentVolume();
+    method public int getMaxVolume();
+    method public int getPlaybackType();
+    field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
+    field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
+  }
+
+  public abstract class MediaLibraryService extends androidx.media2.session.MediaSessionService {
+    ctor public MediaLibraryService();
+    method public abstract androidx.media2.session.MediaLibraryService.MediaLibrarySession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
+    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaLibraryService";
+  }
+
+  public static final class MediaLibraryService.LibraryParams implements androidx.versionedparcelable.VersionedParcelable {
+    method public android.os.Bundle? getExtras();
+    method public boolean isOffline();
+    method public boolean isRecent();
+    method public boolean isSuggested();
+  }
+
+  public static final class MediaLibraryService.LibraryParams.Builder {
+    ctor public MediaLibraryService.LibraryParams.Builder();
+    method public androidx.media2.session.MediaLibraryService.LibraryParams build();
+    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setExtras(android.os.Bundle?);
+    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setOffline(boolean);
+    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setRecent(boolean);
+    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setSuggested(boolean);
+  }
+
+  public static final class MediaLibraryService.MediaLibrarySession extends androidx.media2.session.MediaSession {
+    method public void notifyChildrenChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public void notifyChildrenChanged(String, int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public void notifySearchResultChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+  }
+
+  public static final class MediaLibraryService.MediaLibrarySession.Builder {
+    ctor public MediaLibraryService.MediaLibrarySession.Builder(androidx.media2.session.MediaLibraryService, androidx.media2.common.SessionPlayer, java.util.concurrent.Executor, androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback);
+    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession build();
+    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setExtras(android.os.Bundle);
+    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setId(String);
+    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setSessionActivity(android.app.PendingIntent?);
+  }
+
+  public static class MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback extends androidx.media2.session.MediaSession.SessionCallback {
+    ctor public MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback();
+    method public androidx.media2.session.LibraryResult onGetChildren(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public androidx.media2.session.LibraryResult onGetItem(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
+    method public androidx.media2.session.LibraryResult onGetLibraryRoot(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public androidx.media2.session.LibraryResult onGetSearchResult(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public int onSearch(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public int onSubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public int onUnsubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
+  }
+
+  public class MediaSession implements java.io.Closeable {
+    method public void broadcastCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
+    method public void close();
+    method public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
+    method public String getId();
+    method public androidx.media2.common.SessionPlayer getPlayer();
+    method public android.support.v4.media.session.MediaSessionCompat.Token getSessionCompatToken();
+    method public androidx.media2.session.SessionToken getToken();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
+    method public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setCustomLayout(androidx.media2.session.MediaSession.ControllerInfo, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
+    method public void updatePlayer(androidx.media2.common.SessionPlayer);
+  }
+
+  public static final class MediaSession.Builder {
+    ctor public MediaSession.Builder(android.content.Context, androidx.media2.common.SessionPlayer);
+    method public androidx.media2.session.MediaSession build();
+    method public androidx.media2.session.MediaSession.Builder setExtras(android.os.Bundle);
+    method public androidx.media2.session.MediaSession.Builder setId(String);
+    method public androidx.media2.session.MediaSession.Builder setSessionActivity(android.app.PendingIntent?);
+    method public androidx.media2.session.MediaSession.Builder setSessionCallback(java.util.concurrent.Executor, androidx.media2.session.MediaSession.SessionCallback);
+  }
+
+  public static final class MediaSession.CommandButton implements androidx.versionedparcelable.VersionedParcelable {
+    method public androidx.media2.session.SessionCommand? getCommand();
+    method public CharSequence? getDisplayName();
+    method public android.os.Bundle? getExtras();
+    method public int getIconResId();
+    method public boolean isEnabled();
+  }
+
+  public static final class MediaSession.CommandButton.Builder {
+    ctor public MediaSession.CommandButton.Builder();
+    method public androidx.media2.session.MediaSession.CommandButton build();
+    method public androidx.media2.session.MediaSession.CommandButton.Builder setCommand(androidx.media2.session.SessionCommand?);
+    method public androidx.media2.session.MediaSession.CommandButton.Builder setDisplayName(CharSequence?);
+    method public androidx.media2.session.MediaSession.CommandButton.Builder setEnabled(boolean);
+    method public androidx.media2.session.MediaSession.CommandButton.Builder setExtras(android.os.Bundle?);
+    method public androidx.media2.session.MediaSession.CommandButton.Builder setIconResId(int);
+  }
+
+  public static final class MediaSession.ControllerInfo {
+    method public android.os.Bundle getConnectionHints();
+    method public String getPackageName();
+    method public int getUid();
+  }
+
+  public abstract static class MediaSession.SessionCallback {
+    ctor public MediaSession.SessionCallback();
+    method public int onCommandRequest(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand);
+    method public androidx.media2.session.SessionCommandGroup? onConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+    method public androidx.media2.common.MediaItem? onCreateMediaItem(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String);
+    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
+    method public void onDisconnected(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+    method public int onFastForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+    method public void onPostConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+    method public int onRewind(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+    method public int onSetMediaUri(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, android.net.Uri, android.os.Bundle?);
+    method public int onSetRating(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.common.Rating);
+    method public int onSkipBackward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+    method public int onSkipForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+  }
+
+  public final class MediaSessionManager {
+    method public static androidx.media2.session.MediaSessionManager getInstance(android.content.Context);
+    method public java.util.Set<androidx.media2.session.SessionToken!> getSessionServiceTokens();
+  }
+
+  public abstract class MediaSessionService extends android.app.Service {
+    ctor public MediaSessionService();
+    method public final void addSession(androidx.media2.session.MediaSession);
+    method public final java.util.List<androidx.media2.session.MediaSession!> getSessions();
+    method @CallSuper public android.os.IBinder? onBind(android.content.Intent);
+    method public abstract androidx.media2.session.MediaSession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
+    method public androidx.media2.session.MediaSessionService.MediaNotification? onUpdateNotification(androidx.media2.session.MediaSession);
+    method public final void removeSession(androidx.media2.session.MediaSession);
+    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaSessionService";
+  }
+
+  public static class MediaSessionService.MediaNotification {
+    ctor public MediaSessionService.MediaNotification(int, android.app.Notification);
+    method public android.app.Notification getNotification();
+    method public int getNotificationId();
+  }
+
+  public final class PercentageRating implements androidx.media2.common.Rating {
+    ctor public PercentageRating();
+    ctor public PercentageRating(float);
+    method public float getPercentRating();
+    method public boolean isRated();
+  }
+
+  public abstract class RemoteSessionPlayer extends androidx.media2.common.SessionPlayer {
+    ctor public RemoteSessionPlayer();
+    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> adjustVolume(int);
+    method public abstract int getMaxVolume();
+    method public abstract int getVolume();
+    method public abstract int getVolumeControlType();
+    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> setVolume(int);
+    field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
+    field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
+    field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
+  }
+
+  public static class RemoteSessionPlayer.Callback extends androidx.media2.common.SessionPlayer.PlayerCallback {
+    ctor public RemoteSessionPlayer.Callback();
+    method public void onVolumeChanged(androidx.media2.session.RemoteSessionPlayer, int);
+  }
+
+  public final class SessionCommand implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public SessionCommand(int);
+    ctor public SessionCommand(String, android.os.Bundle?);
+    method public int getCommandCode();
+    method public String? getCustomAction();
+    method public android.os.Bundle? getCustomExtras();
+    field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
+    field public static final int COMMAND_CODE_LIBRARY_GET_CHILDREN = 50003; // 0xc353
+    field public static final int COMMAND_CODE_LIBRARY_GET_ITEM = 50004; // 0xc354
+    field public static final int COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT = 50000; // 0xc350
+    field public static final int COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT = 50006; // 0xc356
+    field public static final int COMMAND_CODE_LIBRARY_SEARCH = 50005; // 0xc355
+    field public static final int COMMAND_CODE_LIBRARY_SUBSCRIBE = 50001; // 0xc351
+    field public static final int COMMAND_CODE_LIBRARY_UNSUBSCRIBE = 50002; // 0xc352
+    field public static final int COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM = 10013; // 0x271d
+    field public static final int COMMAND_CODE_PLAYER_DESELECT_TRACK = 11002; // 0x2afa
+    field public static final int COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM = 10016; // 0x2720
+    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST = 10005; // 0x2715
+    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012; // 0x271c
+    field public static final int COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM = 10019; // 0x2723
+    field public static final int COMMAND_CODE_PLAYER_PAUSE = 10001; // 0x2711
+    field public static final int COMMAND_CODE_PLAYER_PLAY = 10000; // 0x2710
+    field public static final int COMMAND_CODE_PLAYER_PREPARE = 10002; // 0x2712
+    field public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014; // 0x271e
+    field public static final int COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM = 10015; // 0x271f
+    field public static final int COMMAND_CODE_PLAYER_SEEK_TO = 10003; // 0x2713
+    field public static final int COMMAND_CODE_PLAYER_SELECT_TRACK = 11001; // 0x2af9
+    field public static final int COMMAND_CODE_PLAYER_SET_MEDIA_ITEM = 10018; // 0x2722
+    field public static final int COMMAND_CODE_PLAYER_SET_PLAYLIST = 10006; // 0x2716
+    field public static final int COMMAND_CODE_PLAYER_SET_REPEAT_MODE = 10011; // 0x271b
+    field public static final int COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE = 10010; // 0x271a
+    field public static final int COMMAND_CODE_PLAYER_SET_SPEED = 10004; // 0x2714
+    field public static final int COMMAND_CODE_PLAYER_SET_SURFACE = 11000; // 0x2af8
+    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM = 10009; // 0x2719
+    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM = 10007; // 0x2717
+    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM = 10008; // 0x2718
+    field public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017; // 0x2721
+    field public static final int COMMAND_CODE_SESSION_FAST_FORWARD = 40000; // 0x9c40
+    field public static final int COMMAND_CODE_SESSION_REWIND = 40001; // 0x9c41
+    field public static final int COMMAND_CODE_SESSION_SET_MEDIA_URI = 40011; // 0x9c4b
+    field public static final int COMMAND_CODE_SESSION_SET_RATING = 40010; // 0x9c4a
+    field public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003; // 0x9c43
+    field public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002; // 0x9c42
+    field public static final int COMMAND_CODE_VOLUME_ADJUST_VOLUME = 30001; // 0x7531
+    field public static final int COMMAND_CODE_VOLUME_SET_VOLUME = 30000; // 0x7530
+    field public static final int COMMAND_VERSION_1 = 1; // 0x1
+    field public static final int COMMAND_VERSION_2 = 2; // 0x2
+  }
+
+  public final class SessionCommandGroup implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public SessionCommandGroup();
+    ctor public SessionCommandGroup(java.util.Collection<androidx.media2.session.SessionCommand!>?);
+    method public java.util.Set<androidx.media2.session.SessionCommand!> getCommands();
+    method public boolean hasCommand(androidx.media2.session.SessionCommand);
+    method public boolean hasCommand(int);
+  }
+
+  public static final class SessionCommandGroup.Builder {
+    ctor public SessionCommandGroup.Builder();
+    ctor public SessionCommandGroup.Builder(androidx.media2.session.SessionCommandGroup);
+    method public androidx.media2.session.SessionCommandGroup.Builder addAllPredefinedCommands(int);
+    method public androidx.media2.session.SessionCommandGroup.Builder addCommand(androidx.media2.session.SessionCommand);
+    method public androidx.media2.session.SessionCommandGroup build();
+    method public androidx.media2.session.SessionCommandGroup.Builder removeCommand(androidx.media2.session.SessionCommand);
+  }
+
+  public class SessionResult implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public SessionResult(int, android.os.Bundle?);
+    method public long getCompletionTime();
+    method public android.os.Bundle? getCustomCommandResult();
+    method public androidx.media2.common.MediaItem? getMediaItem();
+    method public int getResultCode();
+    field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd
+    field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe
+    field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb
+    field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa
+    field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc
+    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
+    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
+    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
+    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
+    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
+    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
+    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
+    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
+    field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff
+    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
+    field public static final int RESULT_SUCCESS = 0; // 0x0
+  }
+
+  public final class SessionToken implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public SessionToken(android.content.Context, android.content.ComponentName);
+    method public android.os.Bundle getExtras();
+    method public String getPackageName();
+    method public String? getServiceName();
+    method public int getType();
+    method public int getUid();
+    field public static final int TYPE_LIBRARY_SERVICE = 2; // 0x2
+    field public static final int TYPE_SESSION = 0; // 0x0
+    field public static final int TYPE_SESSION_SERVICE = 1; // 0x1
+  }
+
+  public final class StarRating implements androidx.media2.common.Rating {
+    ctor public StarRating(@IntRange(from=1) int);
+    ctor public StarRating(@IntRange(from=1) int, float);
+    method public int getMaxStars();
+    method public float getStarRating();
+    method public boolean isRated();
+  }
+
+  public final class ThumbRating implements androidx.media2.common.Rating {
+    ctor public ThumbRating();
+    ctor public ThumbRating(boolean);
+    method public boolean isRated();
+    method public boolean isThumbUp();
+  }
+
+}
+
diff --git a/media2/media2-session/api/res-1.2.0-beta01.txt b/media2/media2-session/api/res-1.2.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/media2/media2-session/api/res-1.2.0-beta01.txt
diff --git a/media2/media2-session/api/restricted_1.2.0-beta01.txt b/media2/media2-session/api/restricted_1.2.0-beta01.txt
new file mode 100644
index 0000000..e75539b
--- /dev/null
+++ b/media2/media2-session/api/restricted_1.2.0-beta01.txt
@@ -0,0 +1,434 @@
+// Signature format: 4.0
+package androidx.media2.session {
+
+  @androidx.versionedparcelable.VersionedParcelize public final class HeartRating implements androidx.media2.common.Rating {
+    ctor public HeartRating();
+    ctor public HeartRating(boolean);
+    method public boolean hasHeart();
+    method public boolean isRated();
+  }
+
+  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class LibraryResult extends androidx.versionedparcelable.CustomVersionedParcelable implements androidx.media2.common.BaseResult {
+    ctor public LibraryResult(int);
+    ctor public LibraryResult(int, androidx.media2.common.MediaItem?, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    ctor public LibraryResult(int, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public long getCompletionTime();
+    method public androidx.media2.session.MediaLibraryService.LibraryParams? getLibraryParams();
+    method public androidx.media2.common.MediaItem? getMediaItem();
+    method public java.util.List<androidx.media2.common.MediaItem!>? getMediaItems();
+    method public int getResultCode();
+    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
+    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
+    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
+    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
+    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
+    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
+    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
+    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
+  }
+
+  public class MediaBrowser extends androidx.media2.session.MediaController {
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getItem(String);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getLibraryRoot(androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> search(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> subscribe(String, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.LibraryResult!> unsubscribe(String);
+  }
+
+  public static class MediaBrowser.BrowserCallback extends androidx.media2.session.MediaController.ControllerCallback {
+    ctor public MediaBrowser.BrowserCallback();
+    method public void onChildrenChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public void onSearchResultChanged(androidx.media2.session.MediaBrowser, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+  }
+
+  public static final class MediaBrowser.Builder {
+    ctor public MediaBrowser.Builder(android.content.Context);
+    method public androidx.media2.session.MediaBrowser build();
+    method public androidx.media2.session.MediaBrowser.Builder setConnectionHints(android.os.Bundle);
+    method public androidx.media2.session.MediaBrowser.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaBrowser.BrowserCallback);
+    method public androidx.media2.session.MediaBrowser.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
+    method public androidx.media2.session.MediaBrowser.Builder setSessionToken(androidx.media2.session.SessionToken);
+  }
+
+  public class MediaConstants {
+    field public static final String MEDIA_URI_AUTHORITY = "media2-session";
+    field public static final String MEDIA_URI_PATH_PLAY_FROM_MEDIA_ID = "playFromMediaId";
+    field public static final String MEDIA_URI_PATH_PLAY_FROM_SEARCH = "playFromSearch";
+    field public static final String MEDIA_URI_PATH_PREPARE_FROM_MEDIA_ID = "prepareFromMediaId";
+    field public static final String MEDIA_URI_PATH_PREPARE_FROM_SEARCH = "prepareFromSearch";
+    field public static final String MEDIA_URI_PATH_SET_MEDIA_URI = "setMediaUri";
+    field public static final String MEDIA_URI_QUERY_ID = "id";
+    field public static final String MEDIA_URI_QUERY_QUERY = "query";
+    field public static final String MEDIA_URI_QUERY_URI = "uri";
+    field public static final String MEDIA_URI_SCHEME = "androidx";
+  }
+
+  public class MediaController implements java.io.Closeable {
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> addPlaylistItem(@IntRange(from=0) int, String);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> adjustVolume(int, int);
+    method public void close();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> deselectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> fastForward();
+    method public androidx.media2.session.SessionCommandGroup? getAllowedCommands();
+    method public long getBufferedPosition();
+    method @androidx.media2.common.SessionPlayer.BuffState public int getBufferingState();
+    method public androidx.media2.session.SessionToken? getConnectedToken();
+    method public androidx.media2.common.MediaItem? getCurrentMediaItem();
+    method public int getCurrentMediaItemIndex();
+    method public long getCurrentPosition();
+    method public long getDuration();
+    method public int getNextMediaItemIndex();
+    method public androidx.media2.session.MediaController.PlaybackInfo? getPlaybackInfo();
+    method public float getPlaybackSpeed();
+    method public int getPlayerState();
+    method public java.util.List<androidx.media2.common.MediaItem!>? getPlaylist();
+    method public androidx.media2.common.MediaMetadata? getPlaylistMetadata();
+    method public int getPreviousMediaItemIndex();
+    method @androidx.media2.common.SessionPlayer.RepeatMode public int getRepeatMode();
+    method public androidx.media2.common.SessionPlayer.TrackInfo? getSelectedTrack(@androidx.media2.common.SessionPlayer.TrackInfo.MediaTrackType int);
+    method public android.app.PendingIntent? getSessionActivity();
+    method @androidx.media2.common.SessionPlayer.ShuffleMode public int getShuffleMode();
+    method public java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!> getTracks();
+    method public androidx.media2.common.VideoSize getVideoSize();
+    method public boolean isConnected();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> movePlaylistItem(@IntRange(from=0) int, @IntRange(from=0) int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> pause();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> play();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> prepare();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> removePlaylistItem(@IntRange(from=0) int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> replacePlaylistItem(@IntRange(from=0) int, String);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> rewind();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> seekTo(long);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> selectTrack(androidx.media2.common.SessionPlayer.TrackInfo);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaItem(String);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setMediaUri(android.net.Uri, android.os.Bundle?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaybackSpeed(float);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setPlaylist(java.util.List<java.lang.String!>, androidx.media2.common.MediaMetadata?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRating(String, androidx.media2.common.Rating);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setRepeatMode(@androidx.media2.common.SessionPlayer.RepeatMode int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setShuffleMode(@androidx.media2.common.SessionPlayer.ShuffleMode int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setSurface(android.view.Surface?);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setVolumeTo(int, int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipBackward();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipForward();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToNextPlaylistItem();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPlaylistItem(@IntRange(from=0) int);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> skipToPreviousPlaylistItem();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> updatePlaylistMetadata(androidx.media2.common.MediaMetadata?);
+  }
+
+  public static final class MediaController.Builder {
+    ctor public MediaController.Builder(android.content.Context);
+    method public androidx.media2.session.MediaController build();
+    method public androidx.media2.session.MediaController.Builder setConnectionHints(android.os.Bundle);
+    method public androidx.media2.session.MediaController.Builder setControllerCallback(java.util.concurrent.Executor, androidx.media2.session.MediaController.ControllerCallback);
+    method public androidx.media2.session.MediaController.Builder setSessionCompatToken(android.support.v4.media.session.MediaSessionCompat.Token);
+    method public androidx.media2.session.MediaController.Builder setSessionToken(androidx.media2.session.SessionToken);
+  }
+
+  public abstract static class MediaController.ControllerCallback {
+    ctor public MediaController.ControllerCallback();
+    method public void onAllowedCommandsChanged(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
+    method public void onBufferingStateChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, @androidx.media2.common.SessionPlayer.BuffState int);
+    method public void onConnected(androidx.media2.session.MediaController, androidx.media2.session.SessionCommandGroup);
+    method public void onCurrentMediaItemChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaItem?);
+    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaController, androidx.media2.session.SessionCommand, android.os.Bundle?);
+    method public void onDisconnected(androidx.media2.session.MediaController);
+    method public void onPlaybackCompleted(androidx.media2.session.MediaController);
+    method public void onPlaybackInfoChanged(androidx.media2.session.MediaController, androidx.media2.session.MediaController.PlaybackInfo);
+    method public void onPlaybackSpeedChanged(androidx.media2.session.MediaController, float);
+    method public void onPlayerStateChanged(androidx.media2.session.MediaController, @androidx.media2.common.SessionPlayer.PlayerState int);
+    method public void onPlaylistChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.MediaItem!>?, androidx.media2.common.MediaMetadata?);
+    method public void onPlaylistMetadataChanged(androidx.media2.session.MediaController, androidx.media2.common.MediaMetadata?);
+    method public void onRepeatModeChanged(androidx.media2.session.MediaController, @androidx.media2.common.SessionPlayer.RepeatMode int);
+    method public void onSeekCompleted(androidx.media2.session.MediaController, long);
+    method public int onSetCustomLayout(androidx.media2.session.MediaController, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
+    method public void onShuffleModeChanged(androidx.media2.session.MediaController, @androidx.media2.common.SessionPlayer.ShuffleMode int);
+    method public void onSubtitleData(androidx.media2.session.MediaController, androidx.media2.common.MediaItem, androidx.media2.common.SessionPlayer.TrackInfo, androidx.media2.common.SubtitleData);
+    method public void onTrackDeselected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
+    method public void onTrackSelected(androidx.media2.session.MediaController, androidx.media2.common.SessionPlayer.TrackInfo);
+    method public void onTracksChanged(androidx.media2.session.MediaController, java.util.List<androidx.media2.common.SessionPlayer.TrackInfo!>);
+    method public void onVideoSizeChanged(androidx.media2.session.MediaController, androidx.media2.common.VideoSize);
+  }
+
+  @androidx.versionedparcelable.VersionedParcelize public static final class MediaController.PlaybackInfo implements androidx.versionedparcelable.VersionedParcelable {
+    method public androidx.media.AudioAttributesCompat? getAudioAttributes();
+    method public int getControlType();
+    method public int getCurrentVolume();
+    method public int getMaxVolume();
+    method public int getPlaybackType();
+    field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
+    field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
+  }
+
+  public abstract class MediaLibraryService extends androidx.media2.session.MediaSessionService {
+    ctor public MediaLibraryService();
+    method public abstract androidx.media2.session.MediaLibraryService.MediaLibrarySession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
+    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaLibraryService";
+  }
+
+  @androidx.versionedparcelable.VersionedParcelize public static final class MediaLibraryService.LibraryParams implements androidx.versionedparcelable.VersionedParcelable {
+    method public android.os.Bundle? getExtras();
+    method public boolean isOffline();
+    method public boolean isRecent();
+    method public boolean isSuggested();
+  }
+
+  public static final class MediaLibraryService.LibraryParams.Builder {
+    ctor public MediaLibraryService.LibraryParams.Builder();
+    method public androidx.media2.session.MediaLibraryService.LibraryParams build();
+    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setExtras(android.os.Bundle?);
+    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setOffline(boolean);
+    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setRecent(boolean);
+    method public androidx.media2.session.MediaLibraryService.LibraryParams.Builder setSuggested(boolean);
+  }
+
+  public static final class MediaLibraryService.MediaLibrarySession extends androidx.media2.session.MediaSession {
+    method public void notifyChildrenChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public void notifyChildrenChanged(String, int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public void notifySearchResultChanged(androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+  }
+
+  public static final class MediaLibraryService.MediaLibrarySession.Builder {
+    ctor public MediaLibraryService.MediaLibrarySession.Builder(androidx.media2.session.MediaLibraryService, androidx.media2.common.SessionPlayer, java.util.concurrent.Executor, androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback);
+    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession build();
+    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setExtras(android.os.Bundle);
+    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setId(String);
+    method public androidx.media2.session.MediaLibraryService.MediaLibrarySession.Builder setSessionActivity(android.app.PendingIntent?);
+  }
+
+  public static class MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback extends androidx.media2.session.MediaSession.SessionCallback {
+    ctor public MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback();
+    method public androidx.media2.session.LibraryResult onGetChildren(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public androidx.media2.session.LibraryResult onGetItem(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
+    method public androidx.media2.session.LibraryResult onGetLibraryRoot(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public androidx.media2.session.LibraryResult onGetSearchResult(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public int onSearch(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public int onSubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.session.MediaLibraryService.LibraryParams?);
+    method public int onUnsubscribe(androidx.media2.session.MediaLibraryService.MediaLibrarySession, androidx.media2.session.MediaSession.ControllerInfo, String);
+  }
+
+  public class MediaSession implements java.io.Closeable {
+    method public void broadcastCustomCommand(androidx.media2.session.SessionCommand, android.os.Bundle?);
+    method public void close();
+    method public java.util.List<androidx.media2.session.MediaSession.ControllerInfo!> getConnectedControllers();
+    method public String getId();
+    method public androidx.media2.common.SessionPlayer getPlayer();
+    method public android.support.v4.media.session.MediaSessionCompat.Token getSessionCompatToken();
+    method public androidx.media2.session.SessionToken getToken();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> sendCustomCommand(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
+    method public void setAllowedCommands(androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommandGroup);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.session.SessionResult!> setCustomLayout(androidx.media2.session.MediaSession.ControllerInfo, java.util.List<androidx.media2.session.MediaSession.CommandButton!>);
+    method public void updatePlayer(androidx.media2.common.SessionPlayer);
+  }
+
+  public static final class MediaSession.Builder {
+    ctor public MediaSession.Builder(android.content.Context, androidx.media2.common.SessionPlayer);
+    method public androidx.media2.session.MediaSession build();
+    method public androidx.media2.session.MediaSession.Builder setExtras(android.os.Bundle);
+    method public androidx.media2.session.MediaSession.Builder setId(String);
+    method public androidx.media2.session.MediaSession.Builder setSessionActivity(android.app.PendingIntent?);
+    method public androidx.media2.session.MediaSession.Builder setSessionCallback(java.util.concurrent.Executor, androidx.media2.session.MediaSession.SessionCallback);
+  }
+
+  @androidx.versionedparcelable.VersionedParcelize public static final class MediaSession.CommandButton implements androidx.versionedparcelable.VersionedParcelable {
+    method public androidx.media2.session.SessionCommand? getCommand();
+    method public CharSequence? getDisplayName();
+    method public android.os.Bundle? getExtras();
+    method public int getIconResId();
+    method public boolean isEnabled();
+  }
+
+  public static final class MediaSession.CommandButton.Builder {
+    ctor public MediaSession.CommandButton.Builder();
+    method public androidx.media2.session.MediaSession.CommandButton build();
+    method public androidx.media2.session.MediaSession.CommandButton.Builder setCommand(androidx.media2.session.SessionCommand?);
+    method public androidx.media2.session.MediaSession.CommandButton.Builder setDisplayName(CharSequence?);
+    method public androidx.media2.session.MediaSession.CommandButton.Builder setEnabled(boolean);
+    method public androidx.media2.session.MediaSession.CommandButton.Builder setExtras(android.os.Bundle?);
+    method public androidx.media2.session.MediaSession.CommandButton.Builder setIconResId(int);
+  }
+
+  public static final class MediaSession.ControllerInfo {
+    method public android.os.Bundle getConnectionHints();
+    method public String getPackageName();
+    method public int getUid();
+  }
+
+  public abstract static class MediaSession.SessionCallback {
+    ctor public MediaSession.SessionCallback();
+    method public int onCommandRequest(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand);
+    method public androidx.media2.session.SessionCommandGroup? onConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+    method public androidx.media2.common.MediaItem? onCreateMediaItem(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String);
+    method public androidx.media2.session.SessionResult onCustomCommand(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, androidx.media2.session.SessionCommand, android.os.Bundle?);
+    method public void onDisconnected(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+    method public int onFastForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+    method public void onPostConnect(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+    method public int onRewind(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+    method public int onSetMediaUri(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, android.net.Uri, android.os.Bundle?);
+    method public int onSetRating(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo, String, androidx.media2.common.Rating);
+    method public int onSkipBackward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+    method public int onSkipForward(androidx.media2.session.MediaSession, androidx.media2.session.MediaSession.ControllerInfo);
+  }
+
+  public final class MediaSessionManager {
+    method public static androidx.media2.session.MediaSessionManager getInstance(android.content.Context);
+    method public java.util.Set<androidx.media2.session.SessionToken!> getSessionServiceTokens();
+  }
+
+  public abstract class MediaSessionService extends android.app.Service {
+    ctor public MediaSessionService();
+    method public final void addSession(androidx.media2.session.MediaSession);
+    method public final java.util.List<androidx.media2.session.MediaSession!> getSessions();
+    method @CallSuper public android.os.IBinder? onBind(android.content.Intent);
+    method public abstract androidx.media2.session.MediaSession? onGetSession(androidx.media2.session.MediaSession.ControllerInfo);
+    method public androidx.media2.session.MediaSessionService.MediaNotification? onUpdateNotification(androidx.media2.session.MediaSession);
+    method public final void removeSession(androidx.media2.session.MediaSession);
+    field public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaSessionService";
+  }
+
+  public static class MediaSessionService.MediaNotification {
+    ctor public MediaSessionService.MediaNotification(int, android.app.Notification);
+    method public android.app.Notification getNotification();
+    method public int getNotificationId();
+  }
+
+  @androidx.versionedparcelable.VersionedParcelize public final class PercentageRating implements androidx.media2.common.Rating {
+    ctor public PercentageRating();
+    ctor public PercentageRating(float);
+    method public float getPercentRating();
+    method public boolean isRated();
+  }
+
+  public abstract class RemoteSessionPlayer extends androidx.media2.common.SessionPlayer {
+    ctor public RemoteSessionPlayer();
+    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> adjustVolume(int);
+    method public abstract int getMaxVolume();
+    method public abstract int getVolume();
+    method public abstract int getVolumeControlType();
+    method public abstract java.util.concurrent.Future<androidx.media2.common.SessionPlayer.PlayerResult!> setVolume(int);
+    field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
+    field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
+    field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
+  }
+
+  public static class RemoteSessionPlayer.Callback extends androidx.media2.common.SessionPlayer.PlayerCallback {
+    ctor public RemoteSessionPlayer.Callback();
+    method public void onVolumeChanged(androidx.media2.session.RemoteSessionPlayer, int);
+  }
+
+  @androidx.versionedparcelable.VersionedParcelize public final class SessionCommand implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public SessionCommand(int);
+    ctor public SessionCommand(String, android.os.Bundle?);
+    method public int getCommandCode();
+    method public String? getCustomAction();
+    method public android.os.Bundle? getCustomExtras();
+    field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
+    field public static final int COMMAND_CODE_LIBRARY_GET_CHILDREN = 50003; // 0xc353
+    field public static final int COMMAND_CODE_LIBRARY_GET_ITEM = 50004; // 0xc354
+    field public static final int COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT = 50000; // 0xc350
+    field public static final int COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT = 50006; // 0xc356
+    field public static final int COMMAND_CODE_LIBRARY_SEARCH = 50005; // 0xc355
+    field public static final int COMMAND_CODE_LIBRARY_SUBSCRIBE = 50001; // 0xc351
+    field public static final int COMMAND_CODE_LIBRARY_UNSUBSCRIBE = 50002; // 0xc352
+    field public static final int COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM = 10013; // 0x271d
+    field public static final int COMMAND_CODE_PLAYER_DESELECT_TRACK = 11002; // 0x2afa
+    field public static final int COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM = 10016; // 0x2720
+    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST = 10005; // 0x2715
+    field public static final int COMMAND_CODE_PLAYER_GET_PLAYLIST_METADATA = 10012; // 0x271c
+    field public static final int COMMAND_CODE_PLAYER_MOVE_PLAYLIST_ITEM = 10019; // 0x2723
+    field public static final int COMMAND_CODE_PLAYER_PAUSE = 10001; // 0x2711
+    field public static final int COMMAND_CODE_PLAYER_PLAY = 10000; // 0x2710
+    field public static final int COMMAND_CODE_PLAYER_PREPARE = 10002; // 0x2712
+    field public static final int COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM = 10014; // 0x271e
+    field public static final int COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM = 10015; // 0x271f
+    field public static final int COMMAND_CODE_PLAYER_SEEK_TO = 10003; // 0x2713
+    field public static final int COMMAND_CODE_PLAYER_SELECT_TRACK = 11001; // 0x2af9
+    field public static final int COMMAND_CODE_PLAYER_SET_MEDIA_ITEM = 10018; // 0x2722
+    field public static final int COMMAND_CODE_PLAYER_SET_PLAYLIST = 10006; // 0x2716
+    field public static final int COMMAND_CODE_PLAYER_SET_REPEAT_MODE = 10011; // 0x271b
+    field public static final int COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE = 10010; // 0x271a
+    field public static final int COMMAND_CODE_PLAYER_SET_SPEED = 10004; // 0x2714
+    field public static final int COMMAND_CODE_PLAYER_SET_SURFACE = 11000; // 0x2af8
+    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM = 10009; // 0x2719
+    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM = 10007; // 0x2717
+    field public static final int COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM = 10008; // 0x2718
+    field public static final int COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA = 10017; // 0x2721
+    field public static final int COMMAND_CODE_SESSION_FAST_FORWARD = 40000; // 0x9c40
+    field public static final int COMMAND_CODE_SESSION_REWIND = 40001; // 0x9c41
+    field public static final int COMMAND_CODE_SESSION_SET_MEDIA_URI = 40011; // 0x9c4b
+    field public static final int COMMAND_CODE_SESSION_SET_RATING = 40010; // 0x9c4a
+    field public static final int COMMAND_CODE_SESSION_SKIP_BACKWARD = 40003; // 0x9c43
+    field public static final int COMMAND_CODE_SESSION_SKIP_FORWARD = 40002; // 0x9c42
+    field public static final int COMMAND_CODE_VOLUME_ADJUST_VOLUME = 30001; // 0x7531
+    field public static final int COMMAND_CODE_VOLUME_SET_VOLUME = 30000; // 0x7530
+    field public static final int COMMAND_VERSION_1 = 1; // 0x1
+    field public static final int COMMAND_VERSION_2 = 2; // 0x2
+  }
+
+  @androidx.versionedparcelable.VersionedParcelize public final class SessionCommandGroup implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public SessionCommandGroup();
+    ctor public SessionCommandGroup(java.util.Collection<androidx.media2.session.SessionCommand!>?);
+    method public java.util.Set<androidx.media2.session.SessionCommand!> getCommands();
+    method public boolean hasCommand(androidx.media2.session.SessionCommand);
+    method public boolean hasCommand(int);
+  }
+
+  public static final class SessionCommandGroup.Builder {
+    ctor public SessionCommandGroup.Builder();
+    ctor public SessionCommandGroup.Builder(androidx.media2.session.SessionCommandGroup);
+    method public androidx.media2.session.SessionCommandGroup.Builder addAllPredefinedCommands(int);
+    method public androidx.media2.session.SessionCommandGroup.Builder addCommand(androidx.media2.session.SessionCommand);
+    method public androidx.media2.session.SessionCommandGroup build();
+    method public androidx.media2.session.SessionCommandGroup.Builder removeCommand(androidx.media2.session.SessionCommand);
+  }
+
+  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class SessionResult extends androidx.versionedparcelable.CustomVersionedParcelable implements androidx.media2.common.BaseResult {
+    ctor public SessionResult(int, android.os.Bundle?);
+    method public long getCompletionTime();
+    method public android.os.Bundle? getCustomCommandResult();
+    method public androidx.media2.common.MediaItem? getMediaItem();
+    method public int getResultCode();
+    field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
+    field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
+    field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c
+    field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
+    field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
+    field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
+    field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94
+    field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95
+    field public static final int RESULT_SUCCESS = 0; // 0x0
+  }
+
+  @androidx.versionedparcelable.VersionedParcelize public final class SessionToken implements androidx.versionedparcelable.VersionedParcelable {
+    ctor public SessionToken(android.content.Context, android.content.ComponentName);
+    method public android.os.Bundle getExtras();
+    method public String getPackageName();
+    method public String? getServiceName();
+    method public int getType();
+    method public int getUid();
+    field public static final int TYPE_LIBRARY_SERVICE = 2; // 0x2
+    field public static final int TYPE_SESSION = 0; // 0x0
+    field public static final int TYPE_SESSION_SERVICE = 1; // 0x1
+  }
+
+  @androidx.versionedparcelable.VersionedParcelize public final class StarRating implements androidx.media2.common.Rating {
+    ctor public StarRating(@IntRange(from=1) int);
+    ctor public StarRating(@IntRange(from=1) int, float);
+    method public int getMaxStars();
+    method public float getStarRating();
+    method public boolean isRated();
+  }
+
+  @androidx.versionedparcelable.VersionedParcelize public final class ThumbRating implements androidx.media2.common.Rating {
+    ctor public ThumbRating();
+    ctor public ThumbRating(boolean);
+    method public boolean isRated();
+    method public boolean isThumbUp();
+  }
+
+}
+
diff --git a/media2/media2-widget/api/1.2.0-beta01.txt b/media2/media2-widget/api/1.2.0-beta01.txt
new file mode 100644
index 0000000..c21654a
--- /dev/null
+++ b/media2/media2-widget/api/1.2.0-beta01.txt
@@ -0,0 +1,38 @@
+// Signature format: 4.0
+package androidx.media2.widget {
+
+  public class MediaControlView extends android.view.ViewGroup {
+    ctor public MediaControlView(android.content.Context);
+    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?);
+    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?, int);
+    method public void requestPlayButtonFocus();
+    method public void setMediaController(androidx.media2.session.MediaController);
+    method public void setOnFullScreenListener(androidx.media2.widget.MediaControlView.OnFullScreenListener?);
+    method public void setPlayer(androidx.media2.common.SessionPlayer);
+  }
+
+  public static interface MediaControlView.OnFullScreenListener {
+    method public void onFullScreen(android.view.View, boolean);
+  }
+
+  public class VideoView extends android.view.ViewGroup {
+    ctor public VideoView(android.content.Context);
+    ctor public VideoView(android.content.Context, android.util.AttributeSet?);
+    ctor public VideoView(android.content.Context, android.util.AttributeSet?, int);
+    method public androidx.media2.widget.MediaControlView? getMediaControlView();
+    method public int getViewType();
+    method public void setMediaControlView(androidx.media2.widget.MediaControlView, long);
+    method public void setMediaController(androidx.media2.session.MediaController);
+    method public void setOnViewTypeChangedListener(androidx.media2.widget.VideoView.OnViewTypeChangedListener?);
+    method public void setPlayer(androidx.media2.common.SessionPlayer);
+    method public void setViewType(int);
+    field public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
+    field public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
+  }
+
+  public static interface VideoView.OnViewTypeChangedListener {
+    method public void onViewTypeChanged(android.view.View, int);
+  }
+
+}
+
diff --git a/media2/media2-widget/api/public_plus_experimental_1.2.0-beta01.txt b/media2/media2-widget/api/public_plus_experimental_1.2.0-beta01.txt
new file mode 100644
index 0000000..c21654a
--- /dev/null
+++ b/media2/media2-widget/api/public_plus_experimental_1.2.0-beta01.txt
@@ -0,0 +1,38 @@
+// Signature format: 4.0
+package androidx.media2.widget {
+
+  public class MediaControlView extends android.view.ViewGroup {
+    ctor public MediaControlView(android.content.Context);
+    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?);
+    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?, int);
+    method public void requestPlayButtonFocus();
+    method public void setMediaController(androidx.media2.session.MediaController);
+    method public void setOnFullScreenListener(androidx.media2.widget.MediaControlView.OnFullScreenListener?);
+    method public void setPlayer(androidx.media2.common.SessionPlayer);
+  }
+
+  public static interface MediaControlView.OnFullScreenListener {
+    method public void onFullScreen(android.view.View, boolean);
+  }
+
+  public class VideoView extends android.view.ViewGroup {
+    ctor public VideoView(android.content.Context);
+    ctor public VideoView(android.content.Context, android.util.AttributeSet?);
+    ctor public VideoView(android.content.Context, android.util.AttributeSet?, int);
+    method public androidx.media2.widget.MediaControlView? getMediaControlView();
+    method public int getViewType();
+    method public void setMediaControlView(androidx.media2.widget.MediaControlView, long);
+    method public void setMediaController(androidx.media2.session.MediaController);
+    method public void setOnViewTypeChangedListener(androidx.media2.widget.VideoView.OnViewTypeChangedListener?);
+    method public void setPlayer(androidx.media2.common.SessionPlayer);
+    method public void setViewType(int);
+    field public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
+    field public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
+  }
+
+  public static interface VideoView.OnViewTypeChangedListener {
+    method public void onViewTypeChanged(android.view.View, int);
+  }
+
+}
+
diff --git a/media2/media2-widget/api/res-1.2.0-beta01.txt b/media2/media2-widget/api/res-1.2.0-beta01.txt
new file mode 100644
index 0000000..9015818
--- /dev/null
+++ b/media2/media2-widget/api/res-1.2.0-beta01.txt
@@ -0,0 +1,2 @@
+attr enableControlView
+attr viewType
diff --git a/media2/media2-widget/api/restricted_1.2.0-beta01.txt b/media2/media2-widget/api/restricted_1.2.0-beta01.txt
new file mode 100644
index 0000000..c21654a
--- /dev/null
+++ b/media2/media2-widget/api/restricted_1.2.0-beta01.txt
@@ -0,0 +1,38 @@
+// Signature format: 4.0
+package androidx.media2.widget {
+
+  public class MediaControlView extends android.view.ViewGroup {
+    ctor public MediaControlView(android.content.Context);
+    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?);
+    ctor public MediaControlView(android.content.Context, android.util.AttributeSet?, int);
+    method public void requestPlayButtonFocus();
+    method public void setMediaController(androidx.media2.session.MediaController);
+    method public void setOnFullScreenListener(androidx.media2.widget.MediaControlView.OnFullScreenListener?);
+    method public void setPlayer(androidx.media2.common.SessionPlayer);
+  }
+
+  public static interface MediaControlView.OnFullScreenListener {
+    method public void onFullScreen(android.view.View, boolean);
+  }
+
+  public class VideoView extends android.view.ViewGroup {
+    ctor public VideoView(android.content.Context);
+    ctor public VideoView(android.content.Context, android.util.AttributeSet?);
+    ctor public VideoView(android.content.Context, android.util.AttributeSet?, int);
+    method public androidx.media2.widget.MediaControlView? getMediaControlView();
+    method public int getViewType();
+    method public void setMediaControlView(androidx.media2.widget.MediaControlView, long);
+    method public void setMediaController(androidx.media2.session.MediaController);
+    method public void setOnViewTypeChangedListener(androidx.media2.widget.VideoView.OnViewTypeChangedListener?);
+    method public void setPlayer(androidx.media2.common.SessionPlayer);
+    method public void setViewType(int);
+    field public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
+    field public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
+  }
+
+  public static interface VideoView.OnViewTypeChangedListener {
+    method public void onViewTypeChanged(android.view.View, int);
+  }
+
+}
+
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavDeepLinkBuilder.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavDeepLinkBuilder.kt
index 3494ad7..5c27169 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavDeepLinkBuilder.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavDeepLinkBuilder.kt
@@ -336,8 +336,10 @@
                 }
             }
         }
-        return createTaskStackBuilder()
-            .getPendingIntent(requestCode, PendingIntent.FLAG_UPDATE_CURRENT)!!
+        return createTaskStackBuilder().getPendingIntent(
+            requestCode,
+            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+        )!!
     }
 
     /**
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt b/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
index 16cb5fc..895d1565 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
@@ -21,11 +21,16 @@
 import androidx.paging.LoadType.APPEND
 import androidx.paging.LoadType.PREPEND
 import androidx.paging.LoadType.REFRESH
+import androidx.paging.PageEvent.Drop
+import androidx.paging.PageEvent.Insert
 import androidx.paging.PagePresenter.ProcessPageEventCallback
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.BufferOverflow.DROP_OLDEST
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.withContext
 import kotlinx.coroutines.yield
@@ -41,6 +46,7 @@
     private var receiver: UiReceiver? = null
     private val combinedLoadStates = MutableLoadStateCollection()
     private val loadStateListeners = CopyOnWriteArrayList<(CombinedLoadStates) -> Unit>()
+    private val onPagesUpdatedListeners = CopyOnWriteArrayList<() -> Unit>()
 
     private val collectFromRunner = SingleRunner()
 
@@ -128,8 +134,8 @@
 
             // TODO: Validate only empty pages between separator pages and its dependent pages.
             pagingData.flow.collect { event ->
-                withContext<Unit>(mainDispatcher) {
-                    if (event is PageEvent.Insert && event.loadType == REFRESH) {
+                withContext(mainDispatcher) {
+                    if (event is Insert && event.loadType == REFRESH) {
                         lastAccessedIndexUnfulfilled = false
 
                         val newPresenter = PagePresenter(event)
@@ -185,13 +191,13 @@
 
                         // Reset lastAccessedIndexUnfulfilled if a page is dropped, to avoid
                         // infinite loops when maxSize is insufficiently large.
-                        if (event is PageEvent.Drop) {
+                        if (event is Drop) {
                             lastAccessedIndexUnfulfilled = false
                         }
 
                         // If index points to a placeholder after transformations, resend it unless
                         // there are no more items to load.
-                        if (event is PageEvent.Insert) {
+                        if (event is Insert) {
                             val prependDone =
                                 event.combinedLoadStates.prepend.endOfPaginationReached
                             val appendDone = event.combinedLoadStates.append.endOfPaginationReached
@@ -229,6 +235,15 @@
                             }
                         }
                     }
+
+                    // Notify page updates after presenter processes them.
+                    //
+                    // Note: This is not redundant with LoadStates because it does not de-dupe
+                    // in cases where LoadState does not change, which would happen on cached
+                    // PagingData collections.
+                    if (event is Insert || event is Drop) {
+                        onPagesUpdatedListeners.forEach { it() }
+                    }
                 }
             }
         }
@@ -321,13 +336,73 @@
     public val loadStateFlow: Flow<CombinedLoadStates>
         get() = _combinedLoadState
 
+    private val _onPagesUpdatedFlow: MutableSharedFlow<Unit> = MutableSharedFlow(
+        replay = 0,
+        extraBufferCapacity = 64,
+        onBufferOverflow = DROP_OLDEST,
+    )
+
+    /**
+     * A hot [Flow] that emits after the pages presented to the UI are updated, even if the
+     * actual items presented don't change.
+     *
+     * An update is triggered from one of the following:
+     *   * [collectFrom] is called and initial load completes, regardless of any differences in
+     *     the loaded data
+     *   * A [Page][androidx.paging.PagingSource.LoadResult.Page] is inserted
+     *   * A [Page][androidx.paging.PagingSource.LoadResult.Page] is dropped
+     *
+     * Note: This is a [SharedFlow][kotlinx.coroutines.flow.SharedFlow] configured to replay
+     * 0 items with a buffer of size 64. If a collector lags behind page updates, it may
+     * trigger multiple times for each intermediate update that was presented while your collector
+     * was still working. To avoid this behavior, you can
+     * [conflate][kotlinx.coroutines.flow.conflate] this [Flow] so that you only receive the latest
+     * update, which is useful in cases where you are simply updating UI and don't care about
+     * tracking the exact number of page updates.
+     */
+    public val onPagesUpdatedFlow: Flow<Unit>
+        get() = _onPagesUpdatedFlow.asSharedFlow()
+
     init {
+        addOnPagesUpdatedListener {
+            _onPagesUpdatedFlow.tryEmit(Unit)
+        }
+
         addLoadStateListener {
             _combinedLoadState.value = it
         }
     }
 
     /**
+     * Add a listener which triggers after the pages presented to the UI are updated, even if the
+     * actual items presented don't change.
+     *
+     * An update is triggered from one of the following:
+     *   * [collectFrom] is called and initial load completes, regardless of any differences in
+     *     the loaded data
+     *   * A [Page][androidx.paging.PagingSource.LoadResult.Page] is inserted
+     *   * A [Page][androidx.paging.PagingSource.LoadResult.Page] is dropped
+     *
+     * @param listener called after pages presented are updated.
+     *
+     * @see removeOnPagesUpdatedListener
+     */
+    public fun addOnPagesUpdatedListener(listener: () -> Unit) {
+        onPagesUpdatedListeners.add(listener)
+    }
+
+    /**
+     * Remove a previously registered listener for updates to presented pages.
+     *
+     * @param listener Previously registered listener.
+     *
+     * @see addOnPagesUpdatedListener
+     */
+    public fun removeOnPagesUpdatedListener(listener: () -> Unit) {
+        onPagesUpdatedListeners.remove(listener)
+    }
+
+    /**
      * Add a [CombinedLoadStates] listener to observe the loading state of the current [PagingData].
      *
      * As new [PagingData] generations are submitted and displayed, the listener will be notified to
diff --git a/paging/common/src/test/kotlin/androidx/paging/PagingDataDifferTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagingDataDifferTest.kt
index 2b02f56..5aae1a0 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PagingDataDifferTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PagingDataDifferTest.kt
@@ -27,6 +27,9 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.consumeAsFlow
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.launch
@@ -486,6 +489,178 @@
 
         job.cancel()
     }
+
+    @Test
+    fun onPagingDataPresentedListener_empty() = testScope.runBlockingTest {
+        val differ = SimpleDiffer(dummyDifferCallback)
+        val listenerEvents = mutableListOf<Unit>()
+        differ.addOnPagesUpdatedListener {
+            listenerEvents.add(Unit)
+        }
+
+        differ.collectFrom(PagingData.empty())
+        assertThat(listenerEvents.size).isEqualTo(1)
+
+        // No change to LoadState or presented list should still trigger the listener.
+        differ.collectFrom(PagingData.empty())
+        assertThat(listenerEvents.size).isEqualTo(2)
+
+        val pager = Pager(PagingConfig(pageSize = 1)) { TestPagingSource(items = listOf()) }
+        val job = testScope.launch {
+            pager.flow.collectLatest { differ.collectFrom(it) }
+        }
+
+        // Should wait for new generation to load and apply it first.
+        assertThat(listenerEvents.size).isEqualTo(2)
+
+        advanceUntilIdle()
+        assertThat(listenerEvents.size).isEqualTo(3)
+
+        job.cancel()
+    }
+
+    @Test
+    fun onPagingDataPresentedListener_insertDrop() = testScope.runBlockingTest {
+        val differ = SimpleDiffer(dummyDifferCallback)
+        val listenerEvents = mutableListOf<Unit>()
+        differ.addOnPagesUpdatedListener {
+            listenerEvents.add(Unit)
+        }
+
+        val pager = Pager(PagingConfig(pageSize = 1, maxSize = 4), initialKey = 50) {
+            TestPagingSource()
+        }
+        val job = testScope.launch {
+            pager.flow.collectLatest { differ.collectFrom(it) }
+        }
+
+        // Should wait for new generation to load and apply it first.
+        assertThat(listenerEvents.size).isEqualTo(0)
+
+        advanceUntilIdle()
+        assertThat(listenerEvents.size).isEqualTo(1)
+
+        // Trigger PREPEND.
+        differ[50]
+        assertThat(listenerEvents.size).isEqualTo(1)
+        advanceUntilIdle()
+        assertThat(listenerEvents.size).isEqualTo(2)
+
+        // Trigger APPEND + Drop
+        differ[52]
+        assertThat(listenerEvents.size).isEqualTo(2)
+        advanceUntilIdle()
+        assertThat(listenerEvents.size).isEqualTo(4)
+
+        job.cancel()
+    }
+
+    @Test
+    fun onPagingDataPresentedFlow_empty() = testScope.runBlockingTest {
+        val differ = SimpleDiffer(dummyDifferCallback)
+        val listenerEvents = mutableListOf<Unit>()
+        val job1 = testScope.launch {
+            differ.onPagesUpdatedFlow.collect {
+                listenerEvents.add(Unit)
+            }
+        }
+
+        differ.collectFrom(PagingData.empty())
+        assertThat(listenerEvents.size).isEqualTo(1)
+
+        // No change to LoadState or presented list should still trigger the listener.
+        differ.collectFrom(PagingData.empty())
+        assertThat(listenerEvents.size).isEqualTo(2)
+
+        val pager = Pager(PagingConfig(pageSize = 1)) { TestPagingSource(items = listOf()) }
+        val job2 = testScope.launch {
+            pager.flow.collectLatest { differ.collectFrom(it) }
+        }
+
+        // Should wait for new generation to load and apply it first.
+        assertThat(listenerEvents.size).isEqualTo(2)
+
+        advanceUntilIdle()
+        assertThat(listenerEvents.size).isEqualTo(3)
+
+        job1.cancel()
+        job2.cancel()
+    }
+
+    @Test
+    fun onPagingDataPresentedFlow_insertDrop() = testScope.runBlockingTest {
+        val differ = SimpleDiffer(dummyDifferCallback)
+        val listenerEvents = mutableListOf<Unit>()
+        val job1 = testScope.launch {
+            differ.onPagesUpdatedFlow.collect {
+                listenerEvents.add(Unit)
+            }
+        }
+
+        val pager = Pager(PagingConfig(pageSize = 1, maxSize = 4), initialKey = 50) {
+            TestPagingSource()
+        }
+        val job2 = testScope.launch {
+            pager.flow.collectLatest { differ.collectFrom(it) }
+        }
+
+        // Should wait for new generation to load and apply it first.
+        assertThat(listenerEvents.size).isEqualTo(0)
+
+        advanceUntilIdle()
+        assertThat(listenerEvents.size).isEqualTo(1)
+
+        // Trigger PREPEND.
+        differ[50]
+        assertThat(listenerEvents.size).isEqualTo(1)
+        advanceUntilIdle()
+        assertThat(listenerEvents.size).isEqualTo(2)
+
+        // Trigger APPEND + Drop
+        differ[52]
+        assertThat(listenerEvents.size).isEqualTo(2)
+        advanceUntilIdle()
+        assertThat(listenerEvents.size).isEqualTo(4)
+
+        job1.cancel()
+        job2.cancel()
+    }
+
+    @Test
+    fun onPagingDataPresentedFlow_buffer() = testScope.runBlockingTest {
+        val differ = SimpleDiffer(dummyDifferCallback)
+        val listenerEvents = mutableListOf<Unit>()
+
+        // Trigger update, which should get ignored due to onPagesUpdatedFlow being hot.
+        differ.collectFrom(PagingData.empty())
+
+        val job = testScope.launch {
+            differ.onPagesUpdatedFlow.collect {
+                listenerEvents.add(Unit)
+                // Await advanceUntilIdle() before accepting another event.
+                delay(100)
+            }
+        }
+
+        // Previous update before collection happened should be ignored.
+        assertThat(listenerEvents.size).isEqualTo(0)
+
+        // Trigger update; should get immediately received.
+        differ.collectFrom(PagingData.empty())
+        assertThat(listenerEvents.size).isEqualTo(1)
+
+        // Trigger 64 update while collector is still processing; should all get buffered.
+        repeat(64) { differ.collectFrom(PagingData.empty()) }
+
+        // Trigger another update while collector is still processing; should cause event to drop.
+        differ.collectFrom(PagingData.empty())
+
+        // Await all; we should now receive the buffered event.
+        advanceUntilIdle()
+        assertThat(listenerEvents.size).isEqualTo(65)
+
+        job.cancel()
+    }
 }
 
 private fun infinitelySuspendingPagingData(receiver: UiReceiver = dummyReceiver) = PagingData(
diff --git a/paging/integration-tests/testapp/build.gradle b/paging/integration-tests/testapp/build.gradle
index 4b067c3..cc562d3 100644
--- a/paging/integration-tests/testapp/build.gradle
+++ b/paging/integration-tests/testapp/build.gradle
@@ -39,6 +39,13 @@
     implementation("androidx.fragment:fragment-ktx:1.3.0")
     implementation("androidx.appcompat:appcompat:1.1.0")
     implementation(libs.kotlinStdlib)
+
+
+    androidTestImplementation(libs.kotlinTest)
+    androidTestImplementation(libs.testCore)
+    androidTestImplementation(libs.testExtJunit)
+    androidTestImplementation(libs.testRunner)
+    androidTestImplementation(libs.truth)
 }
 
 kapt {
diff --git a/paging/integration-tests/testapp/src/androidTest/kotlin/androidx/paging/integration/testapp/v3/OnPagesUpdatedTest.kt b/paging/integration-tests/testapp/src/androidTest/kotlin/androidx/paging/integration/testapp/v3/OnPagesUpdatedTest.kt
new file mode 100644
index 0000000..88cf1d6
--- /dev/null
+++ b/paging/integration-tests/testapp/src/androidTest/kotlin/androidx/paging/integration/testapp/v3/OnPagesUpdatedTest.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.paging.integration.testapp.v3
+
+import androidx.lifecycle.lifecycleScope
+import androidx.paging.LoadState
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertTrue
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class OnPagesUpdatedTest {
+    @get:Rule
+    val scenarioRule = ActivityScenarioRule(V3Activity::class.java)
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun onPagesUpdatedFlow() = runBlocking {
+        val scenario = scenarioRule.scenario
+
+        lateinit var job: Job
+        lateinit var adapter: V3Adapter
+        scenario.onActivity { activity ->
+            adapter = activity.pagingAdapter
+        }
+
+        // Wait for initial load to complete.
+        adapter.onPagesUpdatedFlow.first { adapter.itemCount > 0 }
+
+        val onPagesUpdatedEventsCh = Channel<Unit>(capacity = 100)
+        val processNextPageUpdateCh = Channel<Unit>(capacity = 1)
+        scenario.onActivity { activity ->
+            // Items are loaded before we start observing.
+            assertThat(activity.pagingAdapter.itemCount).isGreaterThan(0)
+
+            job = activity.lifecycleScope.launch {
+                activity.pagingAdapter.onPagesUpdatedFlow.collect {
+                    onPagesUpdatedEventsCh.send(it)
+                    processNextPageUpdateCh.receive()
+                }
+            }
+
+            // Page update from before we started listening should not be buffered.
+            assertTrue { onPagesUpdatedEventsCh.isEmpty }
+        }
+
+        // Trigger page update.
+        scenario.onActivity { adapter.refresh() }
+        adapter.awaitRefreshIdle()
+        onPagesUpdatedEventsCh.receiveWithTimeoutMillis(10_000)
+
+        // Trigger page update while still processing previous one, this should get buffered.
+        scenario.onActivity { adapter.refresh() }
+        adapter.awaitRefreshIdle()
+        // Ensure we are still waiting for processNextPageUpdateCh to emit to continue.
+        assertTrue { onPagesUpdatedEventsCh.isEmpty }
+
+        // Now allow collector to continue until idle.
+        processNextPageUpdateCh.send(Unit)
+        onPagesUpdatedEventsCh.receiveWithTimeoutMillis(10_000)
+        processNextPageUpdateCh.send(Unit)
+
+        // Trigger a bunch of updates without unblocking page update collector.
+        repeat(66) {
+            scenario.onActivity { adapter.refresh() }
+            adapter.awaitRefreshIdle()
+        }
+
+        // Fully unblock collector.
+        var pageUpdates = 0
+        try {
+            while (true) {
+                processNextPageUpdateCh.trySend(Unit)
+                onPagesUpdatedEventsCh.receiveWithTimeoutMillis(10_000)
+                pageUpdates++
+            }
+        } catch (e: TimeoutCancellationException) {
+            // Ignored, we will eventually hit this once we receive all events.
+        }
+
+        // We should receive exactly 65 events, due to 64 getting buffered.
+        assertThat(pageUpdates).isEqualTo(65)
+
+        onPagesUpdatedEventsCh.close()
+        processNextPageUpdateCh.close()
+        job.cancel()
+    }
+
+    private suspend fun Channel<Unit>.receiveWithTimeoutMillis(timeoutMillis: Long) {
+        withTimeout(timeoutMillis) { receive() }
+    }
+
+    private suspend fun V3Adapter.awaitRefreshIdle() {
+        loadStateFlow.first { it.source.refresh !is LoadState.Loading }
+    }
+}
\ No newline at end of file
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/V3Activity.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/V3Activity.kt
index a8288e4..787bf63 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/V3Activity.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/V3Activity.kt
@@ -35,13 +35,14 @@
 import kotlinx.coroutines.launch
 
 class V3Activity : AppCompatActivity() {
+    val pagingAdapter = V3Adapter()
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
         setContentView(R.layout.activity_recycler_view)
         val viewModel by viewModels<V3ViewModel>()
 
-        val pagingAdapter = V3Adapter()
         val orientationText = when (resources.configuration.orientation) {
             Configuration.ORIENTATION_LANDSCAPE -> "land"
             Configuration.ORIENTATION_PORTRAIT -> "port"
diff --git a/paging/runtime/api/current.txt b/paging/runtime/api/current.txt
index b886d2e..4fcb026 100644
--- a/paging/runtime/api/current.txt
+++ b/paging/runtime/api/current.txt
@@ -28,18 +28,22 @@
     ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
     ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
     method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public void addOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public T? getItem(@IntRange(from=0) int index);
     method public int getItemCount();
     method public kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getOnPagesUpdatedFlow();
     method public T? peek(@IntRange(from=0) int index);
     method public void refresh();
     method public void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public void removeOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public void retry();
     method public androidx.paging.ItemSnapshotList<T> snapshot();
     method public suspend Object? submitData(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     method public void submitData(androidx.lifecycle.Lifecycle lifecycle, androidx.paging.PagingData<T> pagingData);
     property public final int itemCount;
     property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+    property public final kotlinx.coroutines.flow.Flow<kotlin.Unit> onPagesUpdatedFlow;
   }
 
   @Deprecated public final class LivePagedListBuilder<Key, Value> {
@@ -102,13 +106,16 @@
     ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
     ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
     method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void addOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method protected final T? getItem(@IntRange(from=0) int position);
     method public int getItemCount();
     method public final long getItemId(int position);
     method public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getOnPagesUpdatedFlow();
     method public final T? peek(@IntRange(from=0) int index);
     method public final void refresh();
     method public final void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void removeOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public final void retry();
     method public final void setHasStableIds(boolean hasStableIds);
     method public final androidx.paging.ItemSnapshotList<T> snapshot();
@@ -118,6 +125,7 @@
     method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeader(androidx.paging.LoadStateAdapter<?> header);
     method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeaderAndFooter(androidx.paging.LoadStateAdapter<?> header, androidx.paging.LoadStateAdapter<?> footer);
     property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+    property public final kotlinx.coroutines.flow.Flow<kotlin.Unit> onPagesUpdatedFlow;
   }
 
   public final class PagingLiveData {
diff --git a/paging/runtime/api/public_plus_experimental_current.txt b/paging/runtime/api/public_plus_experimental_current.txt
index b886d2e..4fcb026 100644
--- a/paging/runtime/api/public_plus_experimental_current.txt
+++ b/paging/runtime/api/public_plus_experimental_current.txt
@@ -28,18 +28,22 @@
     ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
     ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
     method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public void addOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public T? getItem(@IntRange(from=0) int index);
     method public int getItemCount();
     method public kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getOnPagesUpdatedFlow();
     method public T? peek(@IntRange(from=0) int index);
     method public void refresh();
     method public void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public void removeOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public void retry();
     method public androidx.paging.ItemSnapshotList<T> snapshot();
     method public suspend Object? submitData(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     method public void submitData(androidx.lifecycle.Lifecycle lifecycle, androidx.paging.PagingData<T> pagingData);
     property public final int itemCount;
     property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+    property public final kotlinx.coroutines.flow.Flow<kotlin.Unit> onPagesUpdatedFlow;
   }
 
   @Deprecated public final class LivePagedListBuilder<Key, Value> {
@@ -102,13 +106,16 @@
     ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
     ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
     method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void addOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method protected final T? getItem(@IntRange(from=0) int position);
     method public int getItemCount();
     method public final long getItemId(int position);
     method public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getOnPagesUpdatedFlow();
     method public final T? peek(@IntRange(from=0) int index);
     method public final void refresh();
     method public final void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void removeOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public final void retry();
     method public final void setHasStableIds(boolean hasStableIds);
     method public final androidx.paging.ItemSnapshotList<T> snapshot();
@@ -118,6 +125,7 @@
     method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeader(androidx.paging.LoadStateAdapter<?> header);
     method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeaderAndFooter(androidx.paging.LoadStateAdapter<?> header, androidx.paging.LoadStateAdapter<?> footer);
     property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+    property public final kotlinx.coroutines.flow.Flow<kotlin.Unit> onPagesUpdatedFlow;
   }
 
   public final class PagingLiveData {
diff --git a/paging/runtime/api/restricted_current.txt b/paging/runtime/api/restricted_current.txt
index b886d2e..4fcb026 100644
--- a/paging/runtime/api/restricted_current.txt
+++ b/paging/runtime/api/restricted_current.txt
@@ -28,18 +28,22 @@
     ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
     ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
     method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public void addOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public T? getItem(@IntRange(from=0) int index);
     method public int getItemCount();
     method public kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getOnPagesUpdatedFlow();
     method public T? peek(@IntRange(from=0) int index);
     method public void refresh();
     method public void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public void removeOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public void retry();
     method public androidx.paging.ItemSnapshotList<T> snapshot();
     method public suspend Object? submitData(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     method public void submitData(androidx.lifecycle.Lifecycle lifecycle, androidx.paging.PagingData<T> pagingData);
     property public final int itemCount;
     property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+    property public final kotlinx.coroutines.flow.Flow<kotlin.Unit> onPagesUpdatedFlow;
   }
 
   @Deprecated public final class LivePagedListBuilder<Key, Value> {
@@ -102,13 +106,16 @@
     ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
     ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
     method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void addOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method protected final T? getItem(@IntRange(from=0) int position);
     method public int getItemCount();
     method public final long getItemId(int position);
     method public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getOnPagesUpdatedFlow();
     method public final T? peek(@IntRange(from=0) int index);
     method public final void refresh();
     method public final void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void removeOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public final void retry();
     method public final void setHasStableIds(boolean hasStableIds);
     method public final androidx.paging.ItemSnapshotList<T> snapshot();
@@ -118,6 +125,7 @@
     method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeader(androidx.paging.LoadStateAdapter<?> header);
     method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeaderAndFooter(androidx.paging.LoadStateAdapter<?> header, androidx.paging.LoadStateAdapter<?> footer);
     property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+    property public final kotlinx.coroutines.flow.Flow<kotlin.Unit> onPagesUpdatedFlow;
   }
 
   public final class PagingLiveData {
diff --git a/paging/runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt b/paging/runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
index 1ecdef3..18a7c6a6 100644
--- a/paging/runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
+++ b/paging/runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
@@ -261,6 +261,56 @@
     val loadStateFlow: Flow<CombinedLoadStates> = differBase.loadStateFlow
 
     /**
+     * A hot [Flow] that emits after the pages presented to the UI are updated, even if the
+     * actual items presented don't change.
+     *
+     * An update is triggered from one of the following:
+     *   * [submitData] is called and initial load completes, regardless of any differences in
+     *     the loaded data
+     *   * A [Page][androidx.paging.PagingSource.LoadResult.Page] is inserted
+     *   * A [Page][androidx.paging.PagingSource.LoadResult.Page] is dropped
+     *
+     * Note: This is a [SharedFlow][kotlinx.coroutines.flow.SharedFlow] configured to replay
+     * 0 items with a buffer of size 64. If a collector lags behind page updates, it may
+     * trigger multiple times for each intermediate update that was presented while your collector
+     * was still working. To avoid this behavior, you can
+     * [conflate][kotlinx.coroutines.flow.conflate] this [Flow] so that you only receive the latest
+     * update, which is useful in cases where you are simply updating UI and don't care about
+     * tracking the exact number of page updates.
+     */
+    val onPagesUpdatedFlow: Flow<Unit> = differBase.onPagesUpdatedFlow
+
+    /**
+     * Add a listener which triggers after the pages presented to the UI are updated, even if the
+     * actual items presented don't change.
+     *
+     * An update is triggered from one of the following:
+     *   * [submitData] is called and initial load completes, regardless of any differences in
+     *     the loaded data
+     *   * A [Page][androidx.paging.PagingSource.LoadResult.Page] is inserted
+     *   * A [Page][androidx.paging.PagingSource.LoadResult.Page] is dropped
+     *
+     * @param listener called after pages presented are updated.
+     *
+     * @see removeOnPagesUpdatedListener
+     */
+    fun addOnPagesUpdatedListener(listener: () -> Unit) {
+        differBase.addOnPagesUpdatedListener(listener)
+    }
+
+    /**
+     * Remove a previously registered listener for new [PagingData] generations completing
+     * initial load and presenting to the UI.
+     *
+     * @param listener Previously registered listener.
+     *
+     * @see addOnPagesUpdatedListener
+     */
+    fun removeOnPagesUpdatedListener(listener: () -> Unit) {
+        differBase.removeOnPagesUpdatedListener(listener)
+    }
+
+    /**
      * Add a [CombinedLoadStates] listener to observe the loading state of the current [PagingData].
      *
      * As new [PagingData] generations are submitted and displayed, the listener will be notified to
diff --git a/paging/runtime/src/main/java/androidx/paging/PagingDataAdapter.kt b/paging/runtime/src/main/java/androidx/paging/PagingDataAdapter.kt
index d29fe4f..1541f6b 100644
--- a/paging/runtime/src/main/java/androidx/paging/PagingDataAdapter.kt
+++ b/paging/runtime/src/main/java/androidx/paging/PagingDataAdapter.kt
@@ -261,6 +261,26 @@
     val loadStateFlow: Flow<CombinedLoadStates> = differ.loadStateFlow
 
     /**
+     * A hot [Flow] that emits after the pages presented to the UI are updated, even if the
+     * actual items presented don't change.
+     *
+     * An update is triggered from one of the following:
+     *   * [submitData] is called and initial load completes, regardless of any differences in
+     *     the loaded data
+     *   * A [Page][androidx.paging.PagingSource.LoadResult.Page] is inserted
+     *   * A [Page][androidx.paging.PagingSource.LoadResult.Page] is dropped
+     *
+     * Note: This is a [SharedFlow][kotlinx.coroutines.flow.SharedFlow] configured to replay
+     * 0 items with a buffer of size 64. If a collector lags behind page updates, it may
+     * trigger multiple times for each intermediate update that was presented while your collector
+     * was still working. To avoid this behavior, you can
+     * [conflate][kotlinx.coroutines.flow.conflate] this [Flow] so that you only receive the latest
+     * update, which is useful in cases where you are simply updating UI and don't care about
+     * tracking the exact number of page updates.
+     */
+    val onPagesUpdatedFlow: Flow<Unit> = differ.onPagesUpdatedFlow
+
+    /**
      * Add a [CombinedLoadStates] listener to observe the loading state of the current [PagingData].
      *
      * As new [PagingData] generations are submitted and displayed, the listener will be notified to
@@ -286,6 +306,36 @@
     }
 
     /**
+     * Add a listener which triggers after the pages presented to the UI are updated, even if the
+     * actual items presented don't change.
+     *
+     * An update is triggered from one of the following:
+     *   * [submitData] is called and initial load completes, regardless of any differences in
+     *     the loaded data
+     *   * A [Page][androidx.paging.PagingSource.LoadResult.Page] is inserted
+     *   * A [Page][androidx.paging.PagingSource.LoadResult.Page] is dropped
+     *
+     * @param listener called after pages presented are updated.
+     *
+     * @see removeOnPagesUpdatedListener
+     */
+    fun addOnPagesUpdatedListener(listener: () -> Unit) {
+        differ.addOnPagesUpdatedListener(listener)
+    }
+
+    /**
+     * Remove a previously registered listener for new [PagingData] generations completing
+     * initial load and presenting to the UI.
+     *
+     * @param listener Previously registered listener.
+     *
+     * @see addOnPagesUpdatedListener
+     */
+    fun removeOnPagesUpdatedListener(listener: () -> Unit) {
+        differ.removeOnPagesUpdatedListener(listener)
+    }
+
+    /**
      * Create a [ConcatAdapter] with the provided [LoadStateAdapter]s displaying the
      * [LoadType.PREPEND] [LoadState] as a list item at the end of the presented list.
      *
diff --git a/testutils/testutils-navigation/build.gradle b/testutils/testutils-navigation/build.gradle
index a5d1779..4123407 100644
--- a/testutils/testutils-navigation/build.gradle
+++ b/testutils/testutils-navigation/build.gradle
@@ -27,6 +27,7 @@
     testImplementation("androidx.arch.core:core-testing:2.1.0")
     testImplementation(libs.junit)
     testImplementation(libs.mockitoCore)
+    testImplementation(libs.truth)
 
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testCore)
diff --git a/testutils/testutils-navigation/src/test/java/androidx/testutils/TestNavigatorTest.kt b/testutils/testutils-navigation/src/test/java/androidx/testutils/TestNavigatorTest.kt
index ce8b38d..1756bce 100644
--- a/testutils/testutils-navigation/src/test/java/androidx/testutils/TestNavigatorTest.kt
+++ b/testutils/testutils-navigation/src/test/java/androidx/testutils/TestNavigatorTest.kt
@@ -19,7 +19,7 @@
 import android.os.Bundle
 import androidx.arch.core.executor.testing.InstantTaskExecutorRule
 import androidx.navigation.testing.TestNavigatorState
-import org.junit.Assert.assertEquals
+import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -39,19 +39,15 @@
         val destination = testNavigator.createDestination()
         val args = Bundle()
         testNavigator.navigate(listOf(state.createBackStackEntry(destination, args)), null, null)
-        assertEquals(
-            "TestNavigator back stack size is 1 after navigate",
-            1,
-            testNavigator.backStack.size
-        )
+        assertWithMessage("TestNavigator back stack size is 1 after navigate")
+            .that(testNavigator.backStack.size)
+            .isEqualTo(1)
         val current = testNavigator.current
-        assertEquals(
-            "last() returns last destination navigated to",
-            destination, current.destination
-        )
-        assertEquals(
-            "last() returns arguments Bundle",
-            args, current.arguments
-        )
+        assertWithMessage("last() returns last destination navigated to")
+            .that(current.destination)
+            .isEqualTo(destination)
+        assertWithMessage("last() returns arguments Bundle")
+            .that(current.arguments)
+            .isEqualTo(args)
     }
 }
diff --git a/wear/wear-watchface-data/proguard-rules.pro b/wear/wear-watchface-data/proguard-rules.pro
index ecdad1c..d114eed 100644
--- a/wear/wear-watchface-data/proguard-rules.pro
+++ b/wear/wear-watchface-data/proguard-rules.pro
@@ -18,3 +18,9 @@
 -keep public class android.support.wearable.complications.ComplicationData { *; }
 -keep public class android.support.wearable.complications.ComplicationProviderInfo  { *; }
 -keep public class android.support.wearable.complications.ComplicationText { *; }
+-keep public class android.support.wearable.complications.ComplicationTextTemplate  { *; }
+-keep public class android.support.wearable.complications.TimeDependentText { *; }
+-keep public class android.support.wearable.complications.TimeDifferenceText { *; }
+-keep public class android.support.wearable.complications.TimeFormatText { *; }
+
+
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index 5ab8463..36fe23a 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -994,6 +994,7 @@
                         idAndComplicationData.complicationData.toApiComplicationData()
                     )
                 }
+                watchFaceImpl.complicationSlotsManager.onComplicationsUpdated()
             } else {
                 // If the watchface hasn't been created yet, update pendingInitialComplications so
                 // it can be applied later.
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
index dacc639..4552e9f 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
@@ -147,4 +147,4 @@
             "InteractiveWatchFaceImpl.getUserStyleSchema"
         ) { watchFaceImpl -> watchFaceImpl.complicationSlotsManager.displayPressedAnimation(id) }
     }
-}
\ No newline at end of file
+}
diff --git a/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt b/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
index ba314ad..b764424 100644
--- a/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
+++ b/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
@@ -2696,6 +2696,59 @@
         ).isEqualTo("Example")
     }
 
+    @Test
+    public fun setComplicationDataList() {
+        initWallpaperInteractiveWatchFaceInstance(
+            WatchFaceType.ANALOG,
+            listOf(leftComplication, rightComplication),
+            UserStyleSchema(emptyList()),
+            WallpaperInteractiveWatchFaceInstanceParams(
+                "TestID",
+                DeviceConfig(
+                    false,
+                    false,
+                    0,
+                    0
+                ),
+                WatchUiState(false, 0),
+                UserStyle(emptyMap()).toWireFormat(),
+                emptyList()
+            )
+        )
+
+        val interactiveInstance = InteractiveInstanceManager.getAndRetainInstance("TestID")
+        interactiveInstance!!.updateComplicationData(
+            mutableListOf(
+                IdAndComplicationDataWireFormat(
+                    LEFT_COMPLICATION_ID,
+                    ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
+                        .setShortText(ComplicationText.plainText("LEFT!"))
+                        .build()
+                ),
+                IdAndComplicationDataWireFormat(
+                    RIGHT_COMPLICATION_ID,
+                    ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
+                        .setShortText(ComplicationText.plainText("RIGHT!"))
+                        .build()
+                )
+            )
+        )
+
+        assertThat(engineWrapper.contentDescriptionLabels.size).isEqualTo(3)
+        assertThat(
+            engineWrapper.contentDescriptionLabels[1].text.getTextAt(
+                ApplicationProvider.getApplicationContext<Context>().resources,
+                0
+            )
+        ).isEqualTo("LEFT!")
+        assertThat(
+            engineWrapper.contentDescriptionLabels[2].text.getTextAt(
+                ApplicationProvider.getApplicationContext<Context>().resources,
+                0
+            )
+        ).isEqualTo("RIGHT!")
+    }
+
     @Suppress("DEPRECATION")
     private fun getChinWindowInsetsApi25(@Px chinHeight: Int): WindowInsets =
         WindowInsets.Builder().setSystemWindowInsets(
diff --git a/wear/wear/src/androidTest/AndroidManifest.xml b/wear/wear/src/androidTest/AndroidManifest.xml
index c76bae4..00fc129 100644
--- a/wear/wear/src/androidTest/AndroidManifest.xml
+++ b/wear/wear/src/androidTest/AndroidManifest.xml
@@ -52,6 +52,13 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="androidx.wear.widget.drawer.DrawerRecyclerViewTestActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
         <activity android:name="androidx.wear.ambient.AmbientModeTestActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
diff --git a/wear/wear/src/androidTest/java/androidx/wear/widget/drawer/DrawerRecyclerViewTestActivity.java b/wear/wear/src/androidTest/java/androidx/wear/widget/drawer/DrawerRecyclerViewTestActivity.java
new file mode 100644
index 0000000..a1289b3
--- /dev/null
+++ b/wear/wear/src/androidTest/java/androidx/wear/widget/drawer/DrawerRecyclerViewTestActivity.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.widget.drawer;
+
+import android.app.Activity;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.wear.test.R;
+
+public class DrawerRecyclerViewTestActivity extends Activity {
+    private static final int DRAWER_SIZE = 5;
+    private static final int RECYCLER_SIZE = 5;
+
+    private final WearableNavigationDrawerView.WearableNavigationDrawerAdapter mDrawerAdapter =
+            new WearableNavigationDrawerView.WearableNavigationDrawerAdapter() {
+                @Override
+                public String getItemText(int pos) {
+                    return Integer.toString(pos);
+                }
+
+                @Override
+                public Drawable getItemDrawable(int pos) {
+                    return getDrawable(android.R.drawable.star_on);
+                }
+
+                @Override
+                public int getCount() {
+                    return DRAWER_SIZE;
+                }
+            };
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.test_drawer_horizontal_scroll);
+
+        WearableNavigationDrawerView navDrawer = findViewById(R.id.recycler_navigation_drawer);
+        navDrawer.setAdapter(mDrawerAdapter);
+
+        RecyclerView rv = findViewById(R.id.recycler);
+        rv.setAdapter(new ScrollAdapter());
+        LinearLayoutManager rvLayoutManager = new LinearLayoutManager(this);
+        rvLayoutManager.setOrientation(RecyclerView.HORIZONTAL);
+        rv.setLayoutManager(rvLayoutManager);
+    }
+
+    private class ScrollAdapter extends RecyclerView.Adapter<ScrollAdapter.ViewHolder> {
+        @NonNull
+        @Override
+        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+            return new ViewHolder(
+                    getLayoutInflater().inflate(
+                            R.layout.test_drawer_horizontal_scroll_fragment,
+                            parent,
+                            /* attachToRoot= */false));
+        }
+
+        @Override
+        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+            holder.mTextView.setText(Integer.toString(position));
+        }
+
+        @Override
+        public int getItemCount() {
+            return RECYCLER_SIZE;
+        }
+
+        private class ViewHolder extends RecyclerView.ViewHolder {
+            private final TextView mTextView;
+
+            ViewHolder(@NonNull View itemView) {
+                super(itemView);
+
+                mTextView = itemView.findViewById(R.id.drawer_recycler_fragment);
+            }
+        }
+    }
+}
diff --git a/wear/wear/src/androidTest/java/androidx/wear/widget/drawer/WearableDrawerLayoutEspressoTest.java b/wear/wear/src/androidTest/java/androidx/wear/widget/drawer/WearableDrawerLayoutEspressoTest.java
index 36f4a4a..0f61106 100644
--- a/wear/wear/src/androidTest/java/androidx/wear/widget/drawer/WearableDrawerLayoutEspressoTest.java
+++ b/wear/wear/src/androidTest/java/androidx/wear/widget/drawer/WearableDrawerLayoutEspressoTest.java
@@ -19,6 +19,7 @@
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.action.ViewActions.click;
 import static androidx.test.espresso.action.ViewActions.swipeDown;
+import static androidx.test.espresso.action.ViewActions.swipeLeft;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
 import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
 import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
@@ -48,6 +49,7 @@
 import android.widget.TextView;
 
 import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.core.app.ActivityScenario;
 import androidx.test.espresso.PerformException;
 import androidx.test.espresso.UiController;
 import androidx.test.espresso.ViewAction;
@@ -518,6 +520,22 @@
                 .perform(waitForMatchingView(withText("New Item"), MAX_WAIT_MS));
     }
 
+    @Test
+    public void flingInHorizontalScrollerDoesNotPeekDrawers() {
+        // Note that this test uses a different activity to implement the list...
+        ActivityScenario<DrawerRecyclerViewTestActivity> scenario =
+                ActivityScenario.launch(DrawerRecyclerViewTestActivity.class);
+
+        // Nothing should be peeking
+        onView(withId(R.id.recycler_navigation_drawer)).check(matches(isPeeking(false)));
+
+        onView(withId(R.id.recycler)).perform(swipeLeft());
+        onView(withId(R.id.recycler)).perform(waitForRecyclerToSettle(MAX_WAIT_MS));
+
+        // Drawers should not be peeking...
+        onView(withId(R.id.recycler_navigation_drawer)).check(matches(isPeeking(false)));
+    }
+
     private void scrollToPosition(final RecyclerView recyclerView, final int position) {
         recyclerView.post(new Runnable() {
             @Override
@@ -584,11 +602,15 @@
     }
 
     private TypeSafeMatcher<View> isPeeking() {
+        return isPeeking(true);
+    }
+
+    private TypeSafeMatcher<View> isPeeking(boolean peeking) {
         return new TypeSafeMatcher<View>() {
             @Override
             protected boolean matchesSafely(View view) {
                 WearableDrawerView drawer = (WearableDrawerView) view;
-                return drawer.isPeeking();
+                return drawer.isPeeking() == peeking;
             }
 
             @Override
@@ -697,6 +719,45 @@
         };
     }
 
+    public ViewAction waitForRecyclerToSettle(final long millis) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(RecyclerView.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "Waiting for Recycler ScrollState to be SCROLL_STATE_IDLE";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                if (!(view instanceof RecyclerView)) {
+                    return;
+                }
+
+                RecyclerView recycler = (RecyclerView) view;
+                uiController.loopMainThreadUntilIdle();
+                final long startTime = System.currentTimeMillis();
+                final long endTime = startTime + millis;
+                do {
+                    if (recycler.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) {
+                        return;
+                    }
+                    uiController.loopMainThreadForAtLeast(100); // at least 3 frames
+                } while (System.currentTimeMillis() < endTime);
+
+                // timeout happens
+                throw new PerformException.Builder()
+                        .withActionDescription(this.getDescription())
+                        .withViewDescription(HumanReadables.describe(view))
+                        .withCause(new TimeoutException())
+                        .build();
+            }
+        };
+    }
+
     /**
      * Returns the first child of {@code root} to be an instance of class {@code T}, or {@code null}
      * if none were found.
diff --git a/wear/wear/src/androidTest/res/layout/test_drawer_horizontal_scroll.xml b/wear/wear/src/androidTest/res/layout/test_drawer_horizontal_scroll.xml
new file mode 100644
index 0000000..62541c0
--- /dev/null
+++ b/wear/wear/src/androidTest/res/layout/test_drawer_horizontal_scroll.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  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.
+  -->
+
+<androidx.wear.widget.drawer.WearableDrawerLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/recycler_drawer_layout"
+    style="@style/DrawerLayoutStyle">
+
+    <androidx.wear.widget.drawer.WearableNavigationDrawerView
+        android:id="@+id/recycler_navigation_drawer"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@android:color/holo_red_dark"
+        app:navigationStyle="singlePage" />
+
+    <androidx.wear.widget.drawer.WearableActionDrawerView
+        android:id="@+id/recycler_action_drawer"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@android:color/holo_blue_dark"
+        app:actionMenu="@menu/action_drawer" />
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/recycler"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</androidx.wear.widget.drawer.WearableDrawerLayout>
diff --git a/wear/wear/src/androidTest/res/layout/test_drawer_horizontal_scroll_fragment.xml b/wear/wear/src/androidTest/res/layout/test_drawer_horizontal_scroll_fragment.xml
new file mode 100644
index 0000000..1572682
--- /dev/null
+++ b/wear/wear/src/androidTest/res/layout/test_drawer_horizontal_scroll_fragment.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  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.
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textColor="#FF000000"
+        android:id="@+id/drawer_recycler_fragment" />
+</FrameLayout>
diff --git a/wear/wear/src/main/java/androidx/wear/widget/drawer/WearableDrawerLayout.java b/wear/wear/src/main/java/androidx/wear/widget/drawer/WearableDrawerLayout.java
index 292f648..ff664f6 100644
--- a/wear/wear/src/main/java/androidx/wear/widget/drawer/WearableDrawerLayout.java
+++ b/wear/wear/src/main/java/androidx/wear/widget/drawer/WearableDrawerLayout.java
@@ -641,6 +641,13 @@
         boolean canScrollUp = view.canScrollVertically(UP);
         boolean canScrollDown = view.canScrollVertically(DOWN);
 
+        if (!canScrollUp && !canScrollDown) {
+            // The inner view isn't vertically scrollable, so this fling completion cannot have been
+            // fired from a vertical scroll. To prevent the peeks being shown after a horizontal
+            // scroll, bail out here.
+            return;
+        }
+
         if (canTopPeek && !canScrollUp && !mTopDrawerView.isPeeking()) {
             peekDrawer(Gravity.TOP);
         }