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);
}