Merge "Replace Jetpack's validation with platform's validation for Android U devices and above" into androidx-main
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 2af4575..d0cee53 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -9,14 +9,13 @@
/appcompat/ @alanv
/biometric/ @jbolinger
/collection/ @dlam
-/compose/compiler/ @jimgoog @lelandrichardson
-/compose/runtime/ @jimgoog @lelandrichardson
+/compose/compiler/ @lelandrichardson
+/compose/runtime/ @lelandrichardson
/core/ @alanv
/datastore/ @rohitsat13 @yigit
/fragment/ @jbw0033 @ianhanniballake
/lifecycle/ @jbw0033 @ianhanniballake
/navigation/ @jbw0033 @ianhanniballake @claraf3
/paging/ @claraf3 @ianhanniballake
-/room/ @droid-wan-kenobi @danysantiago @svasilinets
-/work/ @svasilinets @tikurahul
-
+/room/ @droid-wan-kenobi @danysantiago
+/work/ @danysantiago @tikurahul
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 8f005d6..bccc040 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -141,6 +141,9 @@
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
+ <inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
+ <option name="composableFile" value="true" />
+ </inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
diff --git a/TEXT_OWNERS b/TEXT_OWNERS
index d7194e2..86d68c4 100644
--- a/TEXT_OWNERS
+++ b/TEXT_OWNERS
@@ -1,7 +1,6 @@
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
diff --git a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/KotlinAnnotationsDetectorTest.kt b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/KotlinAnnotationsDetectorTest.kt
index 865af3a..d37793f 100644
--- a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/KotlinAnnotationsDetectorTest.kt
+++ b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/KotlinAnnotationsDetectorTest.kt
@@ -163,8 +163,6 @@
javaSample("sample.kotlin.UseKtExperimentalFromJava")
)
- // TODO(b/210881073): Access to annotated property `field` is still not detected.
-
val expected =
"""
src/sample/kotlin/UseKtExperimentalFromJava.java:28: Error: This declaration is opt-in and its usage should be marked with @sample.kotlin.ExperimentalKotlinAnnotation or @OptIn(markerClass = sample.kotlin.ExperimentalKotlinAnnotation.class) [UnsafeOptInUsageError]
diff --git a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt
index c2c6356..25a9639 100644
--- a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt
+++ b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt
@@ -229,8 +229,6 @@
javaSample("sample.optin.UseKtExperimentalFromJava")
)
- // TODO(b/210881073): Access to annotated property `field` is still not detected.
-
val expected =
"""
src/sample/optin/UseKtExperimentalFromJava.java:28: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalKotlinAnnotation or @OptIn(markerClass = sample.optin.ExperimentalKotlinAnnotation.class) [UnsafeOptInUsageError]
diff --git a/appfunctions/appfunctions-common/api/current.txt b/appfunctions/appfunctions-common/api/current.txt
index 724d892f..e9789a2 100644
--- a/appfunctions/appfunctions-common/api/current.txt
+++ b/appfunctions/appfunctions-common/api/current.txt
@@ -56,5 +56,8 @@
property public static final int ERROR_SYSTEM_ERROR;
}
+ @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget.CLASS) public @interface AppFunctionSerializable {
+ }
+
}
diff --git a/appfunctions/appfunctions-common/api/restricted_current.txt b/appfunctions/appfunctions-common/api/restricted_current.txt
index 724d892f..e9789a2 100644
--- a/appfunctions/appfunctions-common/api/restricted_current.txt
+++ b/appfunctions/appfunctions-common/api/restricted_current.txt
@@ -56,5 +56,8 @@
property public static final int ERROR_SYSTEM_ERROR;
}
+ @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget.CLASS) public @interface AppFunctionSerializable {
+ }
+
}
diff --git a/appfunctions/appfunctions-common/src/main/java/androidx/appfunctions/AppFunctionSerializable.kt b/appfunctions/appfunctions-common/src/main/java/androidx/appfunctions/AppFunctionSerializable.kt
new file mode 100644
index 0000000..4688942
--- /dev/null
+++ b/appfunctions/appfunctions-common/src/main/java/androidx/appfunctions/AppFunctionSerializable.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2025 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.appfunctions
+
+/**
+ * Annotates a class to indicate that it can be serialized and transferred between processes using
+ * AppFunction.
+ *
+ * When a class is annotated with `@AppFunctionSerializable` and is used as a parameter or return
+ * type (directly or as a nested entity) in an AppFunction, the shape of the entity defined within
+ * its primary constructor will be exposed to the caller as an
+ * [androidx.appfunctions.metadata.AppFunctionMetadata]. This information allows the caller to
+ * construct the structure input to call an AppFunction or understand what properties are provided
+ * in the structured output.
+ *
+ * **Constraints for Classes Annotated with `@AppFunctionSerializable`:**
+ * * **Primary Constructor Parameters:** Only properties declared in the primary constructor that
+ * expose a getter method are eligible for inclusion in the AppFunctionMetadata. Critically, it is
+ * a **requirement** to place properties with a getter in primary constructor. Attempting to
+ * include constructor parameters that are not declared as properties (and thus don't have a
+ * getter) will result in a compiler error.
+ * * **Supported Property Types:** The properties in the primary constructor must be of one of the
+ * following types:
+ * * `Int`
+ * * `Long`
+ * * `Float`
+ * * `Double`
+ * * `Boolean`
+ * * `String`
+ * * `IntArray`
+ * * `LongArray`
+ * * `FloatArray`
+ * * `DoubleArray`
+ * * `BooleanArray`
+ * * `ByteArray`
+ * * `List<String>`
+ * * Another class annotated with `@AppFunctionSerializable` (enabling nested structures) or a
+ * list of a class annotated with `@AppFunctionSerializable`
+ * * **Public Primary Constructor:** The primary constructor of the annotated class must have public
+ * visibility to allow instantiation.
+ * * **
+ *
+ * **IMPORTANT:** The default value set in the constructor parameter is ignored when the value is
+ * missing from the function calling request. Instead, the specified rule is always used:
+ * * **Non-nullable Properties:** If a value is missing for a non-nullable property from the caller,
+ * the library will throw an exception to notify the caller automatically.
+ * * **Nullable Properties:** If a value is missing for a nullable property, `null` will be used.
+ * * **Collection Properties:** If a value is missing for a collection property, an empty collection
+ * will be used.
+ *
+ * **Example:**
+ *
+ * ```
+ * @AppFunctionSerializable
+ * class Location(val latitude: Double, val longitude: Double)
+ *
+ * @AppFunctionSerializable
+ * class Place(
+ * val name: String,
+ * val location: Location, // Nested AppFunctionSerializable
+ * // Nullable String is allowed, if missing, will be null. The default value will not be used
+ * // when the value is missing
+ * val notes: String? = "default"
+ * )
+ *
+ * @AppFunctionSerializable
+ * class SearchPlaceResult(
+ * val places: List<Place> // If missing, will be an empty list
+ * )
+ *
+ * @AppFunctionSerializable
+ * class Attachment(
+ * uri: String // Putting constructor parameter without getter will result in compiler error
+ * )
+ * ```
+ */
+@Retention(AnnotationRetention.SOURCE)
+@Target(AnnotationTarget.CLASS)
+public annotation class AppFunctionSerializable
diff --git a/appfunctions/appfunctions-compiler/build.gradle b/appfunctions/appfunctions-compiler/build.gradle
index bc0e999..8a70d79 100644
--- a/appfunctions/appfunctions-compiler/build.gradle
+++ b/appfunctions/appfunctions-compiler/build.gradle
@@ -36,6 +36,7 @@
implementation(libs.kspApi)
implementation(libs.kotlinPoet)
+ testImplementationAarAsJar(project(":appfunctions:appfunctions-common"))
testImplementationAarAsJar(project(":appfunctions:appfunctions-runtime"))
testImplementation(libs.googleCompileTesting)
testImplementation(project(":room:room-compiler-processing-testing"))
diff --git a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/AppFunctionCompiler.kt b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/AppFunctionCompiler.kt
index 81f6291..6151d45 100644
--- a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/AppFunctionCompiler.kt
+++ b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/AppFunctionCompiler.kt
@@ -19,6 +19,7 @@
import androidx.appfunctions.compiler.core.ProcessingException
import androidx.appfunctions.compiler.core.logException
import androidx.appfunctions.compiler.processors.AppFunctionIdProcessor
+import androidx.appfunctions.compiler.processors.AppFunctionInventoryProcessor
import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
@@ -51,10 +52,9 @@
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
val idProcessor = AppFunctionIdProcessor(environment.codeGenerator)
+ val inventoryProcessor = AppFunctionInventoryProcessor(environment.codeGenerator)
return AppFunctionCompiler(
- listOf(
- idProcessor,
- ),
+ listOf(idProcessor, inventoryProcessor),
environment.logger,
)
}
diff --git a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/AppFunctionSymbolResolver.kt b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/AppFunctionSymbolResolver.kt
index 5e55efef..2bac5ac 100644
--- a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/AppFunctionSymbolResolver.kt
+++ b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/AppFunctionSymbolResolver.kt
@@ -16,6 +16,7 @@
package androidx.appfunctions.compiler.core
+import androidx.appfunctions.compiler.core.IntrospectionHelper.APP_FUNCTION_CONTEXT_CLASS
import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionAnnotation
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSClassDeclaration
@@ -72,10 +73,35 @@
val appFunctionDeclarations: List<KSFunctionDeclaration>
) {
fun validate(): AnnotatedAppFunctions {
- // TODO: Validate the function is valid
+ validateFirstParameter()
+ validateParameterTypes()
return this
}
+ private fun validateFirstParameter() {
+ for (appFunctionDeclaration in appFunctionDeclarations) {
+ val firstParam = appFunctionDeclaration.parameters.firstOrNull()
+ if (firstParam == null) {
+ throw ProcessingException(
+ "The first parameter of an app function must be " +
+ "$APP_FUNCTION_CONTEXT_CLASS",
+ appFunctionDeclaration
+ )
+ }
+ if (!firstParam.type.isOfType(APP_FUNCTION_CONTEXT_CLASS)) {
+ throw ProcessingException(
+ "The first parameter of an app function must be " +
+ "$APP_FUNCTION_CONTEXT_CLASS",
+ firstParam
+ )
+ }
+ }
+ }
+
+ private fun validateParameterTypes() {
+ // TODO: Validate that the parameter type used by the app functions are supported
+ }
+
/**
* Gets the identifier of an app functions.
*
diff --git a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/IntrospectionHelper.kt b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/IntrospectionHelper.kt
index 7c0fe98..fe0d3fa 100644
--- a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/IntrospectionHelper.kt
+++ b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/IntrospectionHelper.kt
@@ -22,9 +22,18 @@
object IntrospectionHelper {
// Package names
private const val APP_FUNCTIONS_PACKAGE_NAME = "androidx.appfunctions"
+ private const val APP_FUNCTIONS_INTERNAL_PACKAGE_NAME = "androidx.appfunctions.internal"
+ private const val APP_FUNCTIONS_METADATA_PACKAGE_NAME = "androidx.appfunctions.metadata"
// Annotation classes
object AppFunctionAnnotation {
val CLASS_NAME = ClassName(APP_FUNCTIONS_PACKAGE_NAME, "AppFunction")
}
+
+ // Classes
+ val APP_FUNCTION_CONTEXT_CLASS = ClassName(APP_FUNCTIONS_PACKAGE_NAME, "AppFunctionContext")
+ val APP_FUNCTION_INVENTORY_CLASS =
+ ClassName(APP_FUNCTIONS_INTERNAL_PACKAGE_NAME, "AppFunctionInventory")
+ val APP_FUNCTION_METADATA_CLASS =
+ ClassName(APP_FUNCTIONS_METADATA_PACKAGE_NAME, "AppFunctionMetadata")
}
diff --git a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/KspUtils.kt b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/KspUtils.kt
new file mode 100644
index 0000000..1fa4707
--- /dev/null
+++ b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/KspUtils.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2025 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.appfunctions.compiler.core
+
+import com.google.devtools.ksp.symbol.KSTypeReference
+import com.squareup.kotlinpoet.ClassName
+
+/**
+ * Checks if the type reference is of the given type.
+ *
+ * @param type the type to check against
+ * @return true if the type reference is of the given type
+ * @throws ProcessingException If unable to resolve the type.
+ */
+fun KSTypeReference.isOfType(type: ClassName): Boolean {
+ val ksType = this.resolve()
+ val typeName =
+ ksType.declaration.qualifiedName
+ ?: throw ProcessingException(
+ "Unable to resolve the type to check if it is of type [${type}]",
+ this
+ )
+ return typeName.asString() == type.canonicalName
+}
diff --git a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/processors/AppFunctionInventoryProcessor.kt b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/processors/AppFunctionInventoryProcessor.kt
new file mode 100644
index 0000000..cedc597
--- /dev/null
+++ b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/processors/AppFunctionInventoryProcessor.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2025 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.appfunctions.compiler.processors
+
+import androidx.appfunctions.compiler.AppFunctionCompiler
+import androidx.appfunctions.compiler.core.AppFunctionSymbolResolver
+import androidx.appfunctions.compiler.core.AppFunctionSymbolResolver.AnnotatedAppFunctions
+import androidx.appfunctions.compiler.core.IntrospectionHelper
+import com.google.devtools.ksp.processing.CodeGenerator
+import com.google.devtools.ksp.processing.Dependencies
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.processing.SymbolProcessor
+import com.google.devtools.ksp.symbol.KSAnnotated
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.buildCodeBlock
+
+/**
+ * Generates implementations for the AppFunctionInventory interface.
+ *
+ * It resolves all functions in a class annotated with `@AppFunction`, and generates the
+ * corresponding metadata for those functions.
+ */
+class AppFunctionInventoryProcessor(
+ private val codeGenerator: CodeGenerator,
+) : SymbolProcessor {
+ override fun process(resolver: Resolver): List<KSAnnotated> {
+ val appFunctionSymbolResolver = AppFunctionSymbolResolver(resolver)
+ val appFunctionClasses = appFunctionSymbolResolver.resolveAnnotatedAppFunctions()
+ for (appFunctionClass in appFunctionClasses) {
+ generateAppFunctionInventoryClass(appFunctionClass)
+ }
+ return emptyList()
+ }
+
+ private fun generateAppFunctionInventoryClass(appFunctionClass: AnnotatedAppFunctions) {
+ val originalPackageName = appFunctionClass.classDeclaration.packageName.asString()
+ val originalClassName = appFunctionClass.classDeclaration.simpleName.asString()
+
+ val inventoryClassName = getAppFunctionInventoryClassName(originalClassName)
+ val inventoryClassBuilder = TypeSpec.classBuilder(inventoryClassName)
+ inventoryClassBuilder.addSuperinterface(IntrospectionHelper.APP_FUNCTION_INVENTORY_CLASS)
+ inventoryClassBuilder.addAnnotation(AppFunctionCompiler.GENERATED_ANNOTATION)
+ inventoryClassBuilder.addProperty(buildFunctionIdToMetadataMapProperty())
+
+ val fileSpec =
+ FileSpec.builder(originalPackageName, inventoryClassName)
+ .addType(inventoryClassBuilder.build())
+ .build()
+ codeGenerator
+ .createNewFile(
+ Dependencies(
+ aggregating = false,
+ checkNotNull(appFunctionClass.classDeclaration.containingFile)
+ ),
+ originalPackageName,
+ inventoryClassName
+ )
+ .bufferedWriter()
+ .use { fileSpec.writeTo(it) }
+ }
+
+ /** Creates the `functionIdToMetadataMap` property of the `AppFunctionInventory`. */
+ private fun buildFunctionIdToMetadataMapProperty(): PropertySpec {
+ return PropertySpec.builder(
+ "functionIdToMetadataMap",
+ Map::class.asClassName()
+ .parameterizedBy(
+ String::class.asClassName(),
+ IntrospectionHelper.APP_FUNCTION_METADATA_CLASS
+ ),
+ )
+ .addModifiers(KModifier.OVERRIDE)
+ // TODO: Actually build map properties
+ .initializer(buildCodeBlock { addStatement("mapOf()") })
+ .build()
+ }
+
+ private fun getAppFunctionInventoryClassName(functionClassName: String): String {
+ return "$%s_AppFunctionInventory_Impl".format(functionClassName)
+ }
+}
diff --git a/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/AppFunctionCompilerTest.kt b/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/AppFunctionCompilerTest.kt
index 9c6b856..8860052 100644
--- a/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/AppFunctionCompilerTest.kt
+++ b/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/AppFunctionCompilerTest.kt
@@ -43,7 +43,7 @@
}
@Test
- fun testSimpleFunction() {
+ fun testSimpleFunction_genAppFunctionIds_success() {
val report = compilationTestHelper.compileAll(sourceFileNames = listOf("SimpleFunction.KT"))
compilationTestHelper.assertSuccessWithContent(
@@ -52,4 +52,43 @@
goldenFileName = "SimpleFunctionIds.KT"
)
}
+
+ @Test
+ fun testMissingFirstParameter_hasCompileError() {
+ val report =
+ compilationTestHelper.compileAll(sourceFileNames = listOf("MissingFirstParameter.KT"))
+
+ compilationTestHelper.assertErrorWithMessage(
+ report,
+ "The first parameter of an app function must be " +
+ "androidx.appfunctions.AppFunctionContext\n" +
+ " fun missingFirstParameter() {}\n" +
+ " ^"
+ )
+ }
+
+ @Test
+ fun testIncorrectFirstParameter_hasCompileError() {
+ val report =
+ compilationTestHelper.compileAll(sourceFileNames = listOf("IncorrectFirstParameter.KT"))
+
+ compilationTestHelper.assertErrorWithMessage(
+ report,
+ "The first parameter of an app function must be " +
+ "androidx.appfunctions.AppFunctionContext\n" +
+ " fun incorrectFirstParameter(x: Int) {}\n" +
+ " ^"
+ )
+ }
+
+ @Test
+ fun testSimpleFunction_genAppFunctionInventoryImpl_success() {
+ val report = compilationTestHelper.compileAll(sourceFileNames = listOf("SimpleFunction.KT"))
+
+ compilationTestHelper.assertSuccessWithContent(
+ report = report,
+ expectGeneratedFileName = "SimpleFunction_AppFunctionInventory_Impl.kt",
+ goldenFileName = "$%s".format("SimpleFunction_AppFunctionInventory_Impl.KT")
+ )
+ }
}
diff --git a/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/testings/CompilationTestHelper.kt b/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/testings/CompilationTestHelper.kt
index a616921..4e6253d 100644
--- a/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/testings/CompilationTestHelper.kt
+++ b/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/testings/CompilationTestHelper.kt
@@ -136,6 +136,30 @@
.isEqualTo(goldenFile.readText())
}
+ fun assertErrorWithMessage(report: CompilationReport, expectedErrorMessage: String) {
+ Truth.assertWithMessage("Compile succeed").that(report.isSuccess).isFalse()
+
+ val errorDiagnostics = report.diagnostics[Diagnostic.Kind.ERROR] ?: emptyList()
+ var foundError = false
+ for (errorDiagnostic in errorDiagnostics) {
+ if (errorDiagnostic.msg.contains(expectedErrorMessage)) {
+ foundError = true
+ break
+ }
+ }
+ Truth.assertWithMessage(
+ """
+ Unable to find the expected error message [$expectedErrorMessage] from the
+ diagnostics results:
+
+ ${report.printDiagnostics(Diagnostic.Kind.ERROR)}
+ """
+ .trimIndent()
+ )
+ .that(foundError)
+ .isTrue()
+ }
+
private fun ensureKotlinFileNameFormat(sourceFileName: String): String {
val nameParts = sourceFileName.split(".")
require(nameParts.last().lowercase() == "kt") {
diff --git a/appfunctions/appfunctions-compiler/src/test/test-data/input/IncorrectFirstParameter.KT b/appfunctions/appfunctions-compiler/src/test/test-data/input/IncorrectFirstParameter.KT
new file mode 100644
index 0000000..36f59b3
--- /dev/null
+++ b/appfunctions/appfunctions-compiler/src/test/test-data/input/IncorrectFirstParameter.KT
@@ -0,0 +1,8 @@
+package com.testdata
+
+import androidx.appfunctions.AppFunction
+
+class IncorrectFirstParameter {
+ @AppFunction
+ fun incorrectFirstParameter(x: Int) {}
+}
\ No newline at end of file
diff --git a/appfunctions/appfunctions-compiler/src/test/test-data/input/MissingFirstParameter.KT b/appfunctions/appfunctions-compiler/src/test/test-data/input/MissingFirstParameter.KT
new file mode 100644
index 0000000..0216c0f
--- /dev/null
+++ b/appfunctions/appfunctions-compiler/src/test/test-data/input/MissingFirstParameter.KT
@@ -0,0 +1,8 @@
+package com.testdata
+
+import androidx.appfunctions.AppFunction
+
+class MissingFirstParameter {
+ @AppFunction
+ fun missingFirstParameter() {}
+}
\ No newline at end of file
diff --git a/appfunctions/appfunctions-compiler/src/test/test-data/input/SimpleFunction.KT b/appfunctions/appfunctions-compiler/src/test/test-data/input/SimpleFunction.KT
index f2a27d5..2717184 100644
--- a/appfunctions/appfunctions-compiler/src/test/test-data/input/SimpleFunction.KT
+++ b/appfunctions/appfunctions-compiler/src/test/test-data/input/SimpleFunction.KT
@@ -1,8 +1,9 @@
package com.testdata
import androidx.appfunctions.AppFunction
+import androidx.appfunctions.AppFunctionContext
class SimpleFunction {
@AppFunction
- fun simpleFunction() {}
+ fun simpleFunction(appFunctionContext: AppFunctionContext) {}
}
\ No newline at end of file
diff --git a/appfunctions/appfunctions-compiler/src/test/test-data/output/$SimpleFunction_AppFunctionInventory_Impl.KT b/appfunctions/appfunctions-compiler/src/test/test-data/output/$SimpleFunction_AppFunctionInventory_Impl.KT
new file mode 100644
index 0000000..e7653db3
--- /dev/null
+++ b/appfunctions/appfunctions-compiler/src/test/test-data/output/$SimpleFunction_AppFunctionInventory_Impl.KT
@@ -0,0 +1,12 @@
+package com.testdata
+
+import androidx.appfunctions.`internal`.AppFunctionInventory
+import androidx.appfunctions.metadata.AppFunctionMetadata
+import javax.`annotation`.processing.Generated
+import kotlin.String
+import kotlin.collections.Map
+
+@Generated("androidx.appfunctions.compiler.AppFunctionCompiler")
+public class `$SimpleFunction_AppFunctionInventory_Impl` : AppFunctionInventory {
+ override val functionIdToMetadataMap: Map<String, AppFunctionMetadata> = mapOf()
+}
diff --git a/appfunctions/appfunctions-runtime/build.gradle b/appfunctions/appfunctions-runtime/build.gradle
index 3b37e08..a95d75a 100644
--- a/appfunctions/appfunctions-runtime/build.gradle
+++ b/appfunctions/appfunctions-runtime/build.gradle
@@ -35,6 +35,9 @@
// Internal dependencies
implementation("androidx.annotation:annotation:1.9.0-rc01")
implementation project(":appfunctions:appfunctions-common")
+
+ testImplementation(libs.junit)
+ testImplementation(libs.truth)
}
android {
diff --git a/appfunctions/appfunctions-runtime/src/main/java/androidx/appfunctions/internal/AggregateAppFunctionInventory.kt b/appfunctions/appfunctions-runtime/src/main/java/androidx/appfunctions/internal/AggregateAppFunctionInventory.kt
new file mode 100644
index 0000000..6f792de
--- /dev/null
+++ b/appfunctions/appfunctions-runtime/src/main/java/androidx/appfunctions/internal/AggregateAppFunctionInventory.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2025 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.appfunctions.internal
+
+import androidx.annotation.RestrictTo
+import androidx.appfunctions.metadata.AppFunctionMetadata
+
+/**
+ * An [AppFunctionInventory] that aggregates the function metadata from multiple
+ * [AppFunctionInventory] instances.
+ *
+ * AppFunction compiler will automatically generate the implementation of this class to access all
+ * generated [AppFunctionMetadata] exposed by the application.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public abstract class AggregateAppFunctionInventory : AppFunctionInventory {
+
+ /** The list of [AppFunctionInventory] instances that contribute to this aggregate. */
+ public abstract val inventories: List<AppFunctionInventory>
+
+ final override val functionIdToMetadataMap: Map<String, AppFunctionMetadata> by lazy {
+ // Empty collection can't be reduced
+ if (inventories.isEmpty()) return@lazy emptyMap()
+ inventories.map(AppFunctionInventory::functionIdToMetadataMap).reduce { acc, map ->
+ acc + map
+ }
+ }
+}
diff --git a/appfunctions/appfunctions-runtime/src/main/java/androidx/appfunctions/internal/AppFunctionInvoker.kt b/appfunctions/appfunctions-runtime/src/main/java/androidx/appfunctions/internal/AppFunctionInvoker.kt
new file mode 100644
index 0000000..59c4bca
--- /dev/null
+++ b/appfunctions/appfunctions-runtime/src/main/java/androidx/appfunctions/internal/AppFunctionInvoker.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2025 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.appfunctions.internal
+
+import androidx.annotation.RestrictTo
+import androidx.appfunctions.AppFunctionContext
+
+/**
+ * An interface for invoking app functions.
+ *
+ * This interface defines a contract for invoking a set of AppFunctions declared in an application.
+ * Each AppFunction implementation class has a corresponding generated Invoker class that implements
+ * this interface.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public interface AppFunctionInvoker {
+ /**
+ * Sets of function ids that the invoker supports.
+ *
+ * For example, consider the following AppFunction implementation class:
+ * ```kotlin
+ * class NoteFunctions : CreateNote, EditNote {
+ * @AppFunction
+ * override suspend fun createNote() : Note { ... }
+ *
+ * @AppFunction
+ * override suspend fun editNote() : Note { ... }
+ * }
+ * ```
+ *
+ * The set of supported Ids would include the respective function id for the `createNote` and
+ * `editNote` functions.
+ */
+ public val supportedFunctionIds: Set<String>
+
+ /**
+ * Invokes an AppFunction identified by [functionIdentifier], with [parameters].
+ *
+ * @throws [androidx.appfunctions.AppFunctionException] with error code
+ * [androidx.appfunctions.AppFunctionException.ERROR_FUNCTION_NOT_FOUND] if called with
+ * invalid function identifier or code
+ * [androidx.appfunctions.AppFunctionException.ERROR_INVALID_ARGUMENT] if called with invalid
+ * parameters.
+ */
+ public suspend fun unsafeInvoke(
+ appFunctionContext: AppFunctionContext,
+ functionIdentifier: String,
+ parameters: Map<String, Any?>,
+ ): Any?
+}
diff --git a/appfunctions/appfunctions-runtime/src/test/java/androidx/appfunctions/internal/AggregateAppFunctionInventoryTest.kt b/appfunctions/appfunctions-runtime/src/test/java/androidx/appfunctions/internal/AggregateAppFunctionInventoryTest.kt
new file mode 100644
index 0000000..da0da07
--- /dev/null
+++ b/appfunctions/appfunctions-runtime/src/test/java/androidx/appfunctions/internal/AggregateAppFunctionInventoryTest.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2025 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.appfunctions.internal
+
+import androidx.appfunctions.metadata.AppFunctionComponentsMetadata
+import androidx.appfunctions.metadata.AppFunctionDataTypeMetadata
+import androidx.appfunctions.metadata.AppFunctionMetadata
+import androidx.appfunctions.metadata.AppFunctionResponseMetadata
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class AggregateAppFunctionInventoryTest {
+
+ @Test
+ fun testEmpty() {
+ val aggregateInventory =
+ object : AggregateAppFunctionInventory() {
+ override val inventories: List<AppFunctionInventory> = emptyList()
+ }
+
+ assertThat(aggregateInventory.functionIdToMetadataMap).hasSize(0)
+ }
+
+ @Test
+ fun testUniqueInventories() {
+ val aggregateInventory =
+ object : AggregateAppFunctionInventory() {
+ override val inventories: List<AppFunctionInventory> =
+ listOf(Inventory1(), Inventory2())
+ }
+
+ assertThat(aggregateInventory.functionIdToMetadataMap).hasSize(2)
+ assertThat(aggregateInventory.functionIdToMetadataMap.keys)
+ .containsExactly(
+ "androix.appfunctions.internal#test1",
+ "androix.appfunctions.internal#test2"
+ )
+ }
+
+ @Test
+ fun testDuplicatedInventories() {
+ val aggregateInventory =
+ object : AggregateAppFunctionInventory() {
+ override val inventories: List<AppFunctionInventory> =
+ listOf(Inventory1(), Inventory1())
+ }
+
+ assertThat(aggregateInventory.functionIdToMetadataMap).hasSize(1)
+ assertThat(aggregateInventory.functionIdToMetadataMap.keys)
+ .containsExactly(
+ "androix.appfunctions.internal#test1",
+ )
+ }
+
+ private class Inventory1 : AppFunctionInventory {
+ override val functionIdToMetadataMap: Map<String, AppFunctionMetadata> =
+ mapOf(
+ "androix.appfunctions.internal#test1" to
+ AppFunctionMetadata(
+ id = "androix.appfunctions.internal#test1",
+ isEnabledByDefault = false,
+ isRestrictToTrustedCaller = false,
+ displayNameRes = 0,
+ schema = null,
+ parameters = emptyList(),
+ response =
+ AppFunctionResponseMetadata(
+ isNullable = false,
+ dataType =
+ AppFunctionDataTypeMetadata(
+ type = AppFunctionDataTypeMetadata.UNIT
+ )
+ ),
+ components = AppFunctionComponentsMetadata(dataTypes = emptyList())
+ )
+ )
+ }
+
+ private class Inventory2 : AppFunctionInventory {
+ override val functionIdToMetadataMap: Map<String, AppFunctionMetadata> =
+ mapOf(
+ "androix.appfunctions.internal#test2" to
+ AppFunctionMetadata(
+ id = "androix.appfunctions.internal#test2",
+ isEnabledByDefault = false,
+ isRestrictToTrustedCaller = false,
+ displayNameRes = 0,
+ schema = null,
+ parameters = emptyList(),
+ response =
+ AppFunctionResponseMetadata(
+ isNullable = false,
+ dataType =
+ AppFunctionDataTypeMetadata(
+ type = AppFunctionDataTypeMetadata.UNIT
+ )
+ ),
+ components = AppFunctionComponentsMetadata(dataTypes = emptyList())
+ )
+ )
+ }
+}
diff --git a/appsearch/appsearch-builtin-types/api/1.1.0-beta02.txt b/appsearch/appsearch-builtin-types/api/1.1.0-beta02.txt
new file mode 100644
index 0000000..eb040cf
--- /dev/null
+++ b/appsearch/appsearch-builtin-types/api/1.1.0-beta02.txt
@@ -0,0 +1,465 @@
+// Signature format: 4.0
+package androidx.appsearch.app {
+
+ @SuppressCompatibility @RequiresApi(api=android.os.Build.VERSION_CODES.LOLLIPOP) @androidx.appsearch.app.ExperimentalAppSearchApi public class ShortcutAdapter {
+ method public static androidx.core.content.pm.ShortcutInfoCompat.Builder createShortcutBuilderFromDocument(android.content.Context, Object) throws androidx.appsearch.exceptions.AppSearchException;
+ method public static android.net.Uri getDocumentUri(Object) throws androidx.appsearch.exceptions.AppSearchException;
+ method public static android.net.Uri getDocumentUri(String);
+ field public static final String DEFAULT_NAMESPACE = "__shortcut_adapter_ns__";
+ }
+
+}
+
+package androidx.appsearch.builtintypes {
+
+ @androidx.appsearch.annotation.Document(name="builtin:Alarm") public class Alarm extends androidx.appsearch.builtintypes.Thing {
+ method public String? getBlackoutPeriodEndDate();
+ method public String? getBlackoutPeriodStartDate();
+ method public int[]? getDaysOfWeek();
+ method @IntRange(from=0, to=23) public int getHour();
+ method @IntRange(from=0, to=59) public int getMinute();
+ method public androidx.appsearch.builtintypes.AlarmInstance? getNextInstance();
+ method public int getOriginatingDevice();
+ method public androidx.appsearch.builtintypes.AlarmInstance? getPreviousInstance();
+ method public String? getRingtone();
+ method public boolean isEnabled();
+ method public boolean shouldVibrate();
+ field public static final int ORIGINATING_DEVICE_SMART_PHONE = 1; // 0x1
+ field public static final int ORIGINATING_DEVICE_SMART_WATCH = 2; // 0x2
+ field public static final int ORIGINATING_DEVICE_UNKNOWN = 0; // 0x0
+ }
+
+ @androidx.appsearch.annotation.Document.BuilderProducer public static final class Alarm.Builder {
+ ctor public Alarm.Builder(androidx.appsearch.builtintypes.Alarm);
+ ctor public Alarm.Builder(String, String);
+ method public androidx.appsearch.builtintypes.Alarm.Builder addAlternateName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Alarm.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.Alarm build();
+ method public androidx.appsearch.builtintypes.Alarm.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Alarm.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.Alarm.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setBlackoutPeriodEndDate(String?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setBlackoutPeriodStartDate(String?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setDaysOfWeek(@IntRange(from=java.util.Calendar.SUNDAY, to=java.util.Calendar.SATURDAY) int...?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setDocumentTtlMillis(long);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setEnabled(boolean);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setHour(@IntRange(from=0, to=23) int);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Alarm.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setMinute(@IntRange(from=0, to=59) int);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setName(String?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setNextInstance(androidx.appsearch.builtintypes.AlarmInstance?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setOriginatingDevice(int);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Alarm.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setPreviousInstance(androidx.appsearch.builtintypes.AlarmInstance?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setRingtone(String?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setShouldVibrate(boolean);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setUrl(String?);
+ }
+
+ @androidx.appsearch.annotation.Document(name="builtin:AlarmInstance") public class AlarmInstance extends androidx.appsearch.builtintypes.Thing {
+ method public String getScheduledTime();
+ method public long getSnoozeDurationMillis();
+ method public int getStatus();
+ field public static final int STATUS_DISMISSED = 3; // 0x3
+ field public static final int STATUS_FIRING = 2; // 0x2
+ field public static final int STATUS_MISSED = 5; // 0x5
+ field public static final int STATUS_SCHEDULED = 1; // 0x1
+ field public static final int STATUS_SNOOZED = 4; // 0x4
+ field public static final int STATUS_UNKNOWN = 0; // 0x0
+ }
+
+ @androidx.appsearch.annotation.Document.BuilderProducer public static final class AlarmInstance.Builder {
+ ctor public AlarmInstance.Builder(androidx.appsearch.builtintypes.AlarmInstance);
+ ctor public AlarmInstance.Builder(String, String, String);
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder addAlternateName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.AlarmInstance.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.AlarmInstance build();
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.AlarmInstance.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder setDocumentTtlMillis(long);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.AlarmInstance.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder setName(String?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.AlarmInstance.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder setSnoozeDurationMillis(long);
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder setStatus(int);
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder setUrl(String?);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public class BuiltInCorpusFilters {
+ method @RequiresPermission(value=android.Manifest.permission.QUERY_ALL_PACKAGES, conditional=true) public static androidx.appsearch.app.SearchSpec.Builder searchMobileApplicationCorpus(androidx.appsearch.app.SearchSpec.Builder);
+ method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public static androidx.appsearch.app.SearchSpec.Builder searchPersonCorpus(androidx.appsearch.app.SearchSpec.Builder);
+ }
+
+ @androidx.appsearch.annotation.Document(name="builtin:ContactPoint") public class ContactPoint extends androidx.appsearch.builtintypes.Thing {
+ method public java.util.List<java.lang.String!> getAddresses();
+ method public java.util.List<java.lang.String!> getEmails();
+ method public String getLabel();
+ method public java.util.List<java.lang.String!> getTelephones();
+ }
+
+ @androidx.appsearch.annotation.Document.BuilderProducer public static final class ContactPoint.Builder {
+ ctor public ContactPoint.Builder(androidx.appsearch.builtintypes.ContactPoint);
+ ctor public ContactPoint.Builder(String, String, String);
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder addAlternateName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.ContactPoint.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.ContactPoint build();
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.ContactPoint.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder setAddresses(java.util.List<java.lang.String!>);
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder setDocumentTtlMillis(long);
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder setEmails(java.util.List<java.lang.String!>);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.ContactPoint.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder setName(String?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.ContactPoint.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder setTelephones(java.util.List<java.lang.String!>);
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder setUrl(String?);
+ }
+
+ @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.VERBATIM_SEARCH) @androidx.appsearch.annotation.Document(name="builtin:GlobalSearchApplicationInfo") @androidx.appsearch.app.ExperimentalAppSearchApi public final class GlobalSearchApplicationInfo {
+ method public int getApplicationType();
+ method public String getId();
+ method public String getNamespace();
+ method public java.util.List<java.lang.String> getSchemaTypes();
+ field public static final int APPLICATION_TYPE_CONSUMER = 1; // 0x1
+ field public static final int APPLICATION_TYPE_PRODUCER = 0; // 0x0
+ }
+
+ public static final class GlobalSearchApplicationInfo.Builder {
+ ctor public GlobalSearchApplicationInfo.Builder(String, String, int);
+ method public androidx.appsearch.builtintypes.GlobalSearchApplicationInfo build();
+ method public androidx.appsearch.builtintypes.GlobalSearchApplicationInfo.Builder setDocumentClasses(java.util.List<java.lang.Class<? extends java.lang.Object!>>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.builtintypes.GlobalSearchApplicationInfo.Builder setSchemaTypes(java.util.List<java.lang.String>);
+ }
+
+ @androidx.appsearch.annotation.Document(name="builtin:ImageObject") public final class ImageObject extends androidx.appsearch.builtintypes.Thing {
+ method public byte[]? getBytes();
+ method public java.util.List<androidx.appsearch.builtintypes.properties.Keyword!> getKeywords();
+ method public String? getSha256();
+ method public String? getThumbnailSha256();
+ }
+
+ public static final class ImageObject.Builder {
+ ctor public ImageObject.Builder(androidx.appsearch.builtintypes.ImageObject);
+ ctor public ImageObject.Builder(String, String);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder addAlternateName(String);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder addKeyword(androidx.appsearch.builtintypes.properties.Keyword);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder addKeyword(String);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder addKeywords(Iterable<androidx.appsearch.builtintypes.properties.Keyword!>);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.ImageObject.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.ImageObject build();
+ method public androidx.appsearch.builtintypes.ImageObject.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.ImageObject.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.ImageObject.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder setBytes(byte[]?);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder setDocumentTtlMillis(long);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.ImageObject.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder setName(String?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.ImageObject.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder setSha256(String?);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder setThumbnailSha256(String?);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder setUrl(String?);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.annotation.Document(name="builtin:MobileApplication") @androidx.appsearch.app.ExperimentalAppSearchApi public class MobileApplication extends androidx.appsearch.builtintypes.Thing {
+ method public String? getClassName();
+ method public String? getDisplayName();
+ method public android.net.Uri? getIconUri();
+ method public String getPackageName();
+ method public byte[] getSha256Certificate();
+ method public long getUpdatedTimestampMillis();
+ }
+
+ public static final class MobileApplication.Builder {
+ ctor public MobileApplication.Builder(androidx.appsearch.builtintypes.MobileApplication);
+ ctor public MobileApplication.Builder(String, String, String, byte[]);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder addAlternateName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.MobileApplication.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.MobileApplication build();
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.MobileApplication.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setClassName(String);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setDisplayName(String);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setDocumentTtlMillis(long);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setIconUri(android.net.Uri);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.MobileApplication.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setName(String?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.MobileApplication.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setUpdatedTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setUrl(String?);
+ }
+
+ @androidx.appsearch.annotation.Document(name="builtin:Person") public class Person extends androidx.appsearch.builtintypes.Thing {
+ method public java.util.List<java.lang.String!> getAdditionalNames();
+ method public java.util.List<java.lang.String!> getAffiliations();
+ method public java.util.List<androidx.appsearch.builtintypes.ContactPoint!> getContactPoints();
+ method public android.net.Uri? getExternalUri();
+ method public String? getFamilyName();
+ method public String? getGivenName();
+ method public android.net.Uri? getImageUri();
+ method public String? getMiddleName();
+ method public java.util.List<java.lang.String!> getNotes();
+ method public java.util.List<java.lang.String!> getRelations();
+ method public java.util.List<androidx.appsearch.builtintypes.Person.AdditionalName!> getTypedAdditionalNames();
+ method public boolean isBot();
+ method public boolean isImportant();
+ }
+
+ public static class Person.AdditionalName {
+ ctor public Person.AdditionalName(int, String);
+ method public int getType();
+ method public String getValue();
+ field public static final int TYPE_NICKNAME = 1; // 0x1
+ field public static final int TYPE_PHONETIC_NAME = 2; // 0x2
+ field public static final int TYPE_UNKNOWN = 0; // 0x0
+ }
+
+ public static final class Person.Builder {
+ ctor public Person.Builder(androidx.appsearch.builtintypes.Person);
+ ctor public Person.Builder(String, String, String);
+ method public androidx.appsearch.builtintypes.Person.Builder addAlternateName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Person.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.Person build();
+ method public androidx.appsearch.builtintypes.Person.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Person.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.Person.Builder setAdditionalNames(java.util.List<androidx.appsearch.builtintypes.Person.AdditionalName!>);
+ method public androidx.appsearch.builtintypes.Person.Builder setAffiliations(java.util.List<java.lang.String!>);
+ method public androidx.appsearch.builtintypes.Person.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.Person.Builder setBot(boolean);
+ method public androidx.appsearch.builtintypes.Person.Builder setContactPoints(java.util.List<androidx.appsearch.builtintypes.ContactPoint!>);
+ method public androidx.appsearch.builtintypes.Person.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.Person.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.Person.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.Person.Builder setDocumentTtlMillis(long);
+ method public androidx.appsearch.builtintypes.Person.Builder setExternalUri(android.net.Uri);
+ method public androidx.appsearch.builtintypes.Person.Builder setFamilyName(String);
+ method public androidx.appsearch.builtintypes.Person.Builder setGivenName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Person.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.Person.Builder setImageUri(android.net.Uri);
+ method public androidx.appsearch.builtintypes.Person.Builder setImportant(boolean);
+ method public androidx.appsearch.builtintypes.Person.Builder setMiddleName(String);
+ method public androidx.appsearch.builtintypes.Person.Builder setName(String?);
+ method public androidx.appsearch.builtintypes.Person.Builder setNotes(java.util.List<java.lang.String!>);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Person.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.Person.Builder setRelations(java.util.List<java.lang.String!>);
+ method public androidx.appsearch.builtintypes.Person.Builder setUrl(String?);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.annotation.Document(name="builtin:PotentialAction") @androidx.appsearch.app.ExperimentalAppSearchApi public class PotentialAction {
+ method public String? getDescription();
+ method public String? getName();
+ method public String? getUri();
+ }
+
+ public static final class PotentialAction.Builder {
+ ctor public PotentialAction.Builder();
+ ctor public PotentialAction.Builder(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.PotentialAction build();
+ method public androidx.appsearch.builtintypes.PotentialAction.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.PotentialAction.Builder setName(String?);
+ method public androidx.appsearch.builtintypes.PotentialAction.Builder setUri(String?);
+ }
+
+ @androidx.appsearch.annotation.Document(name="builtin:Stopwatch") public class Stopwatch extends androidx.appsearch.builtintypes.Thing {
+ method public long calculateBaseTimeMillis(android.content.Context);
+ method public long calculateCurrentAccumulatedDurationMillis(android.content.Context);
+ method public long getAccumulatedDurationMillis();
+ method public long getBaseTimeMillis();
+ method public long getBaseTimeMillisInElapsedRealtime();
+ method public int getBootCount();
+ method public java.util.List<androidx.appsearch.builtintypes.StopwatchLap!> getLaps();
+ method public int getStatus();
+ field public static final int STATUS_PAUSED = 3; // 0x3
+ field public static final int STATUS_RESET = 1; // 0x1
+ field public static final int STATUS_RUNNING = 2; // 0x2
+ field public static final int STATUS_UNKNOWN = 0; // 0x0
+ }
+
+ public static final class Stopwatch.Builder {
+ ctor public Stopwatch.Builder(androidx.appsearch.builtintypes.Stopwatch);
+ ctor public Stopwatch.Builder(String, String);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder addAlternateName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Stopwatch.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.Stopwatch build();
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Stopwatch.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setAccumulatedDurationMillis(long);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setBaseTimeMillis(android.content.Context, long, long);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setBaseTimeMillis(long, long, int);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setDocumentTtlMillis(long);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Stopwatch.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setLaps(java.util.List<androidx.appsearch.builtintypes.StopwatchLap!>);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setName(String?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Stopwatch.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setStatus(int);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setUrl(String?);
+ }
+
+ @androidx.appsearch.annotation.Document(name="builtin:StopwatchLap") public class StopwatchLap extends androidx.appsearch.builtintypes.Thing {
+ method public long getAccumulatedLapDurationMillis();
+ method public long getLapDurationMillis();
+ method public int getLapNumber();
+ }
+
+ @androidx.appsearch.annotation.Document.BuilderProducer public static final class StopwatchLap.Builder {
+ ctor public StopwatchLap.Builder(androidx.appsearch.builtintypes.StopwatchLap);
+ ctor public StopwatchLap.Builder(String, String);
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder addAlternateName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.StopwatchLap.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.StopwatchLap build();
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.StopwatchLap.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder setAccumulatedLapDurationMillis(long);
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder setDocumentTtlMillis(long);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.StopwatchLap.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder setLapDurationMillis(long);
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder setLapNumber(int);
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder setName(String?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.StopwatchLap.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder setUrl(String?);
+ }
+
+ @androidx.appsearch.annotation.Document(name="builtin:Thing") public class Thing {
+ method public java.util.List<java.lang.String!> getAlternateNames();
+ method public long getCreationTimestampMillis();
+ method public String? getDescription();
+ method public int getDocumentScore();
+ method public long getDocumentTtlMillis();
+ method public String getId();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public String? getImage();
+ method public String? getName();
+ method public String getNamespace();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public java.util.List<androidx.appsearch.builtintypes.PotentialAction!> getPotentialActions();
+ method public String? getUrl();
+ }
+
+ @androidx.appsearch.annotation.Document.BuilderProducer public static final class Thing.Builder {
+ ctor public Thing.Builder(androidx.appsearch.builtintypes.Thing);
+ ctor public Thing.Builder(String, String);
+ method public androidx.appsearch.builtintypes.Thing.Builder addAlternateName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Thing.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.Thing build();
+ method public androidx.appsearch.builtintypes.Thing.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Thing.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.Thing.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.Thing.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.Thing.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.Thing.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.Thing.Builder setDocumentTtlMillis(long);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Thing.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.Thing.Builder setName(String?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Thing.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.Thing.Builder setUrl(String?);
+ }
+
+ @androidx.appsearch.annotation.Document(name="builtin:Timer") public class Timer extends androidx.appsearch.builtintypes.Thing {
+ method public long calculateBaseTimeMillis(android.content.Context);
+ method public long calculateCurrentRemainingDurationMillis(android.content.Context);
+ method public long calculateExpirationTimeMillis(android.content.Context);
+ method public long getBaseTimeMillis();
+ method public long getBaseTimeMillisInElapsedRealtime();
+ method public int getBootCount();
+ method public long getDurationMillis();
+ method public long getOriginalDurationMillis();
+ method public long getRemainingDurationMillis();
+ method public String? getRingtone();
+ method public long getStartTimeMillis();
+ method public int getStatus();
+ method public boolean shouldVibrate();
+ field public static final int STATUS_EXPIRED = 3; // 0x3
+ field public static final int STATUS_MISSED = 4; // 0x4
+ field public static final int STATUS_PAUSED = 2; // 0x2
+ field public static final int STATUS_RESET = 5; // 0x5
+ field public static final int STATUS_STARTED = 1; // 0x1
+ field public static final int STATUS_UNKNOWN = 0; // 0x0
+ }
+
+ public static final class Timer.Builder {
+ ctor public Timer.Builder(androidx.appsearch.builtintypes.Timer);
+ ctor public Timer.Builder(String, String);
+ method public androidx.appsearch.builtintypes.Timer.Builder addAlternateName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Timer.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.Timer build();
+ method public androidx.appsearch.builtintypes.Timer.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Timer.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.Timer.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.Timer.Builder setBaseTimeMillis(android.content.Context, long, long);
+ method public androidx.appsearch.builtintypes.Timer.Builder setBaseTimeMillis(long, long, int);
+ method public androidx.appsearch.builtintypes.Timer.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.Timer.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.Timer.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.Timer.Builder setDocumentTtlMillis(long);
+ method public androidx.appsearch.builtintypes.Timer.Builder setDurationMillis(long);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Timer.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.Timer.Builder setName(String?);
+ method public androidx.appsearch.builtintypes.Timer.Builder setOriginalDurationMillis(long);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Timer.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.Timer.Builder setRemainingDurationMillis(long);
+ method public androidx.appsearch.builtintypes.Timer.Builder setRingtone(String?);
+ method public androidx.appsearch.builtintypes.Timer.Builder setShouldVibrate(boolean);
+ method public androidx.appsearch.builtintypes.Timer.Builder setStartTimeMillis(long);
+ method public androidx.appsearch.builtintypes.Timer.Builder setStatus(int);
+ method public androidx.appsearch.builtintypes.Timer.Builder setUrl(String?);
+ }
+
+ @androidx.appsearch.annotation.Document(name=androidx.appsearch.builtintypes.WebPage.SCHEMA_NAME) public final class WebPage extends androidx.appsearch.builtintypes.Thing {
+ method public androidx.appsearch.builtintypes.ImageObject? getFavicon();
+ field public static final String SCHEMA_NAME = "builtin:WebPage";
+ }
+
+ @androidx.appsearch.annotation.Document.BuilderProducer public static final class WebPage.Builder {
+ ctor public WebPage.Builder(androidx.appsearch.builtintypes.WebPage);
+ ctor public WebPage.Builder(String, String);
+ method public androidx.appsearch.builtintypes.WebPage.Builder addAlternateName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.WebPage.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.WebPage build();
+ method public androidx.appsearch.builtintypes.WebPage.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.WebPage.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.WebPage.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.WebPage.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.WebPage.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.WebPage.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.WebPage.Builder setDocumentTtlMillis(long);
+ method public androidx.appsearch.builtintypes.WebPage.Builder setFavicon(androidx.appsearch.builtintypes.ImageObject?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.WebPage.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.WebPage.Builder setName(String?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.WebPage.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.WebPage.Builder setUrl(String?);
+ }
+
+}
+
+package androidx.appsearch.builtintypes.properties {
+
+ @androidx.appsearch.annotation.Document public final class Keyword {
+ ctor public Keyword(String);
+ method public String? asText();
+ }
+
+}
+
diff --git a/appsearch/appsearch-builtin-types/api/res-1.1.0-beta02.txt b/appsearch/appsearch-builtin-types/api/res-1.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/appsearch/appsearch-builtin-types/api/res-1.1.0-beta02.txt
diff --git a/appsearch/appsearch-builtin-types/api/restricted_1.1.0-beta02.txt b/appsearch/appsearch-builtin-types/api/restricted_1.1.0-beta02.txt
new file mode 100644
index 0000000..3d0db8d
--- /dev/null
+++ b/appsearch/appsearch-builtin-types/api/restricted_1.1.0-beta02.txt
@@ -0,0 +1,467 @@
+// Signature format: 4.0
+package androidx.appsearch.app {
+
+ @SuppressCompatibility @RequiresApi(api=android.os.Build.VERSION_CODES.LOLLIPOP) @androidx.appsearch.app.ExperimentalAppSearchApi public class ShortcutAdapter {
+ method public static androidx.core.content.pm.ShortcutInfoCompat.Builder createShortcutBuilderFromDocument(android.content.Context, Object) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.appsearch.app.GenericDocument? extractDocument(androidx.core.content.pm.ShortcutInfoCompat);
+ method public static android.net.Uri getDocumentUri(Object) throws androidx.appsearch.exceptions.AppSearchException;
+ method public static android.net.Uri getDocumentUri(String);
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final String DEFAULT_DATABASE = "__shortcut_adapter_db__";
+ field public static final String DEFAULT_NAMESPACE = "__shortcut_adapter_ns__";
+ }
+
+}
+
+package androidx.appsearch.builtintypes {
+
+ @androidx.appsearch.annotation.Document(name="builtin:Alarm") public class Alarm extends androidx.appsearch.builtintypes.Thing {
+ method public String? getBlackoutPeriodEndDate();
+ method public String? getBlackoutPeriodStartDate();
+ method public int[]? getDaysOfWeek();
+ method @IntRange(from=0, to=23) public int getHour();
+ method @IntRange(from=0, to=59) public int getMinute();
+ method public androidx.appsearch.builtintypes.AlarmInstance? getNextInstance();
+ method public int getOriginatingDevice();
+ method public androidx.appsearch.builtintypes.AlarmInstance? getPreviousInstance();
+ method public String? getRingtone();
+ method public boolean isEnabled();
+ method public boolean shouldVibrate();
+ field public static final int ORIGINATING_DEVICE_SMART_PHONE = 1; // 0x1
+ field public static final int ORIGINATING_DEVICE_SMART_WATCH = 2; // 0x2
+ field public static final int ORIGINATING_DEVICE_UNKNOWN = 0; // 0x0
+ }
+
+ @androidx.appsearch.annotation.Document.BuilderProducer public static final class Alarm.Builder {
+ ctor public Alarm.Builder(androidx.appsearch.builtintypes.Alarm);
+ ctor public Alarm.Builder(String, String);
+ method public androidx.appsearch.builtintypes.Alarm.Builder addAlternateName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Alarm.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.Alarm build();
+ method public androidx.appsearch.builtintypes.Alarm.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Alarm.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.Alarm.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setBlackoutPeriodEndDate(String?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setBlackoutPeriodStartDate(String?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setDaysOfWeek(@IntRange(from=java.util.Calendar.SUNDAY, to=java.util.Calendar.SATURDAY) int...?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setDocumentTtlMillis(long);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setEnabled(boolean);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setHour(@IntRange(from=0, to=23) int);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Alarm.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setMinute(@IntRange(from=0, to=59) int);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setName(String?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setNextInstance(androidx.appsearch.builtintypes.AlarmInstance?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setOriginatingDevice(int);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Alarm.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setPreviousInstance(androidx.appsearch.builtintypes.AlarmInstance?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setRingtone(String?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setShouldVibrate(boolean);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setUrl(String?);
+ }
+
+ @androidx.appsearch.annotation.Document(name="builtin:AlarmInstance") public class AlarmInstance extends androidx.appsearch.builtintypes.Thing {
+ method public String getScheduledTime();
+ method public long getSnoozeDurationMillis();
+ method public int getStatus();
+ field public static final int STATUS_DISMISSED = 3; // 0x3
+ field public static final int STATUS_FIRING = 2; // 0x2
+ field public static final int STATUS_MISSED = 5; // 0x5
+ field public static final int STATUS_SCHEDULED = 1; // 0x1
+ field public static final int STATUS_SNOOZED = 4; // 0x4
+ field public static final int STATUS_UNKNOWN = 0; // 0x0
+ }
+
+ @androidx.appsearch.annotation.Document.BuilderProducer public static final class AlarmInstance.Builder {
+ ctor public AlarmInstance.Builder(androidx.appsearch.builtintypes.AlarmInstance);
+ ctor public AlarmInstance.Builder(String, String, String);
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder addAlternateName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.AlarmInstance.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.AlarmInstance build();
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.AlarmInstance.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder setDocumentTtlMillis(long);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.AlarmInstance.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder setName(String?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.AlarmInstance.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder setSnoozeDurationMillis(long);
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder setStatus(int);
+ method public androidx.appsearch.builtintypes.AlarmInstance.Builder setUrl(String?);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public class BuiltInCorpusFilters {
+ method @RequiresPermission(value=android.Manifest.permission.QUERY_ALL_PACKAGES, conditional=true) public static androidx.appsearch.app.SearchSpec.Builder searchMobileApplicationCorpus(androidx.appsearch.app.SearchSpec.Builder);
+ method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public static androidx.appsearch.app.SearchSpec.Builder searchPersonCorpus(androidx.appsearch.app.SearchSpec.Builder);
+ }
+
+ @androidx.appsearch.annotation.Document(name="builtin:ContactPoint") public class ContactPoint extends androidx.appsearch.builtintypes.Thing {
+ method public java.util.List<java.lang.String!> getAddresses();
+ method public java.util.List<java.lang.String!> getEmails();
+ method public String getLabel();
+ method public java.util.List<java.lang.String!> getTelephones();
+ }
+
+ @androidx.appsearch.annotation.Document.BuilderProducer public static final class ContactPoint.Builder {
+ ctor public ContactPoint.Builder(androidx.appsearch.builtintypes.ContactPoint);
+ ctor public ContactPoint.Builder(String, String, String);
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder addAlternateName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.ContactPoint.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.ContactPoint build();
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.ContactPoint.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder setAddresses(java.util.List<java.lang.String!>);
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder setDocumentTtlMillis(long);
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder setEmails(java.util.List<java.lang.String!>);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.ContactPoint.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder setName(String?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.ContactPoint.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder setTelephones(java.util.List<java.lang.String!>);
+ method public androidx.appsearch.builtintypes.ContactPoint.Builder setUrl(String?);
+ }
+
+ @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.VERBATIM_SEARCH) @androidx.appsearch.annotation.Document(name="builtin:GlobalSearchApplicationInfo") @androidx.appsearch.app.ExperimentalAppSearchApi public final class GlobalSearchApplicationInfo {
+ method public int getApplicationType();
+ method public String getId();
+ method public String getNamespace();
+ method public java.util.List<java.lang.String> getSchemaTypes();
+ field public static final int APPLICATION_TYPE_CONSUMER = 1; // 0x1
+ field public static final int APPLICATION_TYPE_PRODUCER = 0; // 0x0
+ }
+
+ public static final class GlobalSearchApplicationInfo.Builder {
+ ctor public GlobalSearchApplicationInfo.Builder(String, String, int);
+ method public androidx.appsearch.builtintypes.GlobalSearchApplicationInfo build();
+ method public androidx.appsearch.builtintypes.GlobalSearchApplicationInfo.Builder setDocumentClasses(java.util.List<java.lang.Class<? extends java.lang.Object!>>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.builtintypes.GlobalSearchApplicationInfo.Builder setSchemaTypes(java.util.List<java.lang.String>);
+ }
+
+ @androidx.appsearch.annotation.Document(name="builtin:ImageObject") public final class ImageObject extends androidx.appsearch.builtintypes.Thing {
+ method public byte[]? getBytes();
+ method public java.util.List<androidx.appsearch.builtintypes.properties.Keyword!> getKeywords();
+ method public String? getSha256();
+ method public String? getThumbnailSha256();
+ }
+
+ public static final class ImageObject.Builder {
+ ctor public ImageObject.Builder(androidx.appsearch.builtintypes.ImageObject);
+ ctor public ImageObject.Builder(String, String);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder addAlternateName(String);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder addKeyword(androidx.appsearch.builtintypes.properties.Keyword);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder addKeyword(String);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder addKeywords(Iterable<androidx.appsearch.builtintypes.properties.Keyword!>);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.ImageObject.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.ImageObject build();
+ method public androidx.appsearch.builtintypes.ImageObject.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.ImageObject.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.ImageObject.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder setBytes(byte[]?);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder setDocumentTtlMillis(long);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.ImageObject.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder setName(String?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.ImageObject.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder setSha256(String?);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder setThumbnailSha256(String?);
+ method public androidx.appsearch.builtintypes.ImageObject.Builder setUrl(String?);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.annotation.Document(name="builtin:MobileApplication") @androidx.appsearch.app.ExperimentalAppSearchApi public class MobileApplication extends androidx.appsearch.builtintypes.Thing {
+ method public String? getClassName();
+ method public String? getDisplayName();
+ method public android.net.Uri? getIconUri();
+ method public String getPackageName();
+ method public byte[] getSha256Certificate();
+ method public long getUpdatedTimestampMillis();
+ }
+
+ public static final class MobileApplication.Builder {
+ ctor public MobileApplication.Builder(androidx.appsearch.builtintypes.MobileApplication);
+ ctor public MobileApplication.Builder(String, String, String, byte[]);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder addAlternateName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.MobileApplication.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.MobileApplication build();
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.MobileApplication.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setClassName(String);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setDisplayName(String);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setDocumentTtlMillis(long);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setIconUri(android.net.Uri);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.MobileApplication.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setName(String?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.MobileApplication.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setUpdatedTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.MobileApplication.Builder setUrl(String?);
+ }
+
+ @androidx.appsearch.annotation.Document(name="builtin:Person") public class Person extends androidx.appsearch.builtintypes.Thing {
+ method public java.util.List<java.lang.String!> getAdditionalNames();
+ method public java.util.List<java.lang.String!> getAffiliations();
+ method public java.util.List<androidx.appsearch.builtintypes.ContactPoint!> getContactPoints();
+ method public android.net.Uri? getExternalUri();
+ method public String? getFamilyName();
+ method public String? getGivenName();
+ method public android.net.Uri? getImageUri();
+ method public String? getMiddleName();
+ method public java.util.List<java.lang.String!> getNotes();
+ method public java.util.List<java.lang.String!> getRelations();
+ method public java.util.List<androidx.appsearch.builtintypes.Person.AdditionalName!> getTypedAdditionalNames();
+ method public boolean isBot();
+ method public boolean isImportant();
+ }
+
+ public static class Person.AdditionalName {
+ ctor public Person.AdditionalName(int, String);
+ method public int getType();
+ method public String getValue();
+ field public static final int TYPE_NICKNAME = 1; // 0x1
+ field public static final int TYPE_PHONETIC_NAME = 2; // 0x2
+ field public static final int TYPE_UNKNOWN = 0; // 0x0
+ }
+
+ public static final class Person.Builder {
+ ctor public Person.Builder(androidx.appsearch.builtintypes.Person);
+ ctor public Person.Builder(String, String, String);
+ method public androidx.appsearch.builtintypes.Person.Builder addAlternateName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Person.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.Person build();
+ method public androidx.appsearch.builtintypes.Person.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Person.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.Person.Builder setAdditionalNames(java.util.List<androidx.appsearch.builtintypes.Person.AdditionalName!>);
+ method public androidx.appsearch.builtintypes.Person.Builder setAffiliations(java.util.List<java.lang.String!>);
+ method public androidx.appsearch.builtintypes.Person.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.Person.Builder setBot(boolean);
+ method public androidx.appsearch.builtintypes.Person.Builder setContactPoints(java.util.List<androidx.appsearch.builtintypes.ContactPoint!>);
+ method public androidx.appsearch.builtintypes.Person.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.Person.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.Person.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.Person.Builder setDocumentTtlMillis(long);
+ method public androidx.appsearch.builtintypes.Person.Builder setExternalUri(android.net.Uri);
+ method public androidx.appsearch.builtintypes.Person.Builder setFamilyName(String);
+ method public androidx.appsearch.builtintypes.Person.Builder setGivenName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Person.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.Person.Builder setImageUri(android.net.Uri);
+ method public androidx.appsearch.builtintypes.Person.Builder setImportant(boolean);
+ method public androidx.appsearch.builtintypes.Person.Builder setMiddleName(String);
+ method public androidx.appsearch.builtintypes.Person.Builder setName(String?);
+ method public androidx.appsearch.builtintypes.Person.Builder setNotes(java.util.List<java.lang.String!>);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Person.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.Person.Builder setRelations(java.util.List<java.lang.String!>);
+ method public androidx.appsearch.builtintypes.Person.Builder setUrl(String?);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.annotation.Document(name="builtin:PotentialAction") @androidx.appsearch.app.ExperimentalAppSearchApi public class PotentialAction {
+ method public String? getDescription();
+ method public String? getName();
+ method public String? getUri();
+ }
+
+ public static final class PotentialAction.Builder {
+ ctor public PotentialAction.Builder();
+ ctor public PotentialAction.Builder(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.PotentialAction build();
+ method public androidx.appsearch.builtintypes.PotentialAction.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.PotentialAction.Builder setName(String?);
+ method public androidx.appsearch.builtintypes.PotentialAction.Builder setUri(String?);
+ }
+
+ @androidx.appsearch.annotation.Document(name="builtin:Stopwatch") public class Stopwatch extends androidx.appsearch.builtintypes.Thing {
+ method public long calculateBaseTimeMillis(android.content.Context);
+ method public long calculateCurrentAccumulatedDurationMillis(android.content.Context);
+ method public long getAccumulatedDurationMillis();
+ method public long getBaseTimeMillis();
+ method public long getBaseTimeMillisInElapsedRealtime();
+ method public int getBootCount();
+ method public java.util.List<androidx.appsearch.builtintypes.StopwatchLap!> getLaps();
+ method public int getStatus();
+ field public static final int STATUS_PAUSED = 3; // 0x3
+ field public static final int STATUS_RESET = 1; // 0x1
+ field public static final int STATUS_RUNNING = 2; // 0x2
+ field public static final int STATUS_UNKNOWN = 0; // 0x0
+ }
+
+ public static final class Stopwatch.Builder {
+ ctor public Stopwatch.Builder(androidx.appsearch.builtintypes.Stopwatch);
+ ctor public Stopwatch.Builder(String, String);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder addAlternateName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Stopwatch.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.Stopwatch build();
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Stopwatch.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setAccumulatedDurationMillis(long);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setBaseTimeMillis(android.content.Context, long, long);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setBaseTimeMillis(long, long, int);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setDocumentTtlMillis(long);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Stopwatch.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setLaps(java.util.List<androidx.appsearch.builtintypes.StopwatchLap!>);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setName(String?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Stopwatch.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setStatus(int);
+ method public androidx.appsearch.builtintypes.Stopwatch.Builder setUrl(String?);
+ }
+
+ @androidx.appsearch.annotation.Document(name="builtin:StopwatchLap") public class StopwatchLap extends androidx.appsearch.builtintypes.Thing {
+ method public long getAccumulatedLapDurationMillis();
+ method public long getLapDurationMillis();
+ method public int getLapNumber();
+ }
+
+ @androidx.appsearch.annotation.Document.BuilderProducer public static final class StopwatchLap.Builder {
+ ctor public StopwatchLap.Builder(androidx.appsearch.builtintypes.StopwatchLap);
+ ctor public StopwatchLap.Builder(String, String);
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder addAlternateName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.StopwatchLap.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.StopwatchLap build();
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.StopwatchLap.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder setAccumulatedLapDurationMillis(long);
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder setDocumentTtlMillis(long);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.StopwatchLap.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder setLapDurationMillis(long);
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder setLapNumber(int);
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder setName(String?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.StopwatchLap.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.StopwatchLap.Builder setUrl(String?);
+ }
+
+ @androidx.appsearch.annotation.Document(name="builtin:Thing") public class Thing {
+ method public java.util.List<java.lang.String!> getAlternateNames();
+ method public long getCreationTimestampMillis();
+ method public String? getDescription();
+ method public int getDocumentScore();
+ method public long getDocumentTtlMillis();
+ method public String getId();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public String? getImage();
+ method public String? getName();
+ method public String getNamespace();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public java.util.List<androidx.appsearch.builtintypes.PotentialAction!> getPotentialActions();
+ method public String? getUrl();
+ }
+
+ @androidx.appsearch.annotation.Document.BuilderProducer public static final class Thing.Builder {
+ ctor public Thing.Builder(androidx.appsearch.builtintypes.Thing);
+ ctor public Thing.Builder(String, String);
+ method public androidx.appsearch.builtintypes.Thing.Builder addAlternateName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Thing.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.Thing build();
+ method public androidx.appsearch.builtintypes.Thing.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Thing.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.Thing.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.Thing.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.Thing.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.Thing.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.Thing.Builder setDocumentTtlMillis(long);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Thing.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.Thing.Builder setName(String?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Thing.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.Thing.Builder setUrl(String?);
+ }
+
+ @androidx.appsearch.annotation.Document(name="builtin:Timer") public class Timer extends androidx.appsearch.builtintypes.Thing {
+ method public long calculateBaseTimeMillis(android.content.Context);
+ method public long calculateCurrentRemainingDurationMillis(android.content.Context);
+ method public long calculateExpirationTimeMillis(android.content.Context);
+ method public long getBaseTimeMillis();
+ method public long getBaseTimeMillisInElapsedRealtime();
+ method public int getBootCount();
+ method public long getDurationMillis();
+ method public long getOriginalDurationMillis();
+ method public long getRemainingDurationMillis();
+ method public String? getRingtone();
+ method public long getStartTimeMillis();
+ method public int getStatus();
+ method public boolean shouldVibrate();
+ field public static final int STATUS_EXPIRED = 3; // 0x3
+ field public static final int STATUS_MISSED = 4; // 0x4
+ field public static final int STATUS_PAUSED = 2; // 0x2
+ field public static final int STATUS_RESET = 5; // 0x5
+ field public static final int STATUS_STARTED = 1; // 0x1
+ field public static final int STATUS_UNKNOWN = 0; // 0x0
+ }
+
+ public static final class Timer.Builder {
+ ctor public Timer.Builder(androidx.appsearch.builtintypes.Timer);
+ ctor public Timer.Builder(String, String);
+ method public androidx.appsearch.builtintypes.Timer.Builder addAlternateName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Timer.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.Timer build();
+ method public androidx.appsearch.builtintypes.Timer.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Timer.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.Timer.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.Timer.Builder setBaseTimeMillis(android.content.Context, long, long);
+ method public androidx.appsearch.builtintypes.Timer.Builder setBaseTimeMillis(long, long, int);
+ method public androidx.appsearch.builtintypes.Timer.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.Timer.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.Timer.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.Timer.Builder setDocumentTtlMillis(long);
+ method public androidx.appsearch.builtintypes.Timer.Builder setDurationMillis(long);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Timer.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.Timer.Builder setName(String?);
+ method public androidx.appsearch.builtintypes.Timer.Builder setOriginalDurationMillis(long);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.Timer.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.Timer.Builder setRemainingDurationMillis(long);
+ method public androidx.appsearch.builtintypes.Timer.Builder setRingtone(String?);
+ method public androidx.appsearch.builtintypes.Timer.Builder setShouldVibrate(boolean);
+ method public androidx.appsearch.builtintypes.Timer.Builder setStartTimeMillis(long);
+ method public androidx.appsearch.builtintypes.Timer.Builder setStatus(int);
+ method public androidx.appsearch.builtintypes.Timer.Builder setUrl(String?);
+ }
+
+ @androidx.appsearch.annotation.Document(name=androidx.appsearch.builtintypes.WebPage.SCHEMA_NAME) public final class WebPage extends androidx.appsearch.builtintypes.Thing {
+ method public androidx.appsearch.builtintypes.ImageObject? getFavicon();
+ field public static final String SCHEMA_NAME = "builtin:WebPage";
+ }
+
+ @androidx.appsearch.annotation.Document.BuilderProducer public static final class WebPage.Builder {
+ ctor public WebPage.Builder(androidx.appsearch.builtintypes.WebPage);
+ ctor public WebPage.Builder(String, String);
+ method public androidx.appsearch.builtintypes.WebPage.Builder addAlternateName(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.WebPage.Builder addPotentialAction(androidx.appsearch.builtintypes.PotentialAction);
+ method public androidx.appsearch.builtintypes.WebPage build();
+ method public androidx.appsearch.builtintypes.WebPage.Builder clearAlternateNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.WebPage.Builder clearPotentialActions();
+ method public androidx.appsearch.builtintypes.WebPage.Builder setAlternateNames(java.util.List<java.lang.String!>?);
+ method public androidx.appsearch.builtintypes.WebPage.Builder setCreationTimestampMillis(long);
+ method public androidx.appsearch.builtintypes.WebPage.Builder setDescription(String?);
+ method public androidx.appsearch.builtintypes.WebPage.Builder setDocumentScore(int);
+ method public androidx.appsearch.builtintypes.WebPage.Builder setDocumentTtlMillis(long);
+ method public androidx.appsearch.builtintypes.WebPage.Builder setFavicon(androidx.appsearch.builtintypes.ImageObject?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.WebPage.Builder setImage(String?);
+ method public androidx.appsearch.builtintypes.WebPage.Builder setName(String?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.builtintypes.WebPage.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
+ method public androidx.appsearch.builtintypes.WebPage.Builder setUrl(String?);
+ }
+
+}
+
+package androidx.appsearch.builtintypes.properties {
+
+ @androidx.appsearch.annotation.Document public final class Keyword {
+ ctor public Keyword(String);
+ method public String? asText();
+ }
+
+}
+
diff --git a/appsearch/appsearch-debug-view/api/1.1.0-beta02.txt b/appsearch/appsearch-debug-view/api/1.1.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/appsearch/appsearch-debug-view/api/1.1.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/appsearch/appsearch-debug-view/api/res-1.1.0-beta02.txt b/appsearch/appsearch-debug-view/api/res-1.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/appsearch/appsearch-debug-view/api/res-1.1.0-beta02.txt
diff --git a/appsearch/appsearch-debug-view/api/restricted_1.1.0-beta02.txt b/appsearch/appsearch-debug-view/api/restricted_1.1.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/appsearch/appsearch-debug-view/api/restricted_1.1.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/appsearch/appsearch-external-protobuf/api/1.1.0-beta02.txt b/appsearch/appsearch-external-protobuf/api/1.1.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/appsearch/appsearch-external-protobuf/api/1.1.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/appsearch/appsearch-external-protobuf/api/restricted_1.1.0-beta02.txt b/appsearch/appsearch-external-protobuf/api/restricted_1.1.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/appsearch/appsearch-external-protobuf/api/restricted_1.1.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/appsearch/appsearch-ktx/api/1.1.0-beta02.txt b/appsearch/appsearch-ktx/api/1.1.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/appsearch/appsearch-ktx/api/1.1.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/appsearch/appsearch-ktx/api/res-1.1.0-beta02.txt b/appsearch/appsearch-ktx/api/res-1.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/appsearch/appsearch-ktx/api/res-1.1.0-beta02.txt
diff --git a/appsearch/appsearch-ktx/api/restricted_1.1.0-beta02.txt b/appsearch/appsearch-ktx/api/restricted_1.1.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/appsearch/appsearch-ktx/api/restricted_1.1.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/appsearch/appsearch-local-storage/api/1.1.0-beta02.txt b/appsearch/appsearch-local-storage/api/1.1.0-beta02.txt
new file mode 100644
index 0000000..425b7b2
--- /dev/null
+++ b/appsearch/appsearch-local-storage/api/1.1.0-beta02.txt
@@ -0,0 +1,33 @@
+// Signature format: 4.0
+package androidx.appsearch.localstorage {
+
+ public class LocalStorage {
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GlobalSearchSession!> createGlobalSearchSessionAsync(androidx.appsearch.localstorage.LocalStorage.GlobalSearchContext);
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchSession!> createSearchSessionAsync(androidx.appsearch.localstorage.LocalStorage.SearchContext);
+ }
+
+ public static final class LocalStorage.GlobalSearchContext {
+ method public android.content.Context getContext();
+ method public java.util.concurrent.Executor getWorkerExecutor();
+ }
+
+ public static final class LocalStorage.GlobalSearchContext.Builder {
+ ctor public LocalStorage.GlobalSearchContext.Builder(android.content.Context);
+ method public androidx.appsearch.localstorage.LocalStorage.GlobalSearchContext build();
+ method public androidx.appsearch.localstorage.LocalStorage.GlobalSearchContext.Builder setWorkerExecutor(java.util.concurrent.Executor);
+ }
+
+ public static final class LocalStorage.SearchContext {
+ method public android.content.Context getContext();
+ method public String getDatabaseName();
+ method public java.util.concurrent.Executor getWorkerExecutor();
+ }
+
+ public static final class LocalStorage.SearchContext.Builder {
+ ctor public LocalStorage.SearchContext.Builder(android.content.Context, String);
+ method public androidx.appsearch.localstorage.LocalStorage.SearchContext build();
+ method public androidx.appsearch.localstorage.LocalStorage.SearchContext.Builder setWorkerExecutor(java.util.concurrent.Executor);
+ }
+
+}
+
diff --git a/appsearch/appsearch-local-storage/api/res-1.1.0-beta02.txt b/appsearch/appsearch-local-storage/api/res-1.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/appsearch/appsearch-local-storage/api/res-1.1.0-beta02.txt
diff --git a/appsearch/appsearch-local-storage/api/restricted_1.1.0-beta02.txt b/appsearch/appsearch-local-storage/api/restricted_1.1.0-beta02.txt
new file mode 100644
index 0000000..425b7b2
--- /dev/null
+++ b/appsearch/appsearch-local-storage/api/restricted_1.1.0-beta02.txt
@@ -0,0 +1,33 @@
+// Signature format: 4.0
+package androidx.appsearch.localstorage {
+
+ public class LocalStorage {
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GlobalSearchSession!> createGlobalSearchSessionAsync(androidx.appsearch.localstorage.LocalStorage.GlobalSearchContext);
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchSession!> createSearchSessionAsync(androidx.appsearch.localstorage.LocalStorage.SearchContext);
+ }
+
+ public static final class LocalStorage.GlobalSearchContext {
+ method public android.content.Context getContext();
+ method public java.util.concurrent.Executor getWorkerExecutor();
+ }
+
+ public static final class LocalStorage.GlobalSearchContext.Builder {
+ ctor public LocalStorage.GlobalSearchContext.Builder(android.content.Context);
+ method public androidx.appsearch.localstorage.LocalStorage.GlobalSearchContext build();
+ method public androidx.appsearch.localstorage.LocalStorage.GlobalSearchContext.Builder setWorkerExecutor(java.util.concurrent.Executor);
+ }
+
+ public static final class LocalStorage.SearchContext {
+ method public android.content.Context getContext();
+ method public String getDatabaseName();
+ method public java.util.concurrent.Executor getWorkerExecutor();
+ }
+
+ public static final class LocalStorage.SearchContext.Builder {
+ ctor public LocalStorage.SearchContext.Builder(android.content.Context, String);
+ method public androidx.appsearch.localstorage.LocalStorage.SearchContext build();
+ method public androidx.appsearch.localstorage.LocalStorage.SearchContext.Builder setWorkerExecutor(java.util.concurrent.Executor);
+ }
+
+}
+
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
index af20e81..898e680 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
@@ -3877,17 +3877,27 @@
Collections.emptyMap());
assertThat(getResult).isEqualTo(document);
- // That document should be visible even from another instance.
+ // Inialize a new instance of AppSearch to test initialization.
+ InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
AppSearchImpl appSearchImpl2 = AppSearchImpl.create(
mAppSearchDir,
new AppSearchConfigImpl(
new UnlimitedLimitConfig(),
new LocalStorageIcingOptionsConfig()
),
- /*initStatsBuilder=*/ null,
+ /*initStatsBuilder=*/initStatsBuilder,
/*visibilityChecker=*/ null,
/*revocableFileDescriptorStore=*/ null,
ALWAYS_OPTIMIZE);
+
+ // Initialization should trigger a recovery
+ InitializeStats initStats = initStatsBuilder.build();
+ assertThat(initStats.getDocumentStoreRecoveryCause())
+ .isEqualTo(InitializeStats.RECOVERY_CAUSE_IO_ERROR);
+ assertThat(initStats.getIndexRestorationCause())
+ .isEqualTo(InitializeStats.RECOVERY_CAUSE_IO_ERROR);
+
+ // That document should be visible even from another instance.
getResult = appSearchImpl2.getDocument("package", "database", "namespace1",
"id1",
Collections.emptyMap());
@@ -3950,17 +3960,27 @@
Collections.emptyMap());
assertThat(getResult).isEqualTo(document2);
- // Only the second document should be retrievable from another instance.
+ // Inialize a new instance of AppSearch to test initialization.
+ InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
AppSearchImpl appSearchImpl2 = AppSearchImpl.create(
mAppSearchDir,
new AppSearchConfigImpl(
new UnlimitedLimitConfig(),
new LocalStorageIcingOptionsConfig()
),
- /*initStatsBuilder=*/ null,
+ /*initStatsBuilder=*/initStatsBuilder,
/*visibilityChecker=*/ null,
/*revocableFileDescriptorStore=*/ null,
ALWAYS_OPTIMIZE);
+
+ // Initialization should trigger a recovery
+ InitializeStats initStats = initStatsBuilder.build();
+ assertThat(initStats.getDocumentStoreRecoveryCause())
+ .isEqualTo(InitializeStats.RECOVERY_CAUSE_IO_ERROR);
+ assertThat(initStats.getIndexRestorationCause())
+ .isEqualTo(InitializeStats.RECOVERY_CAUSE_IO_ERROR);
+
+ // Only the second document should be retrievable from another instance.
assertThrows(AppSearchException.class, () -> appSearchImpl2.getDocument("package",
"database",
"namespace1",
@@ -4030,17 +4050,263 @@
Collections.emptyMap());
assertThat(getResult).isEqualTo(document2);
- // Only the second document should be retrievable from another instance.
+ // Inialize a new instance of AppSearch to test initialization.
+ InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
AppSearchImpl appSearchImpl2 = AppSearchImpl.create(
mAppSearchDir,
new AppSearchConfigImpl(
new UnlimitedLimitConfig(),
new LocalStorageIcingOptionsConfig()
),
- /*initStatsBuilder=*/ null,
+ /*initStatsBuilder=*/initStatsBuilder,
/*visibilityChecker=*/ null,
/*revocableFileDescriptorStore=*/ null,
ALWAYS_OPTIMIZE);
+
+ // Initialization should trigger a recovery
+ InitializeStats initStats = initStatsBuilder.build();
+ assertThat(initStats.getDocumentStoreRecoveryCause())
+ .isEqualTo(InitializeStats.RECOVERY_CAUSE_IO_ERROR);
+ assertThat(initStats.getIndexRestorationCause())
+ .isEqualTo(InitializeStats.RECOVERY_CAUSE_IO_ERROR);
+
+ // Only the second document should be retrievable from another instance.
+ assertThrows(AppSearchException.class, () -> appSearchImpl2.getDocument("package",
+ "database",
+ "namespace1",
+ "id1",
+ Collections.emptyMap()));
+ getResult = appSearchImpl2.getDocument("package", "database", "namespace2",
+ "id2",
+ Collections.emptyMap());
+ assertThat(getResult).isEqualTo(document2);
+ appSearchImpl2.close();
+ }
+
+ @Test
+ public void testPutPersistsWithoutRecoveryWithRecoveryProofFlush() throws Exception {
+ List<AppSearchSchema> schemas =
+ Collections.singletonList(new AppSearchSchema.Builder("type").build());
+ InternalSetSchemaResponse internalSetSchemaResponse = mAppSearchImpl.setSchema(
+ "package",
+ "database",
+ schemas,
+ /*visibilityConfigs=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
+ assertThat(internalSetSchemaResponse.isSuccess()).isTrue();
+
+ // Add a document and persist it.
+ GenericDocument document =
+ new GenericDocument.Builder<>("namespace1", "id1", "type").build();
+ mAppSearchImpl.putDocument(
+ "package",
+ "database",
+ document,
+ /*sendChangeNotifications=*/ false,
+ /*logger=*/ null);
+ mAppSearchImpl.persistToDisk(PersistType.Code.RECOVERY_PROOF);
+
+ GenericDocument getResult = mAppSearchImpl.getDocument("package", "database", "namespace1",
+ "id1",
+ Collections.emptyMap());
+ assertThat(getResult).isEqualTo(document);
+
+ // Inialize a new instance of AppSearch to test initialization.
+ InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
+ AppSearchImpl appSearchImpl2 = AppSearchImpl.create(
+ mAppSearchDir,
+ new AppSearchConfigImpl(
+ new UnlimitedLimitConfig(),
+ new LocalStorageIcingOptionsConfig()
+ ),
+ /*initStatsBuilder=*/initStatsBuilder,
+ /*visibilityChecker=*/ null,
+ /*revocableFileDescriptorStore=*/ null,
+ ALWAYS_OPTIMIZE);
+
+ // Initialization should NOT trigger a recovery
+ InitializeStats initStats = initStatsBuilder.build();
+ assertThat(initStats.getDocumentStoreRecoveryCause())
+ .isEqualTo(InitializeStats.RECOVERY_CAUSE_NONE);
+ assertThat(initStats.getIndexRestorationCause())
+ .isEqualTo(InitializeStats.RECOVERY_CAUSE_NONE);
+
+ // That document should be visible even from another instance.
+ getResult = appSearchImpl2.getDocument("package", "database", "namespace1",
+ "id1",
+ Collections.emptyMap());
+ assertThat(getResult).isEqualTo(document);
+ appSearchImpl2.close();
+ }
+
+ @Test
+ public void testDeletePersistsWithoutRecoveryWithRecoveryProofFlush() throws Exception {
+ List<AppSearchSchema> schemas =
+ Collections.singletonList(new AppSearchSchema.Builder("type").build());
+ InternalSetSchemaResponse internalSetSchemaResponse = mAppSearchImpl.setSchema(
+ "package",
+ "database",
+ schemas,
+ /*visibilityConfigs=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
+ assertThat(internalSetSchemaResponse.isSuccess()).isTrue();
+
+ // Add two documents and persist them.
+ GenericDocument document1 =
+ new GenericDocument.Builder<>("namespace1", "id1", "type").build();
+ mAppSearchImpl.putDocument(
+ "package",
+ "database",
+ document1,
+ /*sendChangeNotifications=*/ false,
+ /*logger=*/ null);
+ GenericDocument document2 =
+ new GenericDocument.Builder<>("namespace1", "id2", "type").build();
+ mAppSearchImpl.putDocument(
+ "package",
+ "database",
+ document2,
+ /*sendChangeNotifications=*/ false,
+ /*logger=*/ null);
+ mAppSearchImpl.persistToDisk(PersistType.Code.RECOVERY_PROOF);
+
+ GenericDocument getResult = mAppSearchImpl.getDocument("package", "database", "namespace1",
+ "id1",
+ Collections.emptyMap());
+ assertThat(getResult).isEqualTo(document1);
+ getResult = mAppSearchImpl.getDocument("package", "database", "namespace1",
+ "id2",
+ Collections.emptyMap());
+ assertThat(getResult).isEqualTo(document2);
+
+ // Delete the first document
+ mAppSearchImpl.remove("package", "database", "namespace1", "id1", /*statsBuilder=*/ null);
+ mAppSearchImpl.persistToDisk(PersistType.Code.RECOVERY_PROOF);
+ assertThrows(AppSearchException.class, () -> mAppSearchImpl.getDocument("package",
+ "database",
+ "namespace1",
+ "id1",
+ Collections.emptyMap()));
+ getResult = mAppSearchImpl.getDocument("package", "database", "namespace1",
+ "id2",
+ Collections.emptyMap());
+ assertThat(getResult).isEqualTo(document2);
+
+ // Inialize a new instance of AppSearch to test initialization.
+ InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
+ AppSearchImpl appSearchImpl2 = AppSearchImpl.create(
+ mAppSearchDir,
+ new AppSearchConfigImpl(
+ new UnlimitedLimitConfig(),
+ new LocalStorageIcingOptionsConfig()
+ ),
+ /*initStatsBuilder=*/initStatsBuilder,
+ /*visibilityChecker=*/ null,
+ /*revocableFileDescriptorStore=*/ null,
+ ALWAYS_OPTIMIZE);
+
+ // Initialization should NOT trigger a recovery.
+ InitializeStats initStats = initStatsBuilder.build();
+ assertThat(initStats.getDocumentStoreRecoveryCause())
+ .isEqualTo(InitializeStats.RECOVERY_CAUSE_NONE);
+ assertThat(initStats.getIndexRestorationCause())
+ .isEqualTo(InitializeStats.RECOVERY_CAUSE_NONE);
+
+ // Only the second document should be retrievable from another instance.
+ assertThrows(AppSearchException.class, () -> appSearchImpl2.getDocument("package",
+ "database",
+ "namespace1",
+ "id1",
+ Collections.emptyMap()));
+ getResult = appSearchImpl2.getDocument("package", "database", "namespace1",
+ "id2",
+ Collections.emptyMap());
+ assertThat(getResult).isEqualTo(document2);
+ appSearchImpl2.close();
+ }
+
+ @Test
+ public void testDeleteByQueryPersistsWithoutRecoveryWithRecoveryProofFlush() throws Exception {
+ List<AppSearchSchema> schemas =
+ Collections.singletonList(new AppSearchSchema.Builder("type").build());
+ InternalSetSchemaResponse internalSetSchemaResponse = mAppSearchImpl.setSchema(
+ "package",
+ "database",
+ schemas,
+ /*visibilityConfigs=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
+ assertThat(internalSetSchemaResponse.isSuccess()).isTrue();
+
+ // Add two documents and persist them.
+ GenericDocument document1 =
+ new GenericDocument.Builder<>("namespace1", "id1", "type").build();
+ mAppSearchImpl.putDocument(
+ "package",
+ "database",
+ document1,
+ /*sendChangeNotifications=*/ false,
+ /*logger=*/ null);
+ GenericDocument document2 =
+ new GenericDocument.Builder<>("namespace2", "id2", "type").build();
+ mAppSearchImpl.putDocument(
+ "package",
+ "database",
+ document2,
+ /*sendChangeNotifications=*/ false,
+ /*logger=*/ null);
+ mAppSearchImpl.persistToDisk(PersistType.Code.RECOVERY_PROOF);
+
+ GenericDocument getResult = mAppSearchImpl.getDocument("package", "database", "namespace1",
+ "id1",
+ Collections.emptyMap());
+ assertThat(getResult).isEqualTo(document1);
+ getResult = mAppSearchImpl.getDocument("package", "database", "namespace2",
+ "id2",
+ Collections.emptyMap());
+ assertThat(getResult).isEqualTo(document2);
+
+ // Delete the first document
+ mAppSearchImpl.removeByQuery("package", "database", "",
+ new SearchSpec.Builder().addFilterNamespaces("namespace1").setTermMatch(
+ SearchSpec.TERM_MATCH_EXACT_ONLY).build(), /*statsBuilder=*/ null);
+ mAppSearchImpl.persistToDisk(PersistType.Code.RECOVERY_PROOF);
+ assertThrows(AppSearchException.class, () -> mAppSearchImpl.getDocument("package",
+ "database",
+ "namespace1",
+ "id1",
+ Collections.emptyMap()));
+ getResult = mAppSearchImpl.getDocument("package", "database", "namespace2",
+ "id2",
+ Collections.emptyMap());
+ assertThat(getResult).isEqualTo(document2);
+
+ // Initialize a new instance of AppSearch to test initialization.
+ InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
+ AppSearchImpl appSearchImpl2 = AppSearchImpl.create(
+ mAppSearchDir,
+ new AppSearchConfigImpl(
+ new UnlimitedLimitConfig(),
+ new LocalStorageIcingOptionsConfig()
+ ),
+ /*initStatsBuilder=*/initStatsBuilder,
+ /*visibilityChecker=*/ null,
+ /*revocableFileDescriptorStore=*/ null,
+ ALWAYS_OPTIMIZE);
+
+ // Initialization should NOT trigger a recovery.
+ InitializeStats initStats = initStatsBuilder.build();
+ assertThat(initStats.getDocumentStoreRecoveryCause())
+ .isEqualTo(InitializeStats.RECOVERY_CAUSE_NONE);
+ assertThat(initStats.getIndexRestorationCause())
+ .isEqualTo(InitializeStats.RECOVERY_CAUSE_NONE);
+
+ // Only the second document should be retrievable from another instance.
assertThrows(AppSearchException.class, () -> appSearchImpl2.getDocument("package",
"database",
"namespace1",
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/LocalStorageTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/LocalStorageTest.java
index 333cea3..18f8abe 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/LocalStorageTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/LocalStorageTest.java
@@ -20,8 +20,11 @@
import static org.junit.Assert.assertThrows;
+import androidx.appsearch.exceptions.AppSearchException;
import androidx.test.core.app.ApplicationProvider;
+import com.google.android.icing.proto.PersistType;
+
import org.junit.Test;
import java.util.concurrent.Executor;
@@ -30,11 +33,15 @@
public class LocalStorageTest {
@Test
public void testSameInstance() throws Exception {
+ LocalStorage.resetInstance();
+
Executor executor = Executors.newCachedThreadPool();
LocalStorage b1 = LocalStorage.getOrCreateInstance(
- ApplicationProvider.getApplicationContext(), executor, /*logger=*/ null);
+ ApplicationProvider.getApplicationContext(), executor, /*logger=*/ null,
+ /* persistToDiskRecoveryProof=*/false);
LocalStorage b2 = LocalStorage.getOrCreateInstance(
- ApplicationProvider.getApplicationContext(), executor, /*logger=*/ null);
+ ApplicationProvider.getApplicationContext(), executor, /*logger=*/ null,
+ /* persistToDiskRecoveryProof=*/false);
assertThat(b1).isSameInstanceAs(b2);
}
@@ -88,4 +95,31 @@
"/testDatabaseNameStartWith").build());
assertThat(e).hasMessageThat().isEqualTo("Database name cannot contain '/'");
}
+
+ @Test
+ public void testLocalStorage_persistToDiskRecoveryProofTrue() throws AppSearchException {
+ LocalStorage.resetInstance();
+
+ LocalStorage.SearchContext searchContext =
+ new LocalStorage.SearchContext.Builder(
+ ApplicationProvider.getApplicationContext(),
+ /*databaseName=*/"dbName").setPersistToDiskRecoveryProof(true).build();
+
+ AppSearchConfig config = LocalStorage.getConfig(searchContext);
+ assertThat(config.getLightweightPersistType()).isEqualTo(PersistType.Code.RECOVERY_PROOF);
+ }
+
+ @Test
+ public void testLocalStorage_persistToDiskRecoveryProofFalse() throws AppSearchException {
+ LocalStorage.resetInstance();
+
+ // persistToDiskRecoveryProof is false by default.
+ LocalStorage.SearchContext searchContext =
+ new LocalStorage.SearchContext.Builder(
+ ApplicationProvider.getApplicationContext(),
+ /*databaseName=*/"dbName").build();
+
+ AppSearchConfig config = LocalStorage.getConfig(searchContext);
+ assertThat(config.getLightweightPersistType()).isEqualTo(PersistType.Code.LITE);
+ }
}
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverterTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverterTest.java
index 94d80452..cfb30c8 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverterTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverterTest.java
@@ -436,14 +436,16 @@
new AppSearchConfigImpl(new UnlimitedLimitConfig(),
new LocalStorageIcingOptionsConfig(),
/* storeParentInfoAsSyntheticProperty= */ false,
- /* shouldRetrieveParentInfo= */ true));
+ /* shouldRetrieveParentInfo= */ true,
+ /* persistToDiskRecoveryProof=*/false));
GenericDocument actualDocWithParentAsSyntheticProperty =
GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX,
new SchemaCache(schemaMap),
new AppSearchConfigImpl(new UnlimitedLimitConfig(),
new LocalStorageIcingOptionsConfig(),
/* storeParentInfoAsSyntheticProperty= */ true,
- /* shouldRetrieveParentInfo= */ true));
+ /* shouldRetrieveParentInfo= */ true,
+ /* persistToDiskRecoveryProof=*/false));
assertThat(actualDocWithParentAsMetaField).isEqualTo(expectedDocWithParentAsMetaField);
assertThat(actualDocWithParentAsMetaField).isNotEqualTo(
@@ -505,14 +507,16 @@
new AppSearchConfigImpl(new UnlimitedLimitConfig(),
new LocalStorageIcingOptionsConfig(),
/* storeParentInfoAsSyntheticProperty= */ false,
- /* shouldRetrieveParentInfo= */ true));
+ /* shouldRetrieveParentInfo= */ true,
+ /* persistToDiskRecoveryProof=*/false));
GenericDocument actualDoc2 =
GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX,
new SchemaCache(schemaMap),
new AppSearchConfigImpl(new UnlimitedLimitConfig(),
new LocalStorageIcingOptionsConfig(),
/* storeParentInfoAsSyntheticProperty= */ true,
- /* shouldRetrieveParentInfo= */ true));
+ /* shouldRetrieveParentInfo= */ true,
+ /* persistToDiskRecoveryProof=*/false));
assertThat(actualDoc1).isEqualTo(expectedDoc);
assertThat(actualDoc2).isEqualTo(expectedDoc);
}
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverterTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverterTest.java
index 1576821..fb950a0 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverterTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverterTest.java
@@ -59,7 +59,8 @@
new UnlimitedLimitConfig(),
new LocalStorageIcingOptionsConfig(),
/* storeParentInfoAsSyntheticProperty= */ false,
- /* shouldRetrieveParentInfo= */ true);
+ /* shouldRetrieveParentInfo= */ true,
+ /* persistToDiskRecoveryProof=*/false);
// Building the SearchResult received from query.
DocumentProto.Builder documentProtoBuilder = DocumentProto.newBuilder()
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfig.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfig.java
index 0932377..3b53f7e 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfig.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfig.java
@@ -19,6 +19,10 @@
import androidx.annotation.RestrictTo;
import androidx.appsearch.app.GenericDocument;
+import com.google.android.icing.proto.PersistType;
+
+import org.jspecify.annotations.NonNull;
+
/**
* An interface that wraps AppSearch configurations required to create {@link AppSearchImpl}.
*/
@@ -38,4 +42,10 @@
* {@link androidx.appsearch.flags.Flags#FLAG_ENABLE_SEARCH_RESULT_PARENT_TYPES} in on.
*/
boolean shouldRetrieveParentInfo();
+
+ /**
+ * Returns the {@code PersistType.Code} that should be used to persist common mutations such as
+ * PUTs or DELETEs.
+ */
+ PersistType. @NonNull Code getLightweightPersistType();
}
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfigImpl.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfigImpl.java
index 21142e0..1479d19 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfigImpl.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfigImpl.java
@@ -18,6 +18,8 @@
import androidx.annotation.RestrictTo;
+import com.google.android.icing.proto.PersistType;
+
import org.jspecify.annotations.NonNull;
/**
@@ -30,23 +32,27 @@
private final IcingOptionsConfig mIcingOptionsConfig;
private final boolean mStoreParentInfoAsSyntheticProperty;
private final boolean mShouldRetrieveParentInfo;
+ private final boolean mPersistToDiskRecoveryProof;
public AppSearchConfigImpl(@NonNull LimitConfig limitConfig,
@NonNull IcingOptionsConfig icingOptionsConfig) {
this(limitConfig,
icingOptionsConfig,
/* storeParentInfoAsSyntheticProperty= */ false,
- /* shouldRetrieveParentInfo= */ false);
+ /* shouldRetrieveParentInfo= */ false,
+ /* persistToDiskRecoveryProof= */false);
}
public AppSearchConfigImpl(@NonNull LimitConfig limitConfig,
@NonNull IcingOptionsConfig icingOptionsConfig,
boolean storeParentInfoAsSyntheticProperty,
- boolean shouldRetrieveParentInfo) {
+ boolean shouldRetrieveParentInfo,
+ boolean persistToDiskRecoveryProof) {
mLimitConfig = limitConfig;
mIcingOptionsConfig = icingOptionsConfig;
mStoreParentInfoAsSyntheticProperty = storeParentInfoAsSyntheticProperty;
mShouldRetrieveParentInfo = shouldRetrieveParentInfo;
+ mPersistToDiskRecoveryProof = persistToDiskRecoveryProof;
}
@Override
@@ -163,4 +169,10 @@
public long getOrphanBlobTimeToLiveMs() {
return mIcingOptionsConfig.getOrphanBlobTimeToLiveMs();
}
+
+ @Override
+ public PersistType. @NonNull Code getLightweightPersistType() {
+ return mPersistToDiskRecoveryProof ?
+ PersistType.Code.RECOVERY_PROOF : PersistType.Code.LITE;
+ }
}
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
index 6cb8f15..1dfb53a 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
@@ -503,6 +503,13 @@
}
/**
+ * Returns the instance of AppSearchConfig used by this instance of AppSearchImpl.
+ */
+ public @NonNull AppSearchConfig getConfig() {
+ return mConfig;
+ }
+
+ /**
* Updates the AppSearch schema for this app.
*
* <p>This method belongs to mutate group.
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
index 5938407..d3377ee 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
@@ -64,13 +64,16 @@
final String mDatabaseName;
final Executor mExecutor;
final @Nullable AppSearchLogger mLogger;
+ final boolean mPersistToDiskRecoveryProof;
SearchContext(@NonNull Context context, @NonNull String databaseName,
- @NonNull Executor executor, @Nullable AppSearchLogger logger) {
+ @NonNull Executor executor, @Nullable AppSearchLogger logger,
+ boolean persistToDiskRecoveryProof) {
mContext = Preconditions.checkNotNull(context);
mDatabaseName = Preconditions.checkNotNull(databaseName);
mExecutor = Preconditions.checkNotNull(executor);
mLogger = logger;
+ mPersistToDiskRecoveryProof = persistToDiskRecoveryProof;
}
/**
@@ -107,6 +110,7 @@
private final String mDatabaseName;
private Executor mExecutor;
private @Nullable AppSearchLogger mLogger;
+ private boolean mPersistToDiskRecoveryProof;
/**
* Creates a {@link SearchContext.Builder} instance.
@@ -158,12 +162,32 @@
return this;
}
+ /**
+ * Sets whether AppSearch should call persistToDisk LITE or persistToDisk RECOVERY_PROOF
+ * after mutations ({@link AppSearchSession#putAsync} and
+ * {@link AppSearchSession#removeAsync}). LITE guarantees no data loss on initialization
+ * but with a recovery. RECOVERY_PROOF will guarantee no data loss and no recovery.
+ *
+ * <p>Note: This api is only added to facilitate early opt-ins by clients. It will be
+ * deprecated and then deleted (with the new 'true' behavior enabled) once this change
+ * has had sufficient time to soak.
+ *
+ * @exportToFramework:hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @NonNull
+ public Builder setPersistToDiskRecoveryProof(boolean persistToDiskRecoveryProof) {
+ mPersistToDiskRecoveryProof = persistToDiskRecoveryProof;
+ return this;
+ }
+
/** Builds a {@link SearchContext} instance. */
public @NonNull SearchContext build() {
if (mExecutor == null) {
mExecutor = EXECUTOR;
}
- return new SearchContext(mContext, mDatabaseName, mExecutor, mLogger);
+ return new SearchContext(
+ mContext, mDatabaseName, mExecutor, mLogger, mPersistToDiskRecoveryProof);
}
}
}
@@ -272,7 +296,7 @@
Preconditions.checkNotNull(context);
return FutureUtil.execute(context.mExecutor, () -> {
LocalStorage instance = getOrCreateInstance(context.mContext, context.mExecutor,
- context.mLogger);
+ context.mLogger, context.mPersistToDiskRecoveryProof);
return instance.doCreateSearchSession(context);
});
}
@@ -292,7 +316,7 @@
Preconditions.checkNotNull(context);
return FutureUtil.execute(context.mExecutor, () -> {
LocalStorage instance = getOrCreateInstance(context.mContext, context.mExecutor,
- context.mLogger);
+ context.mLogger, /*persistToDiskRecoveryProof=*/false);
return instance.doCreateGlobalSearchSession(context);
});
}
@@ -305,25 +329,41 @@
*/
@WorkerThread
@VisibleForTesting
- static @NonNull LocalStorage getOrCreateInstance(@NonNull Context context,
- @NonNull Executor executor, @Nullable AppSearchLogger logger)
+ static @NonNull LocalStorage getOrCreateInstance(
+ @NonNull Context context, @NonNull Executor executor,
+ @Nullable AppSearchLogger logger, boolean persistToDiskRecoveryProof)
throws AppSearchException {
Preconditions.checkNotNull(context);
if (sInstance == null) {
synchronized (LocalStorage.class) {
if (sInstance == null) {
- sInstance = new LocalStorage(context, executor, logger);
+ sInstance =
+ new LocalStorage(context, executor, logger, persistToDiskRecoveryProof);
}
}
}
return sInstance;
}
+ @VisibleForTesting
+ static @NonNull AppSearchConfig getConfig(@NonNull SearchContext context)
+ throws AppSearchException {
+ LocalStorage instance = getOrCreateInstance(context.mContext, context.mExecutor,
+ context.mLogger, context.mPersistToDiskRecoveryProof);
+ return instance.mAppSearchImpl.getConfig();
+ }
+
+ @VisibleForTesting
+ static void resetInstance() {
+ sInstance = null;
+ }
+
@WorkerThread
private LocalStorage(
@NonNull Context context,
@NonNull Executor executor,
- @Nullable AppSearchLogger logger)
+ @Nullable AppSearchLogger logger,
+ boolean persistToDiskRecoveryProof)
throws AppSearchException {
Preconditions.checkNotNull(context);
File icingDir = AppSearchEnvironmentFactory.getEnvironmentInstance()
@@ -342,7 +382,8 @@
new UnlimitedLimitConfig(),
new LocalStorageIcingOptionsConfig(),
/* storeParentInfoAsSyntheticProperty= */ false,
- /* shouldRetrieveParentInfo= */ true
+ /* shouldRetrieveParentInfo= */ true,
+ persistToDiskRecoveryProof
);
RevocableFileDescriptorStore revocableFileDescriptorStore = null;
if (Flags.enableBlobStore()) {
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/SearchSessionImpl.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/SearchSessionImpl.java
index 3d820bd..30d8161 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/SearchSessionImpl.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/SearchSessionImpl.java
@@ -376,7 +376,7 @@
}
// Now that the batch has been written. Persist the newly written data.
- mAppSearchImpl.persistToDisk(PersistType.Code.LITE);
+ mAppSearchImpl.persistToDisk(mAppSearchImpl.getConfig().getLightweightPersistType());
mIsMutated = true;
// Schedule a task to dispatch change notifications. See requirements for where the
@@ -605,7 +605,7 @@
}
}
// Now that the batch has been written. Persist the newly written data.
- mAppSearchImpl.persistToDisk(PersistType.Code.LITE);
+ mAppSearchImpl.persistToDisk(mAppSearchImpl.getConfig().getLightweightPersistType());
mIsMutated = true;
// Schedule a task to dispatch change notifications. See requirements for where the
// method is called documented in the method description.
@@ -636,7 +636,7 @@
mAppSearchImpl.removeByQuery(mPackageName, mDatabaseName, queryExpression,
searchSpec, removeStatsBuilder);
// Now that the batch has been written. Persist the newly written data.
- mAppSearchImpl.persistToDisk(PersistType.Code.LITE);
+ mAppSearchImpl.persistToDisk(mAppSearchImpl.getConfig().getLightweightPersistType());
mIsMutated = true;
// Schedule a task to dispatch change notifications. See requirements for where the
// method is called documented in the method description.
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStore.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStore.java
index 6d06eb5c..39e07df 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStore.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStore.java
@@ -234,7 +234,7 @@
prefixedVisibilityConfig);
}
// Now that the visibility document has been written. Persist the newly written data.
- mAppSearchImpl.persistToDisk(PersistType.Code.LITE);
+ mAppSearchImpl.persistToDisk(mAppSearchImpl.getConfig().getLightweightPersistType());
}
/**
diff --git a/appsearch/appsearch-platform-storage/api/1.1.0-beta02.txt b/appsearch/appsearch-platform-storage/api/1.1.0-beta02.txt
new file mode 100644
index 0000000..4709926
--- /dev/null
+++ b/appsearch/appsearch-platform-storage/api/1.1.0-beta02.txt
@@ -0,0 +1,34 @@
+// Signature format: 4.0
+package androidx.appsearch.platformstorage {
+
+ @RequiresApi(android.os.Build.VERSION_CODES.S) public final class PlatformStorage {
+ method @RequiresApi(android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.EnterpriseGlobalSearchSession!> createEnterpriseGlobalSearchSessionAsync(androidx.appsearch.platformstorage.PlatformStorage.GlobalSearchContext);
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GlobalSearchSession!> createGlobalSearchSessionAsync(androidx.appsearch.platformstorage.PlatformStorage.GlobalSearchContext);
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchSession!> createSearchSessionAsync(androidx.appsearch.platformstorage.PlatformStorage.SearchContext);
+ }
+
+ public static final class PlatformStorage.GlobalSearchContext {
+ method public android.content.Context getContext();
+ method public java.util.concurrent.Executor getWorkerExecutor();
+ }
+
+ public static final class PlatformStorage.GlobalSearchContext.Builder {
+ ctor public PlatformStorage.GlobalSearchContext.Builder(android.content.Context);
+ method public androidx.appsearch.platformstorage.PlatformStorage.GlobalSearchContext build();
+ method public androidx.appsearch.platformstorage.PlatformStorage.GlobalSearchContext.Builder setWorkerExecutor(java.util.concurrent.Executor);
+ }
+
+ public static final class PlatformStorage.SearchContext {
+ method public android.content.Context getContext();
+ method public String getDatabaseName();
+ method public java.util.concurrent.Executor getWorkerExecutor();
+ }
+
+ public static final class PlatformStorage.SearchContext.Builder {
+ ctor public PlatformStorage.SearchContext.Builder(android.content.Context, String);
+ method public androidx.appsearch.platformstorage.PlatformStorage.SearchContext build();
+ method public androidx.appsearch.platformstorage.PlatformStorage.SearchContext.Builder setWorkerExecutor(java.util.concurrent.Executor);
+ }
+
+}
+
diff --git a/appsearch/appsearch-platform-storage/api/res-1.1.0-beta02.txt b/appsearch/appsearch-platform-storage/api/res-1.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/appsearch/appsearch-platform-storage/api/res-1.1.0-beta02.txt
diff --git a/appsearch/appsearch-platform-storage/api/restricted_1.1.0-beta02.txt b/appsearch/appsearch-platform-storage/api/restricted_1.1.0-beta02.txt
new file mode 100644
index 0000000..4709926
--- /dev/null
+++ b/appsearch/appsearch-platform-storage/api/restricted_1.1.0-beta02.txt
@@ -0,0 +1,34 @@
+// Signature format: 4.0
+package androidx.appsearch.platformstorage {
+
+ @RequiresApi(android.os.Build.VERSION_CODES.S) public final class PlatformStorage {
+ method @RequiresApi(android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.EnterpriseGlobalSearchSession!> createEnterpriseGlobalSearchSessionAsync(androidx.appsearch.platformstorage.PlatformStorage.GlobalSearchContext);
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GlobalSearchSession!> createGlobalSearchSessionAsync(androidx.appsearch.platformstorage.PlatformStorage.GlobalSearchContext);
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchSession!> createSearchSessionAsync(androidx.appsearch.platformstorage.PlatformStorage.SearchContext);
+ }
+
+ public static final class PlatformStorage.GlobalSearchContext {
+ method public android.content.Context getContext();
+ method public java.util.concurrent.Executor getWorkerExecutor();
+ }
+
+ public static final class PlatformStorage.GlobalSearchContext.Builder {
+ ctor public PlatformStorage.GlobalSearchContext.Builder(android.content.Context);
+ method public androidx.appsearch.platformstorage.PlatformStorage.GlobalSearchContext build();
+ method public androidx.appsearch.platformstorage.PlatformStorage.GlobalSearchContext.Builder setWorkerExecutor(java.util.concurrent.Executor);
+ }
+
+ public static final class PlatformStorage.SearchContext {
+ method public android.content.Context getContext();
+ method public String getDatabaseName();
+ method public java.util.concurrent.Executor getWorkerExecutor();
+ }
+
+ public static final class PlatformStorage.SearchContext.Builder {
+ ctor public PlatformStorage.SearchContext.Builder(android.content.Context, String);
+ method public androidx.appsearch.platformstorage.PlatformStorage.SearchContext build();
+ method public androidx.appsearch.platformstorage.PlatformStorage.SearchContext.Builder setWorkerExecutor(java.util.concurrent.Executor);
+ }
+
+}
+
diff --git a/appsearch/appsearch-play-services-storage/api/1.1.0-beta02.txt b/appsearch/appsearch-play-services-storage/api/1.1.0-beta02.txt
new file mode 100644
index 0000000..af62eee
--- /dev/null
+++ b/appsearch/appsearch-play-services-storage/api/1.1.0-beta02.txt
@@ -0,0 +1,33 @@
+// Signature format: 4.0
+package androidx.appsearch.playservicesstorage {
+
+ public final class PlayServicesStorage {
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GlobalSearchSession!> createGlobalSearchSessionAsync(androidx.appsearch.playservicesstorage.PlayServicesStorage.GlobalSearchContext);
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchSession!> createSearchSessionAsync(androidx.appsearch.playservicesstorage.PlayServicesStorage.SearchContext);
+ }
+
+ public static final class PlayServicesStorage.GlobalSearchContext {
+ method public android.content.Context getContext();
+ method public java.util.concurrent.Executor getWorkerExecutor();
+ }
+
+ public static final class PlayServicesStorage.GlobalSearchContext.Builder {
+ ctor public PlayServicesStorage.GlobalSearchContext.Builder(android.content.Context);
+ method public androidx.appsearch.playservicesstorage.PlayServicesStorage.GlobalSearchContext build();
+ method public androidx.appsearch.playservicesstorage.PlayServicesStorage.GlobalSearchContext.Builder setWorkerExecutor(java.util.concurrent.Executor);
+ }
+
+ public static final class PlayServicesStorage.SearchContext {
+ method public android.content.Context getContext();
+ method public String getDatabaseName();
+ method public java.util.concurrent.Executor getWorkerExecutor();
+ }
+
+ public static final class PlayServicesStorage.SearchContext.Builder {
+ ctor public PlayServicesStorage.SearchContext.Builder(android.content.Context, String);
+ method public androidx.appsearch.playservicesstorage.PlayServicesStorage.SearchContext build();
+ method public androidx.appsearch.playservicesstorage.PlayServicesStorage.SearchContext.Builder setWorkerExecutor(java.util.concurrent.Executor);
+ }
+
+}
+
diff --git a/appsearch/appsearch-play-services-storage/api/res-1.1.0-beta02.txt b/appsearch/appsearch-play-services-storage/api/res-1.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/appsearch/appsearch-play-services-storage/api/res-1.1.0-beta02.txt
diff --git a/appsearch/appsearch-play-services-storage/api/restricted_1.1.0-beta02.txt b/appsearch/appsearch-play-services-storage/api/restricted_1.1.0-beta02.txt
new file mode 100644
index 0000000..af62eee
--- /dev/null
+++ b/appsearch/appsearch-play-services-storage/api/restricted_1.1.0-beta02.txt
@@ -0,0 +1,33 @@
+// Signature format: 4.0
+package androidx.appsearch.playservicesstorage {
+
+ public final class PlayServicesStorage {
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GlobalSearchSession!> createGlobalSearchSessionAsync(androidx.appsearch.playservicesstorage.PlayServicesStorage.GlobalSearchContext);
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchSession!> createSearchSessionAsync(androidx.appsearch.playservicesstorage.PlayServicesStorage.SearchContext);
+ }
+
+ public static final class PlayServicesStorage.GlobalSearchContext {
+ method public android.content.Context getContext();
+ method public java.util.concurrent.Executor getWorkerExecutor();
+ }
+
+ public static final class PlayServicesStorage.GlobalSearchContext.Builder {
+ ctor public PlayServicesStorage.GlobalSearchContext.Builder(android.content.Context);
+ method public androidx.appsearch.playservicesstorage.PlayServicesStorage.GlobalSearchContext build();
+ method public androidx.appsearch.playservicesstorage.PlayServicesStorage.GlobalSearchContext.Builder setWorkerExecutor(java.util.concurrent.Executor);
+ }
+
+ public static final class PlayServicesStorage.SearchContext {
+ method public android.content.Context getContext();
+ method public String getDatabaseName();
+ method public java.util.concurrent.Executor getWorkerExecutor();
+ }
+
+ public static final class PlayServicesStorage.SearchContext.Builder {
+ ctor public PlayServicesStorage.SearchContext.Builder(android.content.Context, String);
+ method public androidx.appsearch.playservicesstorage.PlayServicesStorage.SearchContext build();
+ method public androidx.appsearch.playservicesstorage.PlayServicesStorage.SearchContext.Builder setWorkerExecutor(java.util.concurrent.Executor);
+ }
+
+}
+
diff --git a/appsearch/appsearch/api/1.1.0-beta02.txt b/appsearch/appsearch/api/1.1.0-beta02.txt
new file mode 100644
index 0000000..1527f14
--- /dev/null
+++ b/appsearch/appsearch/api/1.1.0-beta02.txt
@@ -0,0 +1,1253 @@
+// Signature format: 4.0
+package androidx.appsearch.annotation {
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Document {
+ method public abstract String name() default "";
+ method public abstract Class<? extends java.lang.Object!>[] parent() default {};
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.BooleanProperty {
+ method public abstract String name() default "";
+ method public abstract boolean required() default false;
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) public static @interface Document.BuilderProducer {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.BytesProperty {
+ method public abstract String name() default "";
+ method public abstract boolean required() default false;
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.CreationTimestampMillis {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.DocumentProperty {
+ method public abstract boolean indexNestedProperties() default false;
+ method public abstract String[] indexableNestedPropertiesList() default {};
+ method public abstract boolean inheritIndexableNestedPropertiesFromSuperclass() default false;
+ method public abstract String name() default "";
+ method public abstract boolean required() default false;
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.DoubleProperty {
+ method public abstract String name() default "";
+ method public abstract boolean required() default false;
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.EmbeddingProperty {
+ method public abstract int indexingType() default androidx.appsearch.app.AppSearchSchema.EmbeddingPropertyConfig.INDEXING_TYPE_NONE;
+ method public abstract String name() default "";
+ method public abstract boolean required() default false;
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.Id {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.LongProperty {
+ method public abstract int indexingType() default androidx.appsearch.app.AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE;
+ method public abstract String name() default "";
+ method public abstract boolean required() default false;
+ method public abstract Class<? extends androidx.appsearch.app.LongSerializer<? extends java.lang.Object!>!> serializer() default androidx.appsearch.annotation.Document.LongProperty.DefaultSerializer.class;
+ }
+
+ public static final class Document.LongProperty.DefaultSerializer implements androidx.appsearch.app.LongSerializer<java.lang.Long!> {
+ ctor public Document.LongProperty.DefaultSerializer();
+ method public Long deserialize(long);
+ method public long serialize(Long);
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.Namespace {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.Score {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.StringProperty {
+ method public abstract int indexingType() default androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE;
+ method public abstract int joinableValueType() default androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE;
+ method public abstract String name() default "";
+ method public abstract boolean required() default false;
+ method public abstract Class<? extends androidx.appsearch.app.StringSerializer<? extends java.lang.Object!>!> serializer() default androidx.appsearch.annotation.Document.StringProperty.DefaultSerializer.class;
+ method public abstract int tokenizerType() default androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN;
+ }
+
+ public static final class Document.StringProperty.DefaultSerializer implements androidx.appsearch.app.StringSerializer<java.lang.String!> {
+ ctor public Document.StringProperty.DefaultSerializer();
+ method public String deserialize(String);
+ method public String serialize(String);
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.TtlMillis {
+ }
+
+}
+
+package androidx.appsearch.app {
+
+ public final class AppSearchBatchResult<KeyType, ValueType> {
+ method public java.util.Map<KeyType!,androidx.appsearch.app.AppSearchResult<ValueType!>!> getAll();
+ method public java.util.Map<KeyType!,androidx.appsearch.app.AppSearchResult<ValueType!>!> getFailures();
+ method public java.util.Map<KeyType!,ValueType!> getSuccesses();
+ method public boolean isSuccess();
+ }
+
+ public static final class AppSearchBatchResult.Builder<KeyType, ValueType> {
+ ctor public AppSearchBatchResult.Builder();
+ ctor @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public AppSearchBatchResult.Builder(androidx.appsearch.app.AppSearchBatchResult<KeyType!,ValueType!>);
+ method public androidx.appsearch.app.AppSearchBatchResult<KeyType!,ValueType!> build();
+ method public androidx.appsearch.app.AppSearchBatchResult.Builder<KeyType!,ValueType!> setFailure(KeyType, int, String?);
+ method public androidx.appsearch.app.AppSearchBatchResult.Builder<KeyType!,ValueType!> setResult(KeyType, androidx.appsearch.app.AppSearchResult<ValueType!>);
+ method public androidx.appsearch.app.AppSearchBatchResult.Builder<KeyType!,ValueType!> setSuccess(KeyType, ValueType?);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class AppSearchBlobHandle {
+ method public static androidx.appsearch.app.AppSearchBlobHandle createWithSha256(byte[], String, String, String);
+ method public String getDatabaseName();
+ method public String getNamespace();
+ method public String getPackageName();
+ method public byte[] getSha256Digest();
+ }
+
+ @AnyThread public abstract class AppSearchDocumentClassMap {
+ ctor public AppSearchDocumentClassMap();
+ method @WorkerThread public static java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getGlobalMap();
+ method protected abstract java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getMap();
+ }
+
+ public final class AppSearchResult<ValueType> {
+ method public String? getErrorMessage();
+ method public int getResultCode();
+ method public ValueType? getResultValue();
+ method public boolean isSuccess();
+ method public static <ValueType> androidx.appsearch.app.AppSearchResult<ValueType!> newFailedResult(int, String?);
+ method public static <ValueType> androidx.appsearch.app.AppSearchResult<ValueType!> newSuccessfulResult(ValueType?);
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final int RESULT_ALREADY_EXISTS = 12; // 0xc
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final int RESULT_DENIED = 9; // 0x9
+ field public static final int RESULT_INTERNAL_ERROR = 2; // 0x2
+ field public static final int RESULT_INVALID_ARGUMENT = 3; // 0x3
+ field public static final int RESULT_INVALID_SCHEMA = 7; // 0x7
+ field public static final int RESULT_IO_ERROR = 4; // 0x4
+ field public static final int RESULT_NOT_FOUND = 6; // 0x6
+ field public static final int RESULT_OK = 0; // 0x0
+ field public static final int RESULT_OUT_OF_SPACE = 5; // 0x5
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final int RESULT_RATE_LIMITED = 10; // 0xa
+ field public static final int RESULT_SECURITY_ERROR = 8; // 0x8
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final int RESULT_TIMED_OUT = 11; // 0xb
+ field public static final int RESULT_UNKNOWN_ERROR = 1; // 0x1
+ }
+
+ public final class AppSearchSchema {
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public String getDescription();
+ method public java.util.List<java.lang.String!> getParentTypes();
+ method public java.util.List<androidx.appsearch.app.AppSearchSchema.PropertyConfig!> getProperties();
+ method public String getSchemaType();
+ }
+
+ @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name="BLOB_STORAGE") @androidx.appsearch.app.ExperimentalAppSearchApi public static final class AppSearchSchema.BlobHandlePropertyConfig extends androidx.appsearch.app.AppSearchSchema.PropertyConfig {
+ }
+
+ public static final class AppSearchSchema.BlobHandlePropertyConfig.Builder {
+ ctor public AppSearchSchema.BlobHandlePropertyConfig.Builder(String);
+ method public androidx.appsearch.app.AppSearchSchema.BlobHandlePropertyConfig build();
+ method public androidx.appsearch.app.AppSearchSchema.BlobHandlePropertyConfig.Builder setCardinality(int);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SET_DESCRIPTION) public androidx.appsearch.app.AppSearchSchema.BlobHandlePropertyConfig.Builder setDescription(String);
+ }
+
+ public static final class AppSearchSchema.BooleanPropertyConfig extends androidx.appsearch.app.AppSearchSchema.PropertyConfig {
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public boolean isScoringEnabled();
+ }
+
+ public static final class AppSearchSchema.BooleanPropertyConfig.Builder {
+ ctor public AppSearchSchema.BooleanPropertyConfig.Builder(String);
+ method public androidx.appsearch.app.AppSearchSchema.BooleanPropertyConfig build();
+ method public androidx.appsearch.app.AppSearchSchema.BooleanPropertyConfig.Builder setCardinality(int);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SET_DESCRIPTION) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.BooleanPropertyConfig.Builder setDescription(String);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SCORABLE_PROPERTY_CONFIG) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.BooleanPropertyConfig.Builder setScoringEnabled(boolean);
+ }
+
+ public static final class AppSearchSchema.Builder {
+ ctor @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public AppSearchSchema.Builder(androidx.appsearch.app.AppSearchSchema);
+ ctor public AppSearchSchema.Builder(String);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_ADD_PARENT_TYPE) public androidx.appsearch.app.AppSearchSchema.Builder addParentType(String);
+ method public androidx.appsearch.app.AppSearchSchema.Builder addProperty(androidx.appsearch.app.AppSearchSchema.PropertyConfig);
+ method public androidx.appsearch.app.AppSearchSchema build();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.Builder clearParentTypes();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.Builder clearProperties();
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SET_DESCRIPTION) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.Builder setDescription(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.Builder setSchemaType(String);
+ }
+
+ public static final class AppSearchSchema.BytesPropertyConfig extends androidx.appsearch.app.AppSearchSchema.PropertyConfig {
+ }
+
+ public static final class AppSearchSchema.BytesPropertyConfig.Builder {
+ ctor public AppSearchSchema.BytesPropertyConfig.Builder(String);
+ method public androidx.appsearch.app.AppSearchSchema.BytesPropertyConfig build();
+ method public androidx.appsearch.app.AppSearchSchema.BytesPropertyConfig.Builder setCardinality(int);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SET_DESCRIPTION) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.BytesPropertyConfig.Builder setDescription(String);
+ }
+
+ public static final class AppSearchSchema.DocumentPropertyConfig extends androidx.appsearch.app.AppSearchSchema.PropertyConfig {
+ method public java.util.List<java.lang.String!> getIndexableNestedProperties();
+ method public String getSchemaType();
+ method public boolean shouldIndexNestedProperties();
+ }
+
+ public static final class AppSearchSchema.DocumentPropertyConfig.Builder {
+ ctor public AppSearchSchema.DocumentPropertyConfig.Builder(String, String);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES) public androidx.appsearch.app.AppSearchSchema.DocumentPropertyConfig.Builder addIndexableNestedProperties(java.lang.String!...);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES) public androidx.appsearch.app.AppSearchSchema.DocumentPropertyConfig.Builder addIndexableNestedProperties(java.util.Collection<java.lang.String!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES) public androidx.appsearch.app.AppSearchSchema.DocumentPropertyConfig.Builder addIndexableNestedPropertyPaths(androidx.appsearch.app.PropertyPath!...);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES) public androidx.appsearch.app.AppSearchSchema.DocumentPropertyConfig.Builder addIndexableNestedPropertyPaths(java.util.Collection<androidx.appsearch.app.PropertyPath!>);
+ method public androidx.appsearch.app.AppSearchSchema.DocumentPropertyConfig build();
+ method public androidx.appsearch.app.AppSearchSchema.DocumentPropertyConfig.Builder setCardinality(int);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SET_DESCRIPTION) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.DocumentPropertyConfig.Builder setDescription(String);
+ method public androidx.appsearch.app.AppSearchSchema.DocumentPropertyConfig.Builder setShouldIndexNestedProperties(boolean);
+ }
+
+ public static final class AppSearchSchema.DoublePropertyConfig extends androidx.appsearch.app.AppSearchSchema.PropertyConfig {
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public boolean isScoringEnabled();
+ }
+
+ public static final class AppSearchSchema.DoublePropertyConfig.Builder {
+ ctor public AppSearchSchema.DoublePropertyConfig.Builder(String);
+ method public androidx.appsearch.app.AppSearchSchema.DoublePropertyConfig build();
+ method public androidx.appsearch.app.AppSearchSchema.DoublePropertyConfig.Builder setCardinality(int);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SET_DESCRIPTION) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.DoublePropertyConfig.Builder setDescription(String);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SCORABLE_PROPERTY_CONFIG) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.DoublePropertyConfig.Builder setScoringEnabled(boolean);
+ }
+
+ @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public static final class AppSearchSchema.EmbeddingPropertyConfig extends androidx.appsearch.app.AppSearchSchema.PropertyConfig {
+ method public int getIndexingType();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public int getQuantizationType();
+ field public static final int INDEXING_TYPE_NONE = 0; // 0x0
+ field public static final int INDEXING_TYPE_SIMILARITY = 1; // 0x1
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final int QUANTIZATION_TYPE_8_BIT = 1; // 0x1
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final int QUANTIZATION_TYPE_NONE = 0; // 0x0
+ }
+
+ public static final class AppSearchSchema.EmbeddingPropertyConfig.Builder {
+ ctor public AppSearchSchema.EmbeddingPropertyConfig.Builder(String);
+ method public androidx.appsearch.app.AppSearchSchema.EmbeddingPropertyConfig build();
+ method public androidx.appsearch.app.AppSearchSchema.EmbeddingPropertyConfig.Builder setCardinality(int);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SET_DESCRIPTION) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.EmbeddingPropertyConfig.Builder setDescription(String);
+ method public androidx.appsearch.app.AppSearchSchema.EmbeddingPropertyConfig.Builder setIndexingType(int);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_QUANTIZATION) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.EmbeddingPropertyConfig.Builder setQuantizationType(@SuppressCompatibility int);
+ }
+
+ public static final class AppSearchSchema.LongPropertyConfig extends androidx.appsearch.app.AppSearchSchema.PropertyConfig {
+ method public int getIndexingType();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public boolean isScoringEnabled();
+ field public static final int INDEXING_TYPE_NONE = 0; // 0x0
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.NUMERIC_SEARCH) public static final int INDEXING_TYPE_RANGE = 1; // 0x1
+ }
+
+ public static final class AppSearchSchema.LongPropertyConfig.Builder {
+ ctor public AppSearchSchema.LongPropertyConfig.Builder(String);
+ method public androidx.appsearch.app.AppSearchSchema.LongPropertyConfig build();
+ method public androidx.appsearch.app.AppSearchSchema.LongPropertyConfig.Builder setCardinality(int);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SET_DESCRIPTION) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.LongPropertyConfig.Builder setDescription(String);
+ method public androidx.appsearch.app.AppSearchSchema.LongPropertyConfig.Builder setIndexingType(int);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SCORABLE_PROPERTY_CONFIG) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.LongPropertyConfig.Builder setScoringEnabled(boolean);
+ }
+
+ public abstract static class AppSearchSchema.PropertyConfig {
+ method public int getCardinality();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public String getDescription();
+ method public String getName();
+ field public static final int CARDINALITY_OPTIONAL = 2; // 0x2
+ field public static final int CARDINALITY_REPEATED = 1; // 0x1
+ field public static final int CARDINALITY_REQUIRED = 3; // 0x3
+ }
+
+ public static final class AppSearchSchema.StringPropertyConfig extends androidx.appsearch.app.AppSearchSchema.PropertyConfig {
+ method public int getIndexingType();
+ method public int getJoinableValueType();
+ method public int getTokenizerType();
+ field public static final int INDEXING_TYPE_EXACT_TERMS = 1; // 0x1
+ field public static final int INDEXING_TYPE_NONE = 0; // 0x0
+ field public static final int INDEXING_TYPE_PREFIXES = 2; // 0x2
+ field public static final int JOINABLE_VALUE_TYPE_NONE = 0; // 0x0
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.JOIN_SPEC_AND_QUALIFIED_ID) public static final int JOINABLE_VALUE_TYPE_QUALIFIED_ID = 1; // 0x1
+ field public static final int TOKENIZER_TYPE_NONE = 0; // 0x0
+ field public static final int TOKENIZER_TYPE_PLAIN = 1; // 0x1
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.TOKENIZER_TYPE_RFC822) public static final int TOKENIZER_TYPE_RFC822 = 3; // 0x3
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.VERBATIM_SEARCH) public static final int TOKENIZER_TYPE_VERBATIM = 2; // 0x2
+ }
+
+ public static final class AppSearchSchema.StringPropertyConfig.Builder {
+ ctor public AppSearchSchema.StringPropertyConfig.Builder(String);
+ method public androidx.appsearch.app.AppSearchSchema.StringPropertyConfig build();
+ method public androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.Builder setCardinality(int);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SET_DESCRIPTION) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.Builder setDescription(String);
+ method public androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.Builder setIndexingType(int);
+ method public androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.Builder setJoinableValueType(int);
+ method public androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.Builder setTokenizerType(int);
+ }
+
+ public interface AppSearchSession extends java.io.Closeable {
+ method public void close();
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name="BLOB_STORAGE") @androidx.appsearch.app.ExperimentalAppSearchApi public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.CommitBlobResponse!> commitBlobAsync(java.util.Set<androidx.appsearch.app.AppSearchBlobHandle!>);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getByDocumentIdAsync(androidx.appsearch.app.GetByDocumentIdRequest);
+ method public androidx.appsearch.app.Features getFeatures();
+ method public com.google.common.util.concurrent.ListenableFuture<java.util.Set<java.lang.String!>!> getNamespacesAsync();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchemaAsync();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.StorageInfo!> getStorageInfoAsync();
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name="BLOB_STORAGE") @androidx.appsearch.app.ExperimentalAppSearchApi public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.OpenBlobForReadResponse!> openBlobForReadAsync(java.util.Set<androidx.appsearch.app.AppSearchBlobHandle!>);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name="BLOB_STORAGE") @androidx.appsearch.app.ExperimentalAppSearchApi public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.OpenBlobForWriteResponse!> openBlobForWriteAsync(java.util.Set<androidx.appsearch.app.AppSearchBlobHandle!>);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,java.lang.Void!>!> putAsync(androidx.appsearch.app.PutDocumentsRequest);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,java.lang.Void!>!> removeAsync(androidx.appsearch.app.RemoveByDocumentIdRequest);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> removeAsync(String, androidx.appsearch.app.SearchSpec);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name="BLOB_STORAGE") @androidx.appsearch.app.ExperimentalAppSearchApi public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.RemoveBlobResponse!> removeBlobAsync(java.util.Set<androidx.appsearch.app.AppSearchBlobHandle!>);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> reportUsageAsync(androidx.appsearch.app.ReportUsageRequest);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> requestFlushAsync();
+ method public androidx.appsearch.app.SearchResults search(String, androidx.appsearch.app.SearchSpec);
+ method public com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.appsearch.app.SearchSuggestionResult!>!> searchSuggestionAsync(String, androidx.appsearch.app.SearchSuggestionSpec);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name="BLOB_STORAGE") @androidx.appsearch.app.ExperimentalAppSearchApi public default com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setBlobVisibilityAsync(androidx.appsearch.app.SetBlobVisibilityRequest);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.SetSchemaResponse!> setSchemaAsync(androidx.appsearch.app.SetSchemaRequest);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class CommitBlobResponse {
+ ctor public CommitBlobResponse(androidx.appsearch.app.AppSearchBatchResult<androidx.appsearch.app.AppSearchBlobHandle!,java.lang.Void!>);
+ method public androidx.appsearch.app.AppSearchBatchResult<androidx.appsearch.app.AppSearchBlobHandle!,java.lang.Void!> getResult();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<androidx.appsearch.app.CommitBlobResponse!> CREATOR;
+ }
+
+ public interface DocumentClassFactory<T> {
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public T fromGenericDocument(androidx.appsearch.app.GenericDocument, androidx.appsearch.app.DocumentClassMappingContext) throws androidx.appsearch.exceptions.AppSearchException;
+ method public java.util.List<java.lang.Class<? extends java.lang.Object!>!> getDependencyDocumentClasses() throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.AppSearchSchema getSchema() throws androidx.appsearch.exceptions.AppSearchException;
+ method public String getSchemaName();
+ method public androidx.appsearch.app.GenericDocument toGenericDocument(T) throws androidx.appsearch.exceptions.AppSearchException;
+ }
+
+ public class DocumentClassMappingContext {
+ ctor @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public DocumentClassMappingContext(java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!>?, java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!>?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getDocumentClassMap();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getParentTypeMap();
+ }
+
+ @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public final class EmbeddingVector {
+ ctor public EmbeddingVector(float[], String);
+ method public String getModelSignature();
+ method public float[] getValues();
+ }
+
+ @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ENTERPRISE_GLOBAL_SEARCH_SESSION) public interface EnterpriseGlobalSearchSession {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getByDocumentIdAsync(String, String, androidx.appsearch.app.GetByDocumentIdRequest);
+ method public androidx.appsearch.app.Features getFeatures();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchemaAsync(String, String);
+ method public androidx.appsearch.app.SearchResults search(String, androidx.appsearch.app.SearchSpec);
+ }
+
+ @SuppressCompatibility @RequiresOptIn(level=androidx.annotation.RequiresOptIn.Level.ERROR) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalAppSearchApi {
+ }
+
+ public interface Features {
+ method public int getMaxIndexedProperties();
+ method public boolean isFeatureSupported(String);
+ field public static final String ADD_PERMISSIONS_AND_GET_VISIBILITY = "ADD_PERMISSIONS_AND_GET_VISIBILITY";
+ field public static final String ENTERPRISE_GLOBAL_SEARCH_SESSION = "ENTERPRISE_GLOBAL_SEARCH_SESSION";
+ field public static final String GLOBAL_SEARCH_SESSION_GET_BY_ID = "GLOBAL_SEARCH_SESSION_GET_BY_ID";
+ field public static final String GLOBAL_SEARCH_SESSION_GET_SCHEMA = "GLOBAL_SEARCH_SESSION_GET_SCHEMA";
+ field public static final String GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK = "GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK";
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String INDEXER_MOBILE_APPLICATIONS = "INDEXER_MOBILE_APPLICATIONS";
+ field public static final String JOIN_SPEC_AND_QUALIFIED_ID = "JOIN_SPEC_AND_QUALIFIED_ID";
+ field public static final String LIST_FILTER_HAS_PROPERTY_FUNCTION = "LIST_FILTER_HAS_PROPERTY_FUNCTION";
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String LIST_FILTER_MATCH_SCORE_EXPRESSION_FUNCTION = "LIST_FILTER_MATCH_SCORE_EXPRESSION_FUNCTION";
+ field public static final String LIST_FILTER_QUERY_LANGUAGE = "LIST_FILTER_QUERY_LANGUAGE";
+ field public static final String NUMERIC_SEARCH = "NUMERIC_SEARCH";
+ field public static final String SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES = "SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES";
+ field public static final String SCHEMA_ADD_PARENT_TYPE = "SCHEMA_ADD_PARENT_TYPE";
+ field public static final String SCHEMA_EMBEDDING_PROPERTY_CONFIG = "SCHEMA_EMBEDDING_PROPERTY_CONFIG";
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String SCHEMA_EMBEDDING_QUANTIZATION = "SCHEMA_EMBEDDING_QUANTIZATION";
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String SCHEMA_SCORABLE_PROPERTY_CONFIG = "SCHEMA_SCORABLE_PROPERTY_CONFIG";
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String SCHEMA_SET_DESCRIPTION = "SCHEMA_SET_DESCRIPTION";
+ field public static final String SEARCH_RESULT_MATCH_INFO_SUBMATCH = "SEARCH_RESULT_MATCH_INFO_SUBMATCH";
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String SEARCH_RESULT_PARENT_TYPES = "SEARCH_RESULT_PARENT_TYPES";
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String SEARCH_SPEC_ADD_FILTER_DOCUMENT_IDS = "SEARCH_SPEC_ADD_FILTER_DOCUMENT_IDS";
+ field public static final String SEARCH_SPEC_ADD_FILTER_PROPERTIES = "SEARCH_SPEC_ADD_FILTER_PROPERTIES";
+ field public static final String SEARCH_SPEC_ADD_INFORMATIONAL_RANKING_EXPRESSIONS = "SEARCH_SPEC_ADD_INFORMATIONAL_RANKING_EXPRESSIONS";
+ field public static final String SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION = "SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION";
+ field public static final String SEARCH_SPEC_GROUPING_TYPE_PER_SCHEMA = "SEARCH_SPEC_GROUPING_TYPE_PER_SCHEMA";
+ field public static final String SEARCH_SPEC_PROPERTY_WEIGHTS = "SEARCH_SPEC_PROPERTY_WEIGHTS";
+ field public static final String SEARCH_SPEC_SEARCH_STRING_PARAMETERS = "SEARCH_SPEC_SEARCH_STRING_PARAMETERS";
+ field public static final String SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG = "SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG";
+ field public static final String SEARCH_SUGGESTION = "SEARCH_SUGGESTION";
+ field public static final String SET_SCHEMA_CIRCULAR_REFERENCES = "SET_SCHEMA_CIRCULAR_REFERENCES";
+ field public static final String SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG = "SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG";
+ field public static final String SET_SCHEMA_REQUEST_SET_PUBLICLY_VISIBLE = "SET_SCHEMA_REQUEST_SET_PUBLICLY_VISIBLE";
+ field public static final String TOKENIZER_TYPE_RFC822 = "TOKENIZER_TYPE_RFC822";
+ field public static final String VERBATIM_SEARCH = "VERBATIM_SEARCH";
+ }
+
+ public class GenericDocument {
+ ctor protected GenericDocument(androidx.appsearch.app.GenericDocument);
+ method public static androidx.appsearch.app.GenericDocument fromDocumentClass(Object) throws androidx.appsearch.exceptions.AppSearchException;
+ method public long getCreationTimestampMillis();
+ method public String getId();
+ method public String getNamespace();
+ method public Object? getProperty(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchBlobHandle? getPropertyBlobHandle(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchBlobHandle![]? getPropertyBlobHandleArray(String);
+ method public boolean getPropertyBoolean(String);
+ method public boolean[]? getPropertyBooleanArray(String);
+ method public byte[]? getPropertyBytes(String);
+ method public byte[]![]? getPropertyBytesArray(String);
+ method public androidx.appsearch.app.GenericDocument? getPropertyDocument(String);
+ method public androidx.appsearch.app.GenericDocument![]? getPropertyDocumentArray(String);
+ method public double getPropertyDouble(String);
+ method public double[]? getPropertyDoubleArray(String);
+ method public androidx.appsearch.app.EmbeddingVector? getPropertyEmbedding(String);
+ method public androidx.appsearch.app.EmbeddingVector![]? getPropertyEmbeddingArray(String);
+ method public long getPropertyLong(String);
+ method public long[]? getPropertyLongArray(String);
+ method public java.util.Set<java.lang.String!> getPropertyNames();
+ method public String? getPropertyString(String);
+ method public String![]? getPropertyStringArray(String);
+ method public String getSchemaType();
+ method public int getScore();
+ method public long getTtlMillis();
+ method public <T> T toDocumentClass(Class<T!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public <T> T toDocumentClass(Class<T!>, androidx.appsearch.app.DocumentClassMappingContext) throws androidx.appsearch.exceptions.AppSearchException;
+ }
+
+ public static class GenericDocument.Builder<BuilderType extends androidx.appsearch.app.GenericDocument.Builder> {
+ ctor public GenericDocument.Builder(androidx.appsearch.app.GenericDocument);
+ ctor public GenericDocument.Builder(String, String, String);
+ method public androidx.appsearch.app.GenericDocument build();
+ method public BuilderType clearProperty(String);
+ method public BuilderType setCreationTimestampMillis(long);
+ method public BuilderType setId(String);
+ method public BuilderType setNamespace(String);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name="BLOB_STORAGE") @androidx.appsearch.app.ExperimentalAppSearchApi public BuilderType setPropertyBlobHandle(String, androidx.appsearch.app.AppSearchBlobHandle!...);
+ method public BuilderType setPropertyBoolean(String, boolean...);
+ method public BuilderType setPropertyBytes(String, byte[]!...);
+ method public BuilderType setPropertyDocument(String, androidx.appsearch.app.GenericDocument!...);
+ method public BuilderType setPropertyDouble(String, double...);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public BuilderType setPropertyEmbedding(String, androidx.appsearch.app.EmbeddingVector!...);
+ method public BuilderType setPropertyLong(String, long...);
+ method public BuilderType setPropertyString(String, java.lang.String!...);
+ method public BuilderType setSchemaType(String);
+ method public BuilderType setScore(@IntRange(from=0, to=java.lang.Integer.MAX_VALUE) int);
+ method public BuilderType setTtlMillis(long);
+ }
+
+ public final class GetByDocumentIdRequest {
+ method public java.util.Set<java.lang.String!> getIds();
+ method public String getNamespace();
+ method public java.util.Map<java.lang.String!,java.util.List<androidx.appsearch.app.PropertyPath!>!> getProjectionPaths();
+ method public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getProjections();
+ field public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*";
+ }
+
+ public static final class GetByDocumentIdRequest.Builder {
+ ctor public GetByDocumentIdRequest.Builder(String);
+ method public androidx.appsearch.app.GetByDocumentIdRequest.Builder addIds(java.lang.String!...);
+ method public androidx.appsearch.app.GetByDocumentIdRequest.Builder addIds(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.GetByDocumentIdRequest.Builder addProjection(String, java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.GetByDocumentIdRequest.Builder addProjectionPaths(String, java.util.Collection<androidx.appsearch.app.PropertyPath!>);
+ method public androidx.appsearch.app.GetByDocumentIdRequest build();
+ }
+
+ public final class GetSchemaResponse {
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public java.util.Map<java.lang.String!,androidx.appsearch.app.PackageIdentifier!> getPubliclyVisibleSchemas();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public java.util.Map<java.lang.String!,java.util.Set<java.util.Set<java.lang.Integer!>!>!> getRequiredPermissionsForSchemaTypeVisibility();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public java.util.Set<java.lang.String!> getSchemaTypesNotDisplayedBySystem();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public java.util.Map<java.lang.String!,java.util.Set<androidx.appsearch.app.SchemaVisibilityConfig!>!> getSchemaTypesVisibleToConfigs();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public java.util.Map<java.lang.String!,java.util.Set<androidx.appsearch.app.PackageIdentifier!>!> getSchemaTypesVisibleToPackages();
+ method public java.util.Set<androidx.appsearch.app.AppSearchSchema!> getSchemas();
+ method @IntRange(from=0) public int getVersion();
+ }
+
+ public static final class GetSchemaResponse.Builder {
+ ctor public GetSchemaResponse.Builder();
+ ctor @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public GetSchemaResponse.Builder(androidx.appsearch.app.GetSchemaResponse);
+ method public androidx.appsearch.app.GetSchemaResponse.Builder addSchema(androidx.appsearch.app.AppSearchSchema);
+ method public androidx.appsearch.app.GetSchemaResponse.Builder addSchemaTypeNotDisplayedBySystem(String);
+ method public androidx.appsearch.app.GetSchemaResponse build();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.GetSchemaResponse.Builder clearPubliclyVisibleSchema(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.GetSchemaResponse.Builder clearRequiredPermissionsForSchemaTypeVisibility(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.GetSchemaResponse.Builder clearSchemaTypeNotDisplayedBySystem(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.GetSchemaResponse.Builder clearSchemaTypeVisibleToConfigs(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.GetSchemaResponse.Builder clearSchemaTypeVisibleToPackages(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.GetSchemaResponse.Builder clearSchemas();
+ method public androidx.appsearch.app.GetSchemaResponse.Builder setPubliclyVisibleSchema(String, androidx.appsearch.app.PackageIdentifier);
+ method public androidx.appsearch.app.GetSchemaResponse.Builder setRequiredPermissionsForSchemaTypeVisibility(String, java.util.Set<java.util.Set<java.lang.Integer!>!>);
+ method public androidx.appsearch.app.GetSchemaResponse.Builder setSchemaTypeVisibleToConfigs(String, java.util.Set<androidx.appsearch.app.SchemaVisibilityConfig!>);
+ method public androidx.appsearch.app.GetSchemaResponse.Builder setSchemaTypeVisibleToPackages(String, java.util.Set<androidx.appsearch.app.PackageIdentifier!>);
+ method public androidx.appsearch.app.GetSchemaResponse.Builder setVersion(@IntRange(from=0) int);
+ method public androidx.appsearch.app.GetSchemaResponse.Builder setVisibilitySettingSupported(boolean);
+ }
+
+ public interface GlobalSearchSession extends java.io.Closeable {
+ method public void close();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_GET_BY_ID) public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getByDocumentIdAsync(String, String, androidx.appsearch.app.GetByDocumentIdRequest);
+ method public androidx.appsearch.app.Features getFeatures();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_GET_SCHEMA) public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchemaAsync(String, String);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name="BLOB_STORAGE") @androidx.appsearch.app.ExperimentalAppSearchApi public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.OpenBlobForReadResponse!> openBlobForReadAsync(java.util.Set<androidx.appsearch.app.AppSearchBlobHandle!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK) public void registerObserverCallback(String, androidx.appsearch.observer.ObserverSpec, java.util.concurrent.Executor, androidx.appsearch.observer.ObserverCallback) throws androidx.appsearch.exceptions.AppSearchException;
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> reportSystemUsageAsync(androidx.appsearch.app.ReportSystemUsageRequest);
+ method public androidx.appsearch.app.SearchResults search(String, androidx.appsearch.app.SearchSpec);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK) public void unregisterObserverCallback(String, androidx.appsearch.observer.ObserverCallback) throws androidx.appsearch.exceptions.AppSearchException;
+ }
+
+ public final class JoinSpec {
+ method public int getAggregationScoringStrategy();
+ method public String getChildPropertyExpression();
+ method public int getMaxJoinedResultCount();
+ method public String getNestedQuery();
+ method public androidx.appsearch.app.SearchSpec getNestedSearchSpec();
+ field public static final int AGGREGATION_SCORING_AVG_RANKING_SIGNAL = 3; // 0x3
+ field public static final int AGGREGATION_SCORING_MAX_RANKING_SIGNAL = 4; // 0x4
+ field public static final int AGGREGATION_SCORING_MIN_RANKING_SIGNAL = 2; // 0x2
+ field public static final int AGGREGATION_SCORING_OUTER_RESULT_RANKING_SIGNAL = 0; // 0x0
+ field public static final int AGGREGATION_SCORING_RESULT_COUNT = 1; // 0x1
+ field public static final int AGGREGATION_SCORING_SUM_RANKING_SIGNAL = 5; // 0x5
+ }
+
+ public static final class JoinSpec.Builder {
+ ctor @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public JoinSpec.Builder(androidx.appsearch.app.JoinSpec);
+ ctor public JoinSpec.Builder(String);
+ method public androidx.appsearch.app.JoinSpec build();
+ method public androidx.appsearch.app.JoinSpec.Builder setAggregationScoringStrategy(int);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.JoinSpec.Builder setChildPropertyExpression(String);
+ method public androidx.appsearch.app.JoinSpec.Builder setMaxJoinedResultCount(int);
+ method public androidx.appsearch.app.JoinSpec.Builder setNestedSearch(String, androidx.appsearch.app.SearchSpec);
+ }
+
+ public interface LongSerializer<T> {
+ method public T? deserialize(long);
+ method public long serialize(T);
+ }
+
+ public abstract class Migrator {
+ ctor public Migrator();
+ method @WorkerThread public abstract androidx.appsearch.app.GenericDocument onDowngrade(int, int, androidx.appsearch.app.GenericDocument);
+ method @WorkerThread public abstract androidx.appsearch.app.GenericDocument onUpgrade(int, int, androidx.appsearch.app.GenericDocument);
+ method public abstract boolean shouldMigrate(int, int);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class OpenBlobForReadResponse implements java.io.Closeable {
+ ctor public OpenBlobForReadResponse(androidx.appsearch.app.AppSearchBatchResult<androidx.appsearch.app.AppSearchBlobHandle!,android.os.ParcelFileDescriptor!>);
+ method public void close();
+ method public androidx.appsearch.app.AppSearchBatchResult<androidx.appsearch.app.AppSearchBlobHandle!,android.os.ParcelFileDescriptor!> getResult();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<androidx.appsearch.app.OpenBlobForReadResponse!> CREATOR;
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class OpenBlobForWriteResponse implements java.io.Closeable {
+ ctor public OpenBlobForWriteResponse(androidx.appsearch.app.AppSearchBatchResult<androidx.appsearch.app.AppSearchBlobHandle!,android.os.ParcelFileDescriptor!>);
+ method public void close();
+ method public androidx.appsearch.app.AppSearchBatchResult<androidx.appsearch.app.AppSearchBlobHandle!,android.os.ParcelFileDescriptor!> getResult();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<androidx.appsearch.app.OpenBlobForWriteResponse!> CREATOR;
+ }
+
+ public class PackageIdentifier {
+ ctor public PackageIdentifier(String, byte[]);
+ method public String getPackageName();
+ method public byte[] getSha256Certificate();
+ }
+
+ public class PropertyPath implements java.lang.Iterable<androidx.appsearch.app.PropertyPath.PathSegment!> {
+ ctor public PropertyPath(String);
+ ctor public PropertyPath(java.util.List<androidx.appsearch.app.PropertyPath.PathSegment!>);
+ method public androidx.appsearch.app.PropertyPath.PathSegment get(int);
+ method public java.util.Iterator<androidx.appsearch.app.PropertyPath.PathSegment!> iterator();
+ method public int size();
+ }
+
+ public static class PropertyPath.PathSegment {
+ method public static androidx.appsearch.app.PropertyPath.PathSegment create(String);
+ method public static androidx.appsearch.app.PropertyPath.PathSegment create(String, int);
+ method public int getPropertyIndex();
+ method public String getPropertyName();
+ field public static final int NON_REPEATED_CARDINALITY = -1; // 0xffffffff
+ }
+
+ public final class PutDocumentsRequest {
+ method public java.util.List<androidx.appsearch.app.GenericDocument!> getGenericDocuments();
+ method public java.util.List<androidx.appsearch.app.GenericDocument!> getTakenActionGenericDocuments();
+ }
+
+ public static final class PutDocumentsRequest.Builder {
+ ctor public PutDocumentsRequest.Builder();
+ method public androidx.appsearch.app.PutDocumentsRequest.Builder addDocuments(java.lang.Object!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.PutDocumentsRequest.Builder addDocuments(java.util.Collection<? extends java.lang.Object!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.PutDocumentsRequest.Builder addGenericDocuments(androidx.appsearch.app.GenericDocument!...);
+ method public androidx.appsearch.app.PutDocumentsRequest.Builder addGenericDocuments(java.util.Collection<? extends androidx.appsearch.app.GenericDocument!>);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.PutDocumentsRequest.Builder addTakenActions(androidx.appsearch.usagereporting.TakenAction!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.PutDocumentsRequest.Builder addTakenActions(java.util.Collection<? extends androidx.appsearch.usagereporting.TakenAction!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.PutDocumentsRequest build();
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class RemoveBlobResponse {
+ ctor public RemoveBlobResponse(androidx.appsearch.app.AppSearchBatchResult<androidx.appsearch.app.AppSearchBlobHandle!,java.lang.Void!>);
+ method public androidx.appsearch.app.AppSearchBatchResult<androidx.appsearch.app.AppSearchBlobHandle!,java.lang.Void!> getResult();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<androidx.appsearch.app.RemoveBlobResponse!> CREATOR;
+ }
+
+ public final class RemoveByDocumentIdRequest {
+ method public java.util.Set<java.lang.String!> getIds();
+ method public String getNamespace();
+ }
+
+ public static final class RemoveByDocumentIdRequest.Builder {
+ ctor public RemoveByDocumentIdRequest.Builder(String);
+ method public androidx.appsearch.app.RemoveByDocumentIdRequest.Builder addIds(java.lang.String!...);
+ method public androidx.appsearch.app.RemoveByDocumentIdRequest.Builder addIds(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.RemoveByDocumentIdRequest build();
+ }
+
+ public final class ReportSystemUsageRequest {
+ method public String getDatabaseName();
+ method public String getDocumentId();
+ method public String getNamespace();
+ method public String getPackageName();
+ method public long getUsageTimestampMillis();
+ }
+
+ public static final class ReportSystemUsageRequest.Builder {
+ ctor public ReportSystemUsageRequest.Builder(String, String, String, String);
+ method public androidx.appsearch.app.ReportSystemUsageRequest build();
+ method public androidx.appsearch.app.ReportSystemUsageRequest.Builder setUsageTimestampMillis(long);
+ }
+
+ public final class ReportUsageRequest {
+ method public String getDocumentId();
+ method public String getNamespace();
+ method public long getUsageTimestampMillis();
+ }
+
+ public static final class ReportUsageRequest.Builder {
+ ctor public ReportUsageRequest.Builder(String, String);
+ method public androidx.appsearch.app.ReportUsageRequest build();
+ method public androidx.appsearch.app.ReportUsageRequest.Builder setUsageTimestampMillis(long);
+ }
+
+ public final class SchemaVisibilityConfig {
+ method public java.util.List<androidx.appsearch.app.PackageIdentifier!> getAllowedPackages();
+ method public androidx.appsearch.app.PackageIdentifier? getPubliclyVisibleTargetPackage();
+ method public java.util.Set<java.util.Set<java.lang.Integer!>!> getRequiredPermissions();
+ }
+
+ public static final class SchemaVisibilityConfig.Builder {
+ ctor public SchemaVisibilityConfig.Builder();
+ method public androidx.appsearch.app.SchemaVisibilityConfig.Builder addAllowedPackage(androidx.appsearch.app.PackageIdentifier);
+ method public androidx.appsearch.app.SchemaVisibilityConfig.Builder addRequiredPermissions(java.util.Set<java.lang.Integer!>);
+ method public androidx.appsearch.app.SchemaVisibilityConfig build();
+ method public androidx.appsearch.app.SchemaVisibilityConfig.Builder clearAllowedPackages();
+ method public androidx.appsearch.app.SchemaVisibilityConfig.Builder clearRequiredPermissions();
+ method public androidx.appsearch.app.SchemaVisibilityConfig.Builder setPubliclyVisibleTargetPackage(androidx.appsearch.app.PackageIdentifier?);
+ }
+
+ public final class SearchResult {
+ method public String getDatabaseName();
+ method public <T> T getDocument(Class<T!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public <T> T getDocument(Class<T!>, java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!>?) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.GenericDocument getGenericDocument();
+ method public java.util.List<java.lang.Double!> getInformationalRankingSignals();
+ method public java.util.List<androidx.appsearch.app.SearchResult!> getJoinedResults();
+ method public java.util.List<androidx.appsearch.app.SearchResult.MatchInfo!> getMatchInfos();
+ method public String getPackageName();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getParentTypeMap();
+ method public double getRankingSignal();
+ }
+
+ public static final class SearchResult.Builder {
+ ctor public SearchResult.Builder(String, String);
+ method public androidx.appsearch.app.SearchResult.Builder addInformationalRankingSignal(double);
+ method public androidx.appsearch.app.SearchResult.Builder addJoinedResult(androidx.appsearch.app.SearchResult);
+ method public androidx.appsearch.app.SearchResult.Builder addMatchInfo(androidx.appsearch.app.SearchResult.MatchInfo);
+ method public androidx.appsearch.app.SearchResult build();
+ method public androidx.appsearch.app.SearchResult.Builder setDocument(Object) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SearchResult.Builder setGenericDocument(androidx.appsearch.app.GenericDocument);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchResult.Builder setParentTypeMap(java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!>);
+ method public androidx.appsearch.app.SearchResult.Builder setRankingSignal(double);
+ }
+
+ public static final class SearchResult.MatchInfo {
+ method public CharSequence getExactMatch();
+ method public androidx.appsearch.app.SearchResult.MatchRange getExactMatchRange();
+ method public String getFullText();
+ method public String getPropertyPath();
+ method public androidx.appsearch.app.PropertyPath getPropertyPathObject();
+ method public CharSequence getSnippet();
+ method public androidx.appsearch.app.SearchResult.MatchRange getSnippetRange();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_RESULT_MATCH_INFO_SUBMATCH) public CharSequence getSubmatch();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_RESULT_MATCH_INFO_SUBMATCH) public androidx.appsearch.app.SearchResult.MatchRange getSubmatchRange();
+ }
+
+ public static final class SearchResult.MatchInfo.Builder {
+ ctor public SearchResult.MatchInfo.Builder(String);
+ method public androidx.appsearch.app.SearchResult.MatchInfo build();
+ method public androidx.appsearch.app.SearchResult.MatchInfo.Builder setExactMatchRange(androidx.appsearch.app.SearchResult.MatchRange);
+ method public androidx.appsearch.app.SearchResult.MatchInfo.Builder setSnippetRange(androidx.appsearch.app.SearchResult.MatchRange);
+ method public androidx.appsearch.app.SearchResult.MatchInfo.Builder setSubmatchRange(androidx.appsearch.app.SearchResult.MatchRange);
+ }
+
+ public static final class SearchResult.MatchRange {
+ ctor public SearchResult.MatchRange(int, int);
+ method public int getEnd();
+ method public int getStart();
+ }
+
+ public interface SearchResults extends java.io.Closeable {
+ method public void close();
+ method public com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.appsearch.app.SearchResult!>!> getNextPageAsync();
+ }
+
+ public final class SearchSpec {
+ method public String getAdvancedRankingExpression();
+ method public int getDefaultEmbeddingSearchMetricType();
+ method public java.util.List<androidx.appsearch.app.EmbeddingVector!> getEmbeddingParameters();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public java.util.List<java.lang.String!> getFilterDocumentIds();
+ method public java.util.List<java.lang.String!> getFilterNamespaces();
+ method public java.util.List<java.lang.String!> getFilterPackageNames();
+ method public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getFilterProperties();
+ method public java.util.List<java.lang.String!> getFilterSchemas();
+ method public java.util.List<java.lang.String!> getInformationalRankingExpressions();
+ method public androidx.appsearch.app.JoinSpec? getJoinSpec();
+ method public int getMaxSnippetSize();
+ method public int getOrder();
+ method public java.util.Map<java.lang.String!,java.util.List<androidx.appsearch.app.PropertyPath!>!> getProjectionPaths();
+ method public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getProjections();
+ method public java.util.Map<java.lang.String!,java.util.Map<androidx.appsearch.app.PropertyPath!,java.lang.Double!>!> getPropertyWeightPaths();
+ method public java.util.Map<java.lang.String!,java.util.Map<java.lang.String!,java.lang.Double!>!> getPropertyWeights();
+ method public int getRankingStrategy();
+ method public int getResultCountPerPage();
+ method public int getResultGroupingLimit();
+ method public int getResultGroupingTypeFlags();
+ method public String? getSearchSourceLogTag();
+ method public java.util.List<java.lang.String!> getSearchStringParameters();
+ method public int getSnippetCount();
+ method public int getSnippetCountPerProperty();
+ method public int getTermMatch();
+ method public boolean isListFilterHasPropertyFunctionEnabled();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public boolean isListFilterMatchScoreExpressionFunctionEnabled();
+ method public boolean isListFilterQueryLanguageEnabled();
+ method public boolean isNumericSearchEnabled();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public boolean isScorablePropertyRankingEnabled();
+ method public boolean isVerbatimSearchEnabled();
+ field public static final int EMBEDDING_SEARCH_METRIC_TYPE_COSINE = 1; // 0x1
+ field public static final int EMBEDDING_SEARCH_METRIC_TYPE_DEFAULT = 0; // 0x0
+ field public static final int EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT = 2; // 0x2
+ field public static final int EMBEDDING_SEARCH_METRIC_TYPE_EUCLIDEAN = 3; // 0x3
+ field public static final int GROUPING_TYPE_PER_NAMESPACE = 2; // 0x2
+ field public static final int GROUPING_TYPE_PER_PACKAGE = 1; // 0x1
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_GROUPING_TYPE_PER_SCHEMA) public static final int GROUPING_TYPE_PER_SCHEMA = 4; // 0x4
+ field public static final int ORDER_ASCENDING = 1; // 0x1
+ field public static final int ORDER_DESCENDING = 0; // 0x0
+ field public static final int RANKING_STRATEGY_ADVANCED_RANKING_EXPRESSION = 9; // 0x9
+ field public static final int RANKING_STRATEGY_CREATION_TIMESTAMP = 2; // 0x2
+ field public static final int RANKING_STRATEGY_DOCUMENT_SCORE = 1; // 0x1
+ field public static final int RANKING_STRATEGY_JOIN_AGGREGATE_SCORE = 8; // 0x8
+ field public static final int RANKING_STRATEGY_NONE = 0; // 0x0
+ field public static final int RANKING_STRATEGY_RELEVANCE_SCORE = 3; // 0x3
+ field public static final int RANKING_STRATEGY_SYSTEM_USAGE_COUNT = 6; // 0x6
+ field public static final int RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP = 7; // 0x7
+ field public static final int RANKING_STRATEGY_USAGE_COUNT = 4; // 0x4
+ field public static final int RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP = 5; // 0x5
+ field public static final String SCHEMA_TYPE_WILDCARD = "*";
+ field public static final int TERM_MATCH_EXACT_ONLY = 1; // 0x1
+ field public static final int TERM_MATCH_PREFIX = 2; // 0x2
+ }
+
+ public static final class SearchSpec.Builder {
+ ctor public SearchSpec.Builder();
+ ctor @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public SearchSpec.Builder(androidx.appsearch.app.SearchSpec);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addEmbeddingParameters(androidx.appsearch.app.EmbeddingVector!...);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addEmbeddingParameters(java.util.Collection<androidx.appsearch.app.EmbeddingVector!>);
+ method public androidx.appsearch.app.SearchSpec.Builder addFilterDocumentClasses(Class<? extends java.lang.Object!>!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SearchSpec.Builder addFilterDocumentClasses(java.util.Collection<? extends java.lang.Class<? extends java.lang.Object!>!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_DOCUMENT_IDS) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder addFilterDocumentIds(java.lang.String!...);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_DOCUMENT_IDS) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder addFilterDocumentIds(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.SearchSpec.Builder addFilterNamespaces(java.lang.String!...);
+ method public androidx.appsearch.app.SearchSpec.Builder addFilterNamespaces(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.SearchSpec.Builder addFilterPackageNames(java.lang.String!...);
+ method public androidx.appsearch.app.SearchSpec.Builder addFilterPackageNames(java.util.Collection<java.lang.String!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) public androidx.appsearch.app.SearchSpec.Builder addFilterProperties(Class<? extends java.lang.Object!>, java.util.Collection<java.lang.String!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) public androidx.appsearch.app.SearchSpec.Builder addFilterProperties(String, java.util.Collection<java.lang.String!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) public androidx.appsearch.app.SearchSpec.Builder addFilterPropertyPaths(Class<? extends java.lang.Object!>, java.util.Collection<androidx.appsearch.app.PropertyPath!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) public androidx.appsearch.app.SearchSpec.Builder addFilterPropertyPaths(String, java.util.Collection<androidx.appsearch.app.PropertyPath!>);
+ method public androidx.appsearch.app.SearchSpec.Builder addFilterSchemas(java.lang.String!...);
+ method public androidx.appsearch.app.SearchSpec.Builder addFilterSchemas(java.util.Collection<java.lang.String!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_INFORMATIONAL_RANKING_EXPRESSIONS) public androidx.appsearch.app.SearchSpec.Builder addInformationalRankingExpressions(java.lang.String!...);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_INFORMATIONAL_RANKING_EXPRESSIONS) public androidx.appsearch.app.SearchSpec.Builder addInformationalRankingExpressions(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.SearchSpec.Builder addProjection(String, java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.SearchSpec.Builder addProjectionPaths(String, java.util.Collection<androidx.appsearch.app.PropertyPath!>);
+ method public androidx.appsearch.app.SearchSpec.Builder addProjectionPathsForDocumentClass(Class<? extends java.lang.Object!>, java.util.Collection<androidx.appsearch.app.PropertyPath!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SearchSpec.Builder addProjectionsForDocumentClass(Class<? extends java.lang.Object!>, java.util.Collection<java.lang.String!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSpec.Builder addSearchStringParameters(java.lang.String!...);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSpec.Builder addSearchStringParameters(java.util.List<java.lang.String!>);
+ method public androidx.appsearch.app.SearchSpec build();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearEmbeddingParameters();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearFilterDocumentIds();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearFilterNamespaces();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearFilterPackageNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearFilterProperties();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearFilterSchemas();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearInformationalRankingExpressions();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearJoinSpec();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearProjections();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearPropertyWeights();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearResultGrouping();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearSearchSourceLogTag();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearSearchStringParameters();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder setDefaultEmbeddingSearchMetricType(int);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.JOIN_SPEC_AND_QUALIFIED_ID) public androidx.appsearch.app.SearchSpec.Builder setJoinSpec(androidx.appsearch.app.JoinSpec);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.LIST_FILTER_HAS_PROPERTY_FUNCTION) public androidx.appsearch.app.SearchSpec.Builder setListFilterHasPropertyFunctionEnabled(boolean);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.LIST_FILTER_MATCH_SCORE_EXPRESSION_FUNCTION) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder setListFilterMatchScoreExpressionFunctionEnabled(boolean);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.LIST_FILTER_QUERY_LANGUAGE) public androidx.appsearch.app.SearchSpec.Builder setListFilterQueryLanguageEnabled(boolean);
+ method public androidx.appsearch.app.SearchSpec.Builder setMaxSnippetSize(@IntRange(from=0, to=0x2710) int);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.NUMERIC_SEARCH) public androidx.appsearch.app.SearchSpec.Builder setNumericSearchEnabled(boolean);
+ method public androidx.appsearch.app.SearchSpec.Builder setOrder(int);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_PROPERTY_WEIGHTS) public androidx.appsearch.app.SearchSpec.Builder setPropertyWeightPaths(String, java.util.Map<androidx.appsearch.app.PropertyPath!,java.lang.Double!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_PROPERTY_WEIGHTS) public androidx.appsearch.app.SearchSpec.Builder setPropertyWeightPathsForDocumentClass(Class<? extends java.lang.Object!>, java.util.Map<androidx.appsearch.app.PropertyPath!,java.lang.Double!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_PROPERTY_WEIGHTS) public androidx.appsearch.app.SearchSpec.Builder setPropertyWeights(String, java.util.Map<java.lang.String!,java.lang.Double!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_PROPERTY_WEIGHTS) public androidx.appsearch.app.SearchSpec.Builder setPropertyWeightsForDocumentClass(Class<? extends java.lang.Object!>, java.util.Map<java.lang.String!,java.lang.Double!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SearchSpec.Builder setRankingStrategy(int);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION) public androidx.appsearch.app.SearchSpec.Builder setRankingStrategy(String);
+ method public androidx.appsearch.app.SearchSpec.Builder setResultCountPerPage(@IntRange(from=0, to=0x2710) int);
+ method public androidx.appsearch.app.SearchSpec.Builder setResultGrouping(int, int);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SCORABLE_PROPERTY_CONFIG) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder setScorablePropertyRankingEnabled(boolean);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG) public androidx.appsearch.app.SearchSpec.Builder setSearchSourceLogTag(String);
+ method public androidx.appsearch.app.SearchSpec.Builder setSnippetCount(@IntRange(from=0, to=0x2710) int);
+ method public androidx.appsearch.app.SearchSpec.Builder setSnippetCountPerProperty(@IntRange(from=0, to=0x2710) int);
+ method public androidx.appsearch.app.SearchSpec.Builder setTermMatch(int);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.VERBATIM_SEARCH) public androidx.appsearch.app.SearchSpec.Builder setVerbatimSearchEnabled(boolean);
+ }
+
+ public final class SearchSuggestionResult {
+ method public String getSuggestedResult();
+ }
+
+ public static final class SearchSuggestionResult.Builder {
+ ctor public SearchSuggestionResult.Builder();
+ method public androidx.appsearch.app.SearchSuggestionResult build();
+ method public androidx.appsearch.app.SearchSuggestionResult.Builder setSuggestedResult(String);
+ }
+
+ public final class SearchSuggestionSpec {
+ method public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getFilterDocumentIds();
+ method public java.util.List<java.lang.String!> getFilterNamespaces();
+ method public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getFilterProperties();
+ method public java.util.List<java.lang.String!> getFilterSchemas();
+ method public int getMaximumResultCount();
+ method public int getRankingStrategy();
+ method public java.util.List<java.lang.String!> getSearchStringParameters();
+ field public static final int SUGGESTION_RANKING_STRATEGY_DOCUMENT_COUNT = 0; // 0x0
+ field public static final int SUGGESTION_RANKING_STRATEGY_NONE = 2; // 0x2
+ field public static final int SUGGESTION_RANKING_STRATEGY_TERM_FREQUENCY = 1; // 0x1
+ }
+
+ public static final class SearchSuggestionSpec.Builder {
+ ctor public SearchSuggestionSpec.Builder(@IntRange(from=1) int);
+ method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterDocumentClasses(Class<? extends java.lang.Object!>!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterDocumentClasses(java.util.Collection<? extends java.lang.Class<? extends java.lang.Object!>!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterDocumentIds(String, java.lang.String!...);
+ method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterDocumentIds(String, java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterNamespaces(java.lang.String!...);
+ method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterNamespaces(java.util.Collection<java.lang.String!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterProperties(Class<? extends java.lang.Object!>, java.util.Collection<java.lang.String!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterProperties(String, java.util.Collection<java.lang.String!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterPropertyPaths(Class<? extends java.lang.Object!>, java.util.Collection<androidx.appsearch.app.PropertyPath!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterPropertyPaths(String, java.util.Collection<androidx.appsearch.app.PropertyPath!>);
+ method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterSchemas(java.lang.String!...);
+ method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterSchemas(java.util.Collection<java.lang.String!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSuggestionSpec.Builder addSearchStringParameters(java.lang.String!...);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSuggestionSpec.Builder addSearchStringParameters(java.util.List<java.lang.String!>);
+ method public androidx.appsearch.app.SearchSuggestionSpec build();
+ method public androidx.appsearch.app.SearchSuggestionSpec.Builder setRankingStrategy(int);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public class SetBlobVisibilityRequest {
+ method public java.util.Set<java.lang.String!> getNamespacesNotDisplayedBySystem();
+ method public java.util.Map<java.lang.String!,java.util.Set<androidx.appsearch.app.SchemaVisibilityConfig!>!> getNamespacesVisibleToConfigs();
+ }
+
+ public static final class SetBlobVisibilityRequest.Builder {
+ ctor public SetBlobVisibilityRequest.Builder();
+ method public androidx.appsearch.app.SetBlobVisibilityRequest.Builder addNamespaceVisibleToConfig(String, androidx.appsearch.app.SchemaVisibilityConfig);
+ method public androidx.appsearch.app.SetBlobVisibilityRequest build();
+ method public androidx.appsearch.app.SetBlobVisibilityRequest.Builder clearNamespaceVisibleToConfigs(String);
+ method public androidx.appsearch.app.SetBlobVisibilityRequest.Builder setNamespaceDisplayedBySystem(String, boolean);
+ }
+
+ public final class SetSchemaRequest {
+ method public java.util.Map<java.lang.String!,androidx.appsearch.app.Migrator!> getMigrators();
+ method public java.util.Map<java.lang.String!,androidx.appsearch.app.PackageIdentifier!> getPubliclyVisibleSchemas();
+ method public java.util.Map<java.lang.String!,java.util.Set<java.util.Set<java.lang.Integer!>!>!> getRequiredPermissionsForSchemaTypeVisibility();
+ method public java.util.Set<androidx.appsearch.app.AppSearchSchema!> getSchemas();
+ method public java.util.Set<java.lang.String!> getSchemasNotDisplayedBySystem();
+ method public java.util.Map<java.lang.String!,java.util.Set<androidx.appsearch.app.SchemaVisibilityConfig!>!> getSchemasVisibleToConfigs();
+ method public java.util.Map<java.lang.String!,java.util.Set<androidx.appsearch.app.PackageIdentifier!>!> getSchemasVisibleToPackages();
+ method @IntRange(from=1) public int getVersion();
+ method public boolean isForceOverride();
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public static final int READ_ASSISTANT_APP_SEARCH_DATA = 6; // 0x6
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public static final int READ_CALENDAR = 2; // 0x2
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public static final int READ_CONTACTS = 3; // 0x3
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public static final int READ_EXTERNAL_STORAGE = 4; // 0x4
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public static final int READ_HOME_APP_SEARCH_DATA = 5; // 0x5
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public static final int READ_SMS = 1; // 0x1
+ }
+
+ public static final class SetSchemaRequest.Builder {
+ ctor public SetSchemaRequest.Builder();
+ ctor @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public SetSchemaRequest.Builder(androidx.appsearch.app.SetSchemaRequest);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG) public androidx.appsearch.app.SetSchemaRequest.Builder addDocumentClassVisibleToConfig(Class<? extends java.lang.Object!>, androidx.appsearch.app.SchemaVisibilityConfig) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SetSchemaRequest.Builder addDocumentClasses(Class<? extends java.lang.Object!>!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SetSchemaRequest.Builder addDocumentClasses(java.util.Collection<? extends java.lang.Class<? extends java.lang.Object!>!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public androidx.appsearch.app.SetSchemaRequest.Builder addRequiredPermissionsForDocumentClassVisibility(Class<? extends java.lang.Object!>, java.util.Set<java.lang.Integer!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public androidx.appsearch.app.SetSchemaRequest.Builder addRequiredPermissionsForSchemaTypeVisibility(String, java.util.Set<java.lang.Integer!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG) public androidx.appsearch.app.SetSchemaRequest.Builder addSchemaTypeVisibleToConfig(String, androidx.appsearch.app.SchemaVisibilityConfig);
+ method public androidx.appsearch.app.SetSchemaRequest.Builder addSchemas(androidx.appsearch.app.AppSearchSchema!...);
+ method public androidx.appsearch.app.SetSchemaRequest.Builder addSchemas(java.util.Collection<androidx.appsearch.app.AppSearchSchema!>);
+ method public androidx.appsearch.app.SetSchemaRequest build();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG) public androidx.appsearch.app.SetSchemaRequest.Builder clearDocumentClassVisibleToConfigs(Class<? extends java.lang.Object!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SetSchemaRequest.Builder clearMigrators();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public androidx.appsearch.app.SetSchemaRequest.Builder clearRequiredPermissionsForDocumentClassVisibility(Class<? extends java.lang.Object!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public androidx.appsearch.app.SetSchemaRequest.Builder clearRequiredPermissionsForSchemaTypeVisibility(String);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG) public androidx.appsearch.app.SetSchemaRequest.Builder clearSchemaTypeVisibleToConfigs(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SetSchemaRequest.Builder clearSchemas();
+ method public androidx.appsearch.app.SetSchemaRequest.Builder setDocumentClassDisplayedBySystem(Class<? extends java.lang.Object!>, boolean) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SetSchemaRequest.Builder setDocumentClassVisibilityForPackage(Class<? extends java.lang.Object!>, boolean, androidx.appsearch.app.PackageIdentifier) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SetSchemaRequest.Builder setForceOverride(boolean);
+ method public androidx.appsearch.app.SetSchemaRequest.Builder setMigrator(String, androidx.appsearch.app.Migrator);
+ method public androidx.appsearch.app.SetSchemaRequest.Builder setMigrators(java.util.Map<java.lang.String!,androidx.appsearch.app.Migrator!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SET_SCHEMA_REQUEST_SET_PUBLICLY_VISIBLE) public androidx.appsearch.app.SetSchemaRequest.Builder setPubliclyVisibleDocumentClass(Class<? extends java.lang.Object!>, androidx.appsearch.app.PackageIdentifier?) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SET_SCHEMA_REQUEST_SET_PUBLICLY_VISIBLE) public androidx.appsearch.app.SetSchemaRequest.Builder setPubliclyVisibleSchema(String, androidx.appsearch.app.PackageIdentifier?);
+ method public androidx.appsearch.app.SetSchemaRequest.Builder setSchemaTypeDisplayedBySystem(String, boolean);
+ method public androidx.appsearch.app.SetSchemaRequest.Builder setSchemaTypeVisibilityForPackage(String, boolean, androidx.appsearch.app.PackageIdentifier);
+ method public androidx.appsearch.app.SetSchemaRequest.Builder setVersion(@IntRange(from=1) int);
+ }
+
+ public final class SetSchemaResponse {
+ method public java.util.Set<java.lang.String!> getDeletedTypes();
+ method public java.util.Set<java.lang.String!> getIncompatibleTypes();
+ method public java.util.Set<java.lang.String!> getMigratedTypes();
+ method public java.util.List<androidx.appsearch.app.SetSchemaResponse.MigrationFailure!> getMigrationFailures();
+ }
+
+ public static final class SetSchemaResponse.Builder {
+ ctor public SetSchemaResponse.Builder();
+ method public androidx.appsearch.app.SetSchemaResponse.Builder addDeletedType(String);
+ method public androidx.appsearch.app.SetSchemaResponse.Builder addDeletedTypes(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.SetSchemaResponse.Builder addIncompatibleType(String);
+ method public androidx.appsearch.app.SetSchemaResponse.Builder addIncompatibleTypes(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.SetSchemaResponse.Builder addMigratedType(String);
+ method public androidx.appsearch.app.SetSchemaResponse.Builder addMigratedTypes(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.SetSchemaResponse.Builder addMigrationFailure(androidx.appsearch.app.SetSchemaResponse.MigrationFailure);
+ method public androidx.appsearch.app.SetSchemaResponse.Builder addMigrationFailures(java.util.Collection<androidx.appsearch.app.SetSchemaResponse.MigrationFailure!>);
+ method public androidx.appsearch.app.SetSchemaResponse build();
+ }
+
+ public static class SetSchemaResponse.MigrationFailure {
+ ctor public SetSchemaResponse.MigrationFailure(String, String, String, androidx.appsearch.app.AppSearchResult<? extends java.lang.Object!>);
+ method public androidx.appsearch.app.AppSearchResult<java.lang.Void!> getAppSearchResult();
+ method public String getDocumentId();
+ method public String getNamespace();
+ method public String getSchemaType();
+ }
+
+ public final class StorageInfo {
+ method public int getAliveDocumentsCount();
+ method public int getAliveNamespacesCount();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public int getBlobsCount();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public long getBlobsSizeBytes();
+ method public long getSizeBytes();
+ }
+
+ public static final class StorageInfo.Builder {
+ ctor public StorageInfo.Builder();
+ method public androidx.appsearch.app.StorageInfo build();
+ method public androidx.appsearch.app.StorageInfo.Builder setAliveDocumentsCount(int);
+ method public androidx.appsearch.app.StorageInfo.Builder setAliveNamespacesCount(int);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.StorageInfo.Builder setBlobsCount(int);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.StorageInfo.Builder setBlobsSizeBytes(long);
+ method public androidx.appsearch.app.StorageInfo.Builder setSizeBytes(long);
+ }
+
+ public interface StringSerializer<T> {
+ method public T? deserialize(String);
+ method public String serialize(T);
+ }
+
+}
+
+package androidx.appsearch.ast {
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public interface FunctionNode extends androidx.appsearch.ast.Node {
+ method public String getFunctionName();
+ field public static final String FUNCTION_NAME_GET_SEARCH_STRING_PARAMETER = "getSearchStringParameter";
+ field public static final String FUNCTION_NAME_HAS_PROPERTY = "hasProperty";
+ field public static final String FUNCTION_NAME_PROPERTY_DEFINED = "propertyDefined";
+ field public static final String FUNCTION_NAME_SEARCH = "search";
+ field public static final String FUNCTION_NAME_SEMANTIC_SEARCH = "semanticSearch";
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class NegationNode implements androidx.appsearch.ast.Node {
+ ctor public NegationNode(androidx.appsearch.ast.Node);
+ method public androidx.appsearch.ast.Node getChild();
+ method public void setChild(androidx.appsearch.ast.Node);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public interface Node {
+ method public default java.util.List<androidx.appsearch.ast.Node!> getChildren();
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class TextNode implements androidx.appsearch.ast.Node {
+ ctor public TextNode(androidx.appsearch.ast.TextNode);
+ ctor public TextNode(String);
+ method public String getValue();
+ method public boolean isPrefix();
+ method public boolean isVerbatim();
+ method public void setPrefix(boolean);
+ method public void setValue(String);
+ method public void setVerbatim(boolean);
+ }
+
+}
+
+package androidx.appsearch.ast.operators {
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class AndNode implements androidx.appsearch.ast.Node {
+ ctor public AndNode(androidx.appsearch.ast.Node, androidx.appsearch.ast.Node, androidx.appsearch.ast.Node!...);
+ ctor public AndNode(java.util.List<androidx.appsearch.ast.Node!>);
+ method public void addChild(androidx.appsearch.ast.Node);
+ method public int getIndexOfChild(androidx.appsearch.ast.Node);
+ method public boolean removeChild(androidx.appsearch.ast.Node);
+ method public void setChild(int, androidx.appsearch.ast.Node);
+ method public void setChildren(java.util.List<androidx.appsearch.ast.Node!>);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class ComparatorNode implements androidx.appsearch.ast.Node {
+ ctor public ComparatorNode(int, androidx.appsearch.app.PropertyPath, long);
+ method public int getComparator();
+ method public androidx.appsearch.app.PropertyPath getPropertyPath();
+ method public long getValue();
+ method public void setComparator(int);
+ method public void setPropertyPath(androidx.appsearch.app.PropertyPath);
+ method public void setValue(long);
+ field public static final int EQUALS = 0; // 0x0
+ field public static final int GREATER_EQUALS = 4; // 0x4
+ field public static final int GREATER_THAN = 3; // 0x3
+ field public static final int LESS_EQUALS = 2; // 0x2
+ field public static final int LESS_THAN = 1; // 0x1
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class OrNode implements androidx.appsearch.ast.Node {
+ ctor public OrNode(androidx.appsearch.ast.Node, androidx.appsearch.ast.Node, androidx.appsearch.ast.Node!...);
+ ctor public OrNode(java.util.List<androidx.appsearch.ast.Node!>);
+ method public void addChild(androidx.appsearch.ast.Node);
+ method public int getIndexOfChild(androidx.appsearch.ast.Node);
+ method public boolean removeChild(androidx.appsearch.ast.Node);
+ method public void setChild(int, androidx.appsearch.ast.Node);
+ method public void setChildren(java.util.List<androidx.appsearch.ast.Node!>);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class PropertyRestrictNode implements androidx.appsearch.ast.Node {
+ ctor public PropertyRestrictNode(androidx.appsearch.app.PropertyPath, androidx.appsearch.ast.Node);
+ method public androidx.appsearch.ast.Node getChild();
+ method public androidx.appsearch.app.PropertyPath getPropertyPath();
+ method public void setChild(androidx.appsearch.ast.Node);
+ method public void setPropertyPath(androidx.appsearch.app.PropertyPath);
+ }
+
+}
+
+package androidx.appsearch.ast.query {
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class GetSearchStringParameterNode implements androidx.appsearch.ast.FunctionNode {
+ ctor public GetSearchStringParameterNode(int);
+ method public String getFunctionName();
+ method public int getSearchStringIndex();
+ method public void setSearchStringIndex(int);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class HasPropertyNode implements androidx.appsearch.ast.FunctionNode {
+ ctor public HasPropertyNode(androidx.appsearch.app.PropertyPath);
+ method public String getFunctionName();
+ method public androidx.appsearch.app.PropertyPath getPropertyPath();
+ method public void setPropertyPath(androidx.appsearch.app.PropertyPath);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class PropertyDefinedNode implements androidx.appsearch.ast.FunctionNode {
+ ctor public PropertyDefinedNode(androidx.appsearch.app.PropertyPath);
+ method public String getFunctionName();
+ method public androidx.appsearch.app.PropertyPath getPropertyPath();
+ method public void setPropertyPath(androidx.appsearch.app.PropertyPath);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class SearchNode implements androidx.appsearch.ast.FunctionNode {
+ ctor public SearchNode(androidx.appsearch.ast.Node);
+ ctor public SearchNode(androidx.appsearch.ast.Node, java.util.List<androidx.appsearch.app.PropertyPath!>);
+ method public void addPropertyPath(androidx.appsearch.app.PropertyPath);
+ method public androidx.appsearch.ast.Node getChild();
+ method public String getFunctionName();
+ method public java.util.List<androidx.appsearch.app.PropertyPath!> getPropertyPaths();
+ method public void setChild(androidx.appsearch.ast.Node);
+ method public void setPropertyPaths(java.util.List<androidx.appsearch.app.PropertyPath!>);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class SemanticSearchNode implements androidx.appsearch.ast.FunctionNode {
+ ctor public SemanticSearchNode(int);
+ ctor public SemanticSearchNode(int, float);
+ ctor public SemanticSearchNode(int, float, float);
+ ctor public SemanticSearchNode(int, float, float, int);
+ method public int getDistanceMetric();
+ method public String getFunctionName();
+ method public float getLowerBound();
+ method public float getUpperBound();
+ method public int getVectorIndex();
+ method public void setBounds(float, float);
+ method public void setDistanceMetric(int);
+ method public void setVectorIndex(int);
+ }
+
+}
+
+package androidx.appsearch.exceptions {
+
+ public class AppSearchException extends java.lang.Exception {
+ ctor public AppSearchException(int);
+ ctor public AppSearchException(int, String?);
+ ctor public AppSearchException(int, String?, Throwable?);
+ method public int getResultCode();
+ method public <T> androidx.appsearch.app.AppSearchResult<T!> toAppSearchResult();
+ }
+
+}
+
+package androidx.appsearch.observer {
+
+ public final class DocumentChangeInfo {
+ ctor public DocumentChangeInfo(String, String, String, String, java.util.Set<java.lang.String!>);
+ method public java.util.Set<java.lang.String!> getChangedDocumentIds();
+ method public String getDatabaseName();
+ method public String getNamespace();
+ method public String getPackageName();
+ method public String getSchemaName();
+ }
+
+ public interface ObserverCallback {
+ method public void onDocumentChanged(androidx.appsearch.observer.DocumentChangeInfo);
+ method public void onSchemaChanged(androidx.appsearch.observer.SchemaChangeInfo);
+ }
+
+ public final class ObserverSpec {
+ method public java.util.Set<java.lang.String!> getFilterSchemas();
+ }
+
+ public static final class ObserverSpec.Builder {
+ ctor public ObserverSpec.Builder();
+ method public androidx.appsearch.observer.ObserverSpec.Builder addFilterDocumentClasses(Class<? extends java.lang.Object!>!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.observer.ObserverSpec.Builder addFilterDocumentClasses(java.util.Collection<? extends java.lang.Class<? extends java.lang.Object!>!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.observer.ObserverSpec.Builder addFilterSchemas(java.lang.String!...);
+ method public androidx.appsearch.observer.ObserverSpec.Builder addFilterSchemas(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.observer.ObserverSpec build();
+ }
+
+ public final class SchemaChangeInfo {
+ ctor public SchemaChangeInfo(String, String, java.util.Set<java.lang.String!>);
+ method public java.util.Set<java.lang.String!> getChangedSchemaNames();
+ method public String getDatabaseName();
+ method public String getPackageName();
+ }
+
+}
+
+package androidx.appsearch.usagereporting {
+
+ @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.JOIN_SPEC_AND_QUALIFIED_ID) @androidx.appsearch.annotation.Document(name="builtin:ClickAction") @androidx.appsearch.app.ExperimentalAppSearchApi public class ClickAction extends androidx.appsearch.usagereporting.TakenAction {
+ method public String? getQuery();
+ method public String? getReferencedQualifiedId();
+ method public int getResultRankGlobal();
+ method public int getResultRankInBlock();
+ method public long getTimeStayOnResultMillis();
+ }
+
+ @androidx.appsearch.annotation.Document.BuilderProducer public static final class ClickAction.Builder {
+ ctor public ClickAction.Builder(androidx.appsearch.usagereporting.ClickAction);
+ ctor public ClickAction.Builder(String, String, long);
+ method public androidx.appsearch.usagereporting.ClickAction build();
+ method public androidx.appsearch.usagereporting.ClickAction.Builder setDocumentTtlMillis(long);
+ method public androidx.appsearch.usagereporting.ClickAction.Builder setQuery(String?);
+ method public androidx.appsearch.usagereporting.ClickAction.Builder setReferencedQualifiedId(String?);
+ method public androidx.appsearch.usagereporting.ClickAction.Builder setResultRankGlobal(int);
+ method public androidx.appsearch.usagereporting.ClickAction.Builder setResultRankInBlock(int);
+ method public androidx.appsearch.usagereporting.ClickAction.Builder setTimeStayOnResultMillis(long);
+ }
+
+ @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.JOIN_SPEC_AND_QUALIFIED_ID) @androidx.appsearch.annotation.Document(name="builtin:DismissAction") @androidx.appsearch.app.ExperimentalAppSearchApi public class DismissAction extends androidx.appsearch.usagereporting.TakenAction {
+ method public String? getQuery();
+ method public String? getReferencedQualifiedId();
+ method public int getResultRankGlobal();
+ method public int getResultRankInBlock();
+ }
+
+ @androidx.appsearch.annotation.Document.BuilderProducer public static final class DismissAction.Builder {
+ ctor public DismissAction.Builder(androidx.appsearch.usagereporting.DismissAction);
+ ctor public DismissAction.Builder(String, String, long);
+ method public androidx.appsearch.usagereporting.DismissAction build();
+ method public androidx.appsearch.usagereporting.DismissAction.Builder setDocumentTtlMillis(long);
+ method public androidx.appsearch.usagereporting.DismissAction.Builder setQuery(String?);
+ method public androidx.appsearch.usagereporting.DismissAction.Builder setReferencedQualifiedId(String?);
+ method public androidx.appsearch.usagereporting.DismissAction.Builder setResultRankGlobal(int);
+ method public androidx.appsearch.usagereporting.DismissAction.Builder setResultRankInBlock(int);
+ }
+
+ @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.JOIN_SPEC_AND_QUALIFIED_ID) @androidx.appsearch.annotation.Document(name="builtin:ImpressionAction") @androidx.appsearch.app.ExperimentalAppSearchApi public class ImpressionAction extends androidx.appsearch.usagereporting.TakenAction {
+ method public String? getQuery();
+ method public String? getReferencedQualifiedId();
+ method public int getResultRankGlobal();
+ method public int getResultRankInBlock();
+ }
+
+ @androidx.appsearch.annotation.Document.BuilderProducer public static final class ImpressionAction.Builder {
+ ctor public ImpressionAction.Builder(androidx.appsearch.usagereporting.ImpressionAction);
+ ctor public ImpressionAction.Builder(String, String, long);
+ method public androidx.appsearch.usagereporting.ImpressionAction build();
+ method public androidx.appsearch.usagereporting.ImpressionAction.Builder setDocumentTtlMillis(long);
+ method public androidx.appsearch.usagereporting.ImpressionAction.Builder setQuery(String?);
+ method public androidx.appsearch.usagereporting.ImpressionAction.Builder setReferencedQualifiedId(String?);
+ method public androidx.appsearch.usagereporting.ImpressionAction.Builder setResultRankGlobal(int);
+ method public androidx.appsearch.usagereporting.ImpressionAction.Builder setResultRankInBlock(int);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.annotation.Document(name="builtin:SearchAction") @androidx.appsearch.app.ExperimentalAppSearchApi public class SearchAction extends androidx.appsearch.usagereporting.TakenAction {
+ method public int getFetchedResultCount();
+ method public String? getQuery();
+ }
+
+ @androidx.appsearch.annotation.Document.BuilderProducer public static final class SearchAction.Builder {
+ ctor public SearchAction.Builder(androidx.appsearch.usagereporting.SearchAction);
+ ctor public SearchAction.Builder(String, String, long);
+ method public androidx.appsearch.usagereporting.SearchAction build();
+ method public androidx.appsearch.usagereporting.SearchAction.Builder setDocumentTtlMillis(long);
+ method public androidx.appsearch.usagereporting.SearchAction.Builder setFetchedResultCount(int);
+ method public androidx.appsearch.usagereporting.SearchAction.Builder setQuery(String?);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.annotation.Document(name="builtin:TakenAction") @androidx.appsearch.app.ExperimentalAppSearchApi public abstract class TakenAction {
+ method public long getActionTimestampMillis();
+ method public long getDocumentTtlMillis();
+ method public String getId();
+ method public String getNamespace();
+ }
+
+}
+
+package androidx.appsearch.util {
+
+ public class DocumentIdUtil {
+ method public static String createQualifiedId(String, String, androidx.appsearch.app.GenericDocument);
+ method public static String createQualifiedId(String, String, String, String);
+ }
+
+}
+
diff --git a/appsearch/appsearch/api/res-1.1.0-beta02.txt b/appsearch/appsearch/api/res-1.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/appsearch/appsearch/api/res-1.1.0-beta02.txt
diff --git a/appsearch/appsearch/api/restricted_1.1.0-beta02.txt b/appsearch/appsearch/api/restricted_1.1.0-beta02.txt
new file mode 100644
index 0000000..1527f14
--- /dev/null
+++ b/appsearch/appsearch/api/restricted_1.1.0-beta02.txt
@@ -0,0 +1,1253 @@
+// Signature format: 4.0
+package androidx.appsearch.annotation {
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Document {
+ method public abstract String name() default "";
+ method public abstract Class<? extends java.lang.Object!>[] parent() default {};
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.BooleanProperty {
+ method public abstract String name() default "";
+ method public abstract boolean required() default false;
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) public static @interface Document.BuilderProducer {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.BytesProperty {
+ method public abstract String name() default "";
+ method public abstract boolean required() default false;
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.CreationTimestampMillis {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.DocumentProperty {
+ method public abstract boolean indexNestedProperties() default false;
+ method public abstract String[] indexableNestedPropertiesList() default {};
+ method public abstract boolean inheritIndexableNestedPropertiesFromSuperclass() default false;
+ method public abstract String name() default "";
+ method public abstract boolean required() default false;
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.DoubleProperty {
+ method public abstract String name() default "";
+ method public abstract boolean required() default false;
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.EmbeddingProperty {
+ method public abstract int indexingType() default androidx.appsearch.app.AppSearchSchema.EmbeddingPropertyConfig.INDEXING_TYPE_NONE;
+ method public abstract String name() default "";
+ method public abstract boolean required() default false;
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.Id {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.LongProperty {
+ method public abstract int indexingType() default androidx.appsearch.app.AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE;
+ method public abstract String name() default "";
+ method public abstract boolean required() default false;
+ method public abstract Class<? extends androidx.appsearch.app.LongSerializer<? extends java.lang.Object!>!> serializer() default androidx.appsearch.annotation.Document.LongProperty.DefaultSerializer.class;
+ }
+
+ public static final class Document.LongProperty.DefaultSerializer implements androidx.appsearch.app.LongSerializer<java.lang.Long!> {
+ ctor public Document.LongProperty.DefaultSerializer();
+ method public Long deserialize(long);
+ method public long serialize(Long);
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.Namespace {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.Score {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.StringProperty {
+ method public abstract int indexingType() default androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE;
+ method public abstract int joinableValueType() default androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE;
+ method public abstract String name() default "";
+ method public abstract boolean required() default false;
+ method public abstract Class<? extends androidx.appsearch.app.StringSerializer<? extends java.lang.Object!>!> serializer() default androidx.appsearch.annotation.Document.StringProperty.DefaultSerializer.class;
+ method public abstract int tokenizerType() default androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN;
+ }
+
+ public static final class Document.StringProperty.DefaultSerializer implements androidx.appsearch.app.StringSerializer<java.lang.String!> {
+ ctor public Document.StringProperty.DefaultSerializer();
+ method public String deserialize(String);
+ method public String serialize(String);
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.TtlMillis {
+ }
+
+}
+
+package androidx.appsearch.app {
+
+ public final class AppSearchBatchResult<KeyType, ValueType> {
+ method public java.util.Map<KeyType!,androidx.appsearch.app.AppSearchResult<ValueType!>!> getAll();
+ method public java.util.Map<KeyType!,androidx.appsearch.app.AppSearchResult<ValueType!>!> getFailures();
+ method public java.util.Map<KeyType!,ValueType!> getSuccesses();
+ method public boolean isSuccess();
+ }
+
+ public static final class AppSearchBatchResult.Builder<KeyType, ValueType> {
+ ctor public AppSearchBatchResult.Builder();
+ ctor @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public AppSearchBatchResult.Builder(androidx.appsearch.app.AppSearchBatchResult<KeyType!,ValueType!>);
+ method public androidx.appsearch.app.AppSearchBatchResult<KeyType!,ValueType!> build();
+ method public androidx.appsearch.app.AppSearchBatchResult.Builder<KeyType!,ValueType!> setFailure(KeyType, int, String?);
+ method public androidx.appsearch.app.AppSearchBatchResult.Builder<KeyType!,ValueType!> setResult(KeyType, androidx.appsearch.app.AppSearchResult<ValueType!>);
+ method public androidx.appsearch.app.AppSearchBatchResult.Builder<KeyType!,ValueType!> setSuccess(KeyType, ValueType?);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class AppSearchBlobHandle {
+ method public static androidx.appsearch.app.AppSearchBlobHandle createWithSha256(byte[], String, String, String);
+ method public String getDatabaseName();
+ method public String getNamespace();
+ method public String getPackageName();
+ method public byte[] getSha256Digest();
+ }
+
+ @AnyThread public abstract class AppSearchDocumentClassMap {
+ ctor public AppSearchDocumentClassMap();
+ method @WorkerThread public static java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getGlobalMap();
+ method protected abstract java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getMap();
+ }
+
+ public final class AppSearchResult<ValueType> {
+ method public String? getErrorMessage();
+ method public int getResultCode();
+ method public ValueType? getResultValue();
+ method public boolean isSuccess();
+ method public static <ValueType> androidx.appsearch.app.AppSearchResult<ValueType!> newFailedResult(int, String?);
+ method public static <ValueType> androidx.appsearch.app.AppSearchResult<ValueType!> newSuccessfulResult(ValueType?);
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final int RESULT_ALREADY_EXISTS = 12; // 0xc
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final int RESULT_DENIED = 9; // 0x9
+ field public static final int RESULT_INTERNAL_ERROR = 2; // 0x2
+ field public static final int RESULT_INVALID_ARGUMENT = 3; // 0x3
+ field public static final int RESULT_INVALID_SCHEMA = 7; // 0x7
+ field public static final int RESULT_IO_ERROR = 4; // 0x4
+ field public static final int RESULT_NOT_FOUND = 6; // 0x6
+ field public static final int RESULT_OK = 0; // 0x0
+ field public static final int RESULT_OUT_OF_SPACE = 5; // 0x5
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final int RESULT_RATE_LIMITED = 10; // 0xa
+ field public static final int RESULT_SECURITY_ERROR = 8; // 0x8
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final int RESULT_TIMED_OUT = 11; // 0xb
+ field public static final int RESULT_UNKNOWN_ERROR = 1; // 0x1
+ }
+
+ public final class AppSearchSchema {
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public String getDescription();
+ method public java.util.List<java.lang.String!> getParentTypes();
+ method public java.util.List<androidx.appsearch.app.AppSearchSchema.PropertyConfig!> getProperties();
+ method public String getSchemaType();
+ }
+
+ @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name="BLOB_STORAGE") @androidx.appsearch.app.ExperimentalAppSearchApi public static final class AppSearchSchema.BlobHandlePropertyConfig extends androidx.appsearch.app.AppSearchSchema.PropertyConfig {
+ }
+
+ public static final class AppSearchSchema.BlobHandlePropertyConfig.Builder {
+ ctor public AppSearchSchema.BlobHandlePropertyConfig.Builder(String);
+ method public androidx.appsearch.app.AppSearchSchema.BlobHandlePropertyConfig build();
+ method public androidx.appsearch.app.AppSearchSchema.BlobHandlePropertyConfig.Builder setCardinality(int);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SET_DESCRIPTION) public androidx.appsearch.app.AppSearchSchema.BlobHandlePropertyConfig.Builder setDescription(String);
+ }
+
+ public static final class AppSearchSchema.BooleanPropertyConfig extends androidx.appsearch.app.AppSearchSchema.PropertyConfig {
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public boolean isScoringEnabled();
+ }
+
+ public static final class AppSearchSchema.BooleanPropertyConfig.Builder {
+ ctor public AppSearchSchema.BooleanPropertyConfig.Builder(String);
+ method public androidx.appsearch.app.AppSearchSchema.BooleanPropertyConfig build();
+ method public androidx.appsearch.app.AppSearchSchema.BooleanPropertyConfig.Builder setCardinality(int);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SET_DESCRIPTION) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.BooleanPropertyConfig.Builder setDescription(String);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SCORABLE_PROPERTY_CONFIG) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.BooleanPropertyConfig.Builder setScoringEnabled(boolean);
+ }
+
+ public static final class AppSearchSchema.Builder {
+ ctor @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public AppSearchSchema.Builder(androidx.appsearch.app.AppSearchSchema);
+ ctor public AppSearchSchema.Builder(String);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_ADD_PARENT_TYPE) public androidx.appsearch.app.AppSearchSchema.Builder addParentType(String);
+ method public androidx.appsearch.app.AppSearchSchema.Builder addProperty(androidx.appsearch.app.AppSearchSchema.PropertyConfig);
+ method public androidx.appsearch.app.AppSearchSchema build();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.Builder clearParentTypes();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.Builder clearProperties();
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SET_DESCRIPTION) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.Builder setDescription(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.Builder setSchemaType(String);
+ }
+
+ public static final class AppSearchSchema.BytesPropertyConfig extends androidx.appsearch.app.AppSearchSchema.PropertyConfig {
+ }
+
+ public static final class AppSearchSchema.BytesPropertyConfig.Builder {
+ ctor public AppSearchSchema.BytesPropertyConfig.Builder(String);
+ method public androidx.appsearch.app.AppSearchSchema.BytesPropertyConfig build();
+ method public androidx.appsearch.app.AppSearchSchema.BytesPropertyConfig.Builder setCardinality(int);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SET_DESCRIPTION) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.BytesPropertyConfig.Builder setDescription(String);
+ }
+
+ public static final class AppSearchSchema.DocumentPropertyConfig extends androidx.appsearch.app.AppSearchSchema.PropertyConfig {
+ method public java.util.List<java.lang.String!> getIndexableNestedProperties();
+ method public String getSchemaType();
+ method public boolean shouldIndexNestedProperties();
+ }
+
+ public static final class AppSearchSchema.DocumentPropertyConfig.Builder {
+ ctor public AppSearchSchema.DocumentPropertyConfig.Builder(String, String);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES) public androidx.appsearch.app.AppSearchSchema.DocumentPropertyConfig.Builder addIndexableNestedProperties(java.lang.String!...);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES) public androidx.appsearch.app.AppSearchSchema.DocumentPropertyConfig.Builder addIndexableNestedProperties(java.util.Collection<java.lang.String!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES) public androidx.appsearch.app.AppSearchSchema.DocumentPropertyConfig.Builder addIndexableNestedPropertyPaths(androidx.appsearch.app.PropertyPath!...);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES) public androidx.appsearch.app.AppSearchSchema.DocumentPropertyConfig.Builder addIndexableNestedPropertyPaths(java.util.Collection<androidx.appsearch.app.PropertyPath!>);
+ method public androidx.appsearch.app.AppSearchSchema.DocumentPropertyConfig build();
+ method public androidx.appsearch.app.AppSearchSchema.DocumentPropertyConfig.Builder setCardinality(int);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SET_DESCRIPTION) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.DocumentPropertyConfig.Builder setDescription(String);
+ method public androidx.appsearch.app.AppSearchSchema.DocumentPropertyConfig.Builder setShouldIndexNestedProperties(boolean);
+ }
+
+ public static final class AppSearchSchema.DoublePropertyConfig extends androidx.appsearch.app.AppSearchSchema.PropertyConfig {
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public boolean isScoringEnabled();
+ }
+
+ public static final class AppSearchSchema.DoublePropertyConfig.Builder {
+ ctor public AppSearchSchema.DoublePropertyConfig.Builder(String);
+ method public androidx.appsearch.app.AppSearchSchema.DoublePropertyConfig build();
+ method public androidx.appsearch.app.AppSearchSchema.DoublePropertyConfig.Builder setCardinality(int);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SET_DESCRIPTION) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.DoublePropertyConfig.Builder setDescription(String);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SCORABLE_PROPERTY_CONFIG) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.DoublePropertyConfig.Builder setScoringEnabled(boolean);
+ }
+
+ @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public static final class AppSearchSchema.EmbeddingPropertyConfig extends androidx.appsearch.app.AppSearchSchema.PropertyConfig {
+ method public int getIndexingType();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public int getQuantizationType();
+ field public static final int INDEXING_TYPE_NONE = 0; // 0x0
+ field public static final int INDEXING_TYPE_SIMILARITY = 1; // 0x1
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final int QUANTIZATION_TYPE_8_BIT = 1; // 0x1
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final int QUANTIZATION_TYPE_NONE = 0; // 0x0
+ }
+
+ public static final class AppSearchSchema.EmbeddingPropertyConfig.Builder {
+ ctor public AppSearchSchema.EmbeddingPropertyConfig.Builder(String);
+ method public androidx.appsearch.app.AppSearchSchema.EmbeddingPropertyConfig build();
+ method public androidx.appsearch.app.AppSearchSchema.EmbeddingPropertyConfig.Builder setCardinality(int);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SET_DESCRIPTION) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.EmbeddingPropertyConfig.Builder setDescription(String);
+ method public androidx.appsearch.app.AppSearchSchema.EmbeddingPropertyConfig.Builder setIndexingType(int);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_QUANTIZATION) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.EmbeddingPropertyConfig.Builder setQuantizationType(@SuppressCompatibility int);
+ }
+
+ public static final class AppSearchSchema.LongPropertyConfig extends androidx.appsearch.app.AppSearchSchema.PropertyConfig {
+ method public int getIndexingType();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public boolean isScoringEnabled();
+ field public static final int INDEXING_TYPE_NONE = 0; // 0x0
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.NUMERIC_SEARCH) public static final int INDEXING_TYPE_RANGE = 1; // 0x1
+ }
+
+ public static final class AppSearchSchema.LongPropertyConfig.Builder {
+ ctor public AppSearchSchema.LongPropertyConfig.Builder(String);
+ method public androidx.appsearch.app.AppSearchSchema.LongPropertyConfig build();
+ method public androidx.appsearch.app.AppSearchSchema.LongPropertyConfig.Builder setCardinality(int);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SET_DESCRIPTION) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.LongPropertyConfig.Builder setDescription(String);
+ method public androidx.appsearch.app.AppSearchSchema.LongPropertyConfig.Builder setIndexingType(int);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SCORABLE_PROPERTY_CONFIG) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.LongPropertyConfig.Builder setScoringEnabled(boolean);
+ }
+
+ public abstract static class AppSearchSchema.PropertyConfig {
+ method public int getCardinality();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public String getDescription();
+ method public String getName();
+ field public static final int CARDINALITY_OPTIONAL = 2; // 0x2
+ field public static final int CARDINALITY_REPEATED = 1; // 0x1
+ field public static final int CARDINALITY_REQUIRED = 3; // 0x3
+ }
+
+ public static final class AppSearchSchema.StringPropertyConfig extends androidx.appsearch.app.AppSearchSchema.PropertyConfig {
+ method public int getIndexingType();
+ method public int getJoinableValueType();
+ method public int getTokenizerType();
+ field public static final int INDEXING_TYPE_EXACT_TERMS = 1; // 0x1
+ field public static final int INDEXING_TYPE_NONE = 0; // 0x0
+ field public static final int INDEXING_TYPE_PREFIXES = 2; // 0x2
+ field public static final int JOINABLE_VALUE_TYPE_NONE = 0; // 0x0
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.JOIN_SPEC_AND_QUALIFIED_ID) public static final int JOINABLE_VALUE_TYPE_QUALIFIED_ID = 1; // 0x1
+ field public static final int TOKENIZER_TYPE_NONE = 0; // 0x0
+ field public static final int TOKENIZER_TYPE_PLAIN = 1; // 0x1
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.TOKENIZER_TYPE_RFC822) public static final int TOKENIZER_TYPE_RFC822 = 3; // 0x3
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.VERBATIM_SEARCH) public static final int TOKENIZER_TYPE_VERBATIM = 2; // 0x2
+ }
+
+ public static final class AppSearchSchema.StringPropertyConfig.Builder {
+ ctor public AppSearchSchema.StringPropertyConfig.Builder(String);
+ method public androidx.appsearch.app.AppSearchSchema.StringPropertyConfig build();
+ method public androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.Builder setCardinality(int);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SET_DESCRIPTION) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.Builder setDescription(String);
+ method public androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.Builder setIndexingType(int);
+ method public androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.Builder setJoinableValueType(int);
+ method public androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.Builder setTokenizerType(int);
+ }
+
+ public interface AppSearchSession extends java.io.Closeable {
+ method public void close();
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name="BLOB_STORAGE") @androidx.appsearch.app.ExperimentalAppSearchApi public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.CommitBlobResponse!> commitBlobAsync(java.util.Set<androidx.appsearch.app.AppSearchBlobHandle!>);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getByDocumentIdAsync(androidx.appsearch.app.GetByDocumentIdRequest);
+ method public androidx.appsearch.app.Features getFeatures();
+ method public com.google.common.util.concurrent.ListenableFuture<java.util.Set<java.lang.String!>!> getNamespacesAsync();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchemaAsync();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.StorageInfo!> getStorageInfoAsync();
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name="BLOB_STORAGE") @androidx.appsearch.app.ExperimentalAppSearchApi public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.OpenBlobForReadResponse!> openBlobForReadAsync(java.util.Set<androidx.appsearch.app.AppSearchBlobHandle!>);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name="BLOB_STORAGE") @androidx.appsearch.app.ExperimentalAppSearchApi public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.OpenBlobForWriteResponse!> openBlobForWriteAsync(java.util.Set<androidx.appsearch.app.AppSearchBlobHandle!>);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,java.lang.Void!>!> putAsync(androidx.appsearch.app.PutDocumentsRequest);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,java.lang.Void!>!> removeAsync(androidx.appsearch.app.RemoveByDocumentIdRequest);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> removeAsync(String, androidx.appsearch.app.SearchSpec);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name="BLOB_STORAGE") @androidx.appsearch.app.ExperimentalAppSearchApi public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.RemoveBlobResponse!> removeBlobAsync(java.util.Set<androidx.appsearch.app.AppSearchBlobHandle!>);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> reportUsageAsync(androidx.appsearch.app.ReportUsageRequest);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> requestFlushAsync();
+ method public androidx.appsearch.app.SearchResults search(String, androidx.appsearch.app.SearchSpec);
+ method public com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.appsearch.app.SearchSuggestionResult!>!> searchSuggestionAsync(String, androidx.appsearch.app.SearchSuggestionSpec);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name="BLOB_STORAGE") @androidx.appsearch.app.ExperimentalAppSearchApi public default com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setBlobVisibilityAsync(androidx.appsearch.app.SetBlobVisibilityRequest);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.SetSchemaResponse!> setSchemaAsync(androidx.appsearch.app.SetSchemaRequest);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class CommitBlobResponse {
+ ctor public CommitBlobResponse(androidx.appsearch.app.AppSearchBatchResult<androidx.appsearch.app.AppSearchBlobHandle!,java.lang.Void!>);
+ method public androidx.appsearch.app.AppSearchBatchResult<androidx.appsearch.app.AppSearchBlobHandle!,java.lang.Void!> getResult();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<androidx.appsearch.app.CommitBlobResponse!> CREATOR;
+ }
+
+ public interface DocumentClassFactory<T> {
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public T fromGenericDocument(androidx.appsearch.app.GenericDocument, androidx.appsearch.app.DocumentClassMappingContext) throws androidx.appsearch.exceptions.AppSearchException;
+ method public java.util.List<java.lang.Class<? extends java.lang.Object!>!> getDependencyDocumentClasses() throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.AppSearchSchema getSchema() throws androidx.appsearch.exceptions.AppSearchException;
+ method public String getSchemaName();
+ method public androidx.appsearch.app.GenericDocument toGenericDocument(T) throws androidx.appsearch.exceptions.AppSearchException;
+ }
+
+ public class DocumentClassMappingContext {
+ ctor @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public DocumentClassMappingContext(java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!>?, java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!>?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getDocumentClassMap();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getParentTypeMap();
+ }
+
+ @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public final class EmbeddingVector {
+ ctor public EmbeddingVector(float[], String);
+ method public String getModelSignature();
+ method public float[] getValues();
+ }
+
+ @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ENTERPRISE_GLOBAL_SEARCH_SESSION) public interface EnterpriseGlobalSearchSession {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getByDocumentIdAsync(String, String, androidx.appsearch.app.GetByDocumentIdRequest);
+ method public androidx.appsearch.app.Features getFeatures();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchemaAsync(String, String);
+ method public androidx.appsearch.app.SearchResults search(String, androidx.appsearch.app.SearchSpec);
+ }
+
+ @SuppressCompatibility @RequiresOptIn(level=androidx.annotation.RequiresOptIn.Level.ERROR) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalAppSearchApi {
+ }
+
+ public interface Features {
+ method public int getMaxIndexedProperties();
+ method public boolean isFeatureSupported(String);
+ field public static final String ADD_PERMISSIONS_AND_GET_VISIBILITY = "ADD_PERMISSIONS_AND_GET_VISIBILITY";
+ field public static final String ENTERPRISE_GLOBAL_SEARCH_SESSION = "ENTERPRISE_GLOBAL_SEARCH_SESSION";
+ field public static final String GLOBAL_SEARCH_SESSION_GET_BY_ID = "GLOBAL_SEARCH_SESSION_GET_BY_ID";
+ field public static final String GLOBAL_SEARCH_SESSION_GET_SCHEMA = "GLOBAL_SEARCH_SESSION_GET_SCHEMA";
+ field public static final String GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK = "GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK";
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String INDEXER_MOBILE_APPLICATIONS = "INDEXER_MOBILE_APPLICATIONS";
+ field public static final String JOIN_SPEC_AND_QUALIFIED_ID = "JOIN_SPEC_AND_QUALIFIED_ID";
+ field public static final String LIST_FILTER_HAS_PROPERTY_FUNCTION = "LIST_FILTER_HAS_PROPERTY_FUNCTION";
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String LIST_FILTER_MATCH_SCORE_EXPRESSION_FUNCTION = "LIST_FILTER_MATCH_SCORE_EXPRESSION_FUNCTION";
+ field public static final String LIST_FILTER_QUERY_LANGUAGE = "LIST_FILTER_QUERY_LANGUAGE";
+ field public static final String NUMERIC_SEARCH = "NUMERIC_SEARCH";
+ field public static final String SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES = "SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES";
+ field public static final String SCHEMA_ADD_PARENT_TYPE = "SCHEMA_ADD_PARENT_TYPE";
+ field public static final String SCHEMA_EMBEDDING_PROPERTY_CONFIG = "SCHEMA_EMBEDDING_PROPERTY_CONFIG";
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String SCHEMA_EMBEDDING_QUANTIZATION = "SCHEMA_EMBEDDING_QUANTIZATION";
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String SCHEMA_SCORABLE_PROPERTY_CONFIG = "SCHEMA_SCORABLE_PROPERTY_CONFIG";
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String SCHEMA_SET_DESCRIPTION = "SCHEMA_SET_DESCRIPTION";
+ field public static final String SEARCH_RESULT_MATCH_INFO_SUBMATCH = "SEARCH_RESULT_MATCH_INFO_SUBMATCH";
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String SEARCH_RESULT_PARENT_TYPES = "SEARCH_RESULT_PARENT_TYPES";
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String SEARCH_SPEC_ADD_FILTER_DOCUMENT_IDS = "SEARCH_SPEC_ADD_FILTER_DOCUMENT_IDS";
+ field public static final String SEARCH_SPEC_ADD_FILTER_PROPERTIES = "SEARCH_SPEC_ADD_FILTER_PROPERTIES";
+ field public static final String SEARCH_SPEC_ADD_INFORMATIONAL_RANKING_EXPRESSIONS = "SEARCH_SPEC_ADD_INFORMATIONAL_RANKING_EXPRESSIONS";
+ field public static final String SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION = "SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION";
+ field public static final String SEARCH_SPEC_GROUPING_TYPE_PER_SCHEMA = "SEARCH_SPEC_GROUPING_TYPE_PER_SCHEMA";
+ field public static final String SEARCH_SPEC_PROPERTY_WEIGHTS = "SEARCH_SPEC_PROPERTY_WEIGHTS";
+ field public static final String SEARCH_SPEC_SEARCH_STRING_PARAMETERS = "SEARCH_SPEC_SEARCH_STRING_PARAMETERS";
+ field public static final String SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG = "SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG";
+ field public static final String SEARCH_SUGGESTION = "SEARCH_SUGGESTION";
+ field public static final String SET_SCHEMA_CIRCULAR_REFERENCES = "SET_SCHEMA_CIRCULAR_REFERENCES";
+ field public static final String SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG = "SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG";
+ field public static final String SET_SCHEMA_REQUEST_SET_PUBLICLY_VISIBLE = "SET_SCHEMA_REQUEST_SET_PUBLICLY_VISIBLE";
+ field public static final String TOKENIZER_TYPE_RFC822 = "TOKENIZER_TYPE_RFC822";
+ field public static final String VERBATIM_SEARCH = "VERBATIM_SEARCH";
+ }
+
+ public class GenericDocument {
+ ctor protected GenericDocument(androidx.appsearch.app.GenericDocument);
+ method public static androidx.appsearch.app.GenericDocument fromDocumentClass(Object) throws androidx.appsearch.exceptions.AppSearchException;
+ method public long getCreationTimestampMillis();
+ method public String getId();
+ method public String getNamespace();
+ method public Object? getProperty(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchBlobHandle? getPropertyBlobHandle(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.AppSearchBlobHandle![]? getPropertyBlobHandleArray(String);
+ method public boolean getPropertyBoolean(String);
+ method public boolean[]? getPropertyBooleanArray(String);
+ method public byte[]? getPropertyBytes(String);
+ method public byte[]![]? getPropertyBytesArray(String);
+ method public androidx.appsearch.app.GenericDocument? getPropertyDocument(String);
+ method public androidx.appsearch.app.GenericDocument![]? getPropertyDocumentArray(String);
+ method public double getPropertyDouble(String);
+ method public double[]? getPropertyDoubleArray(String);
+ method public androidx.appsearch.app.EmbeddingVector? getPropertyEmbedding(String);
+ method public androidx.appsearch.app.EmbeddingVector![]? getPropertyEmbeddingArray(String);
+ method public long getPropertyLong(String);
+ method public long[]? getPropertyLongArray(String);
+ method public java.util.Set<java.lang.String!> getPropertyNames();
+ method public String? getPropertyString(String);
+ method public String![]? getPropertyStringArray(String);
+ method public String getSchemaType();
+ method public int getScore();
+ method public long getTtlMillis();
+ method public <T> T toDocumentClass(Class<T!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public <T> T toDocumentClass(Class<T!>, androidx.appsearch.app.DocumentClassMappingContext) throws androidx.appsearch.exceptions.AppSearchException;
+ }
+
+ public static class GenericDocument.Builder<BuilderType extends androidx.appsearch.app.GenericDocument.Builder> {
+ ctor public GenericDocument.Builder(androidx.appsearch.app.GenericDocument);
+ ctor public GenericDocument.Builder(String, String, String);
+ method public androidx.appsearch.app.GenericDocument build();
+ method public BuilderType clearProperty(String);
+ method public BuilderType setCreationTimestampMillis(long);
+ method public BuilderType setId(String);
+ method public BuilderType setNamespace(String);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name="BLOB_STORAGE") @androidx.appsearch.app.ExperimentalAppSearchApi public BuilderType setPropertyBlobHandle(String, androidx.appsearch.app.AppSearchBlobHandle!...);
+ method public BuilderType setPropertyBoolean(String, boolean...);
+ method public BuilderType setPropertyBytes(String, byte[]!...);
+ method public BuilderType setPropertyDocument(String, androidx.appsearch.app.GenericDocument!...);
+ method public BuilderType setPropertyDouble(String, double...);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public BuilderType setPropertyEmbedding(String, androidx.appsearch.app.EmbeddingVector!...);
+ method public BuilderType setPropertyLong(String, long...);
+ method public BuilderType setPropertyString(String, java.lang.String!...);
+ method public BuilderType setSchemaType(String);
+ method public BuilderType setScore(@IntRange(from=0, to=java.lang.Integer.MAX_VALUE) int);
+ method public BuilderType setTtlMillis(long);
+ }
+
+ public final class GetByDocumentIdRequest {
+ method public java.util.Set<java.lang.String!> getIds();
+ method public String getNamespace();
+ method public java.util.Map<java.lang.String!,java.util.List<androidx.appsearch.app.PropertyPath!>!> getProjectionPaths();
+ method public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getProjections();
+ field public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*";
+ }
+
+ public static final class GetByDocumentIdRequest.Builder {
+ ctor public GetByDocumentIdRequest.Builder(String);
+ method public androidx.appsearch.app.GetByDocumentIdRequest.Builder addIds(java.lang.String!...);
+ method public androidx.appsearch.app.GetByDocumentIdRequest.Builder addIds(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.GetByDocumentIdRequest.Builder addProjection(String, java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.GetByDocumentIdRequest.Builder addProjectionPaths(String, java.util.Collection<androidx.appsearch.app.PropertyPath!>);
+ method public androidx.appsearch.app.GetByDocumentIdRequest build();
+ }
+
+ public final class GetSchemaResponse {
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public java.util.Map<java.lang.String!,androidx.appsearch.app.PackageIdentifier!> getPubliclyVisibleSchemas();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public java.util.Map<java.lang.String!,java.util.Set<java.util.Set<java.lang.Integer!>!>!> getRequiredPermissionsForSchemaTypeVisibility();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public java.util.Set<java.lang.String!> getSchemaTypesNotDisplayedBySystem();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public java.util.Map<java.lang.String!,java.util.Set<androidx.appsearch.app.SchemaVisibilityConfig!>!> getSchemaTypesVisibleToConfigs();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public java.util.Map<java.lang.String!,java.util.Set<androidx.appsearch.app.PackageIdentifier!>!> getSchemaTypesVisibleToPackages();
+ method public java.util.Set<androidx.appsearch.app.AppSearchSchema!> getSchemas();
+ method @IntRange(from=0) public int getVersion();
+ }
+
+ public static final class GetSchemaResponse.Builder {
+ ctor public GetSchemaResponse.Builder();
+ ctor @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public GetSchemaResponse.Builder(androidx.appsearch.app.GetSchemaResponse);
+ method public androidx.appsearch.app.GetSchemaResponse.Builder addSchema(androidx.appsearch.app.AppSearchSchema);
+ method public androidx.appsearch.app.GetSchemaResponse.Builder addSchemaTypeNotDisplayedBySystem(String);
+ method public androidx.appsearch.app.GetSchemaResponse build();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.GetSchemaResponse.Builder clearPubliclyVisibleSchema(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.GetSchemaResponse.Builder clearRequiredPermissionsForSchemaTypeVisibility(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.GetSchemaResponse.Builder clearSchemaTypeNotDisplayedBySystem(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.GetSchemaResponse.Builder clearSchemaTypeVisibleToConfigs(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.GetSchemaResponse.Builder clearSchemaTypeVisibleToPackages(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.GetSchemaResponse.Builder clearSchemas();
+ method public androidx.appsearch.app.GetSchemaResponse.Builder setPubliclyVisibleSchema(String, androidx.appsearch.app.PackageIdentifier);
+ method public androidx.appsearch.app.GetSchemaResponse.Builder setRequiredPermissionsForSchemaTypeVisibility(String, java.util.Set<java.util.Set<java.lang.Integer!>!>);
+ method public androidx.appsearch.app.GetSchemaResponse.Builder setSchemaTypeVisibleToConfigs(String, java.util.Set<androidx.appsearch.app.SchemaVisibilityConfig!>);
+ method public androidx.appsearch.app.GetSchemaResponse.Builder setSchemaTypeVisibleToPackages(String, java.util.Set<androidx.appsearch.app.PackageIdentifier!>);
+ method public androidx.appsearch.app.GetSchemaResponse.Builder setVersion(@IntRange(from=0) int);
+ method public androidx.appsearch.app.GetSchemaResponse.Builder setVisibilitySettingSupported(boolean);
+ }
+
+ public interface GlobalSearchSession extends java.io.Closeable {
+ method public void close();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_GET_BY_ID) public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getByDocumentIdAsync(String, String, androidx.appsearch.app.GetByDocumentIdRequest);
+ method public androidx.appsearch.app.Features getFeatures();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_GET_SCHEMA) public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchemaAsync(String, String);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name="BLOB_STORAGE") @androidx.appsearch.app.ExperimentalAppSearchApi public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.OpenBlobForReadResponse!> openBlobForReadAsync(java.util.Set<androidx.appsearch.app.AppSearchBlobHandle!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK) public void registerObserverCallback(String, androidx.appsearch.observer.ObserverSpec, java.util.concurrent.Executor, androidx.appsearch.observer.ObserverCallback) throws androidx.appsearch.exceptions.AppSearchException;
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> reportSystemUsageAsync(androidx.appsearch.app.ReportSystemUsageRequest);
+ method public androidx.appsearch.app.SearchResults search(String, androidx.appsearch.app.SearchSpec);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK) public void unregisterObserverCallback(String, androidx.appsearch.observer.ObserverCallback) throws androidx.appsearch.exceptions.AppSearchException;
+ }
+
+ public final class JoinSpec {
+ method public int getAggregationScoringStrategy();
+ method public String getChildPropertyExpression();
+ method public int getMaxJoinedResultCount();
+ method public String getNestedQuery();
+ method public androidx.appsearch.app.SearchSpec getNestedSearchSpec();
+ field public static final int AGGREGATION_SCORING_AVG_RANKING_SIGNAL = 3; // 0x3
+ field public static final int AGGREGATION_SCORING_MAX_RANKING_SIGNAL = 4; // 0x4
+ field public static final int AGGREGATION_SCORING_MIN_RANKING_SIGNAL = 2; // 0x2
+ field public static final int AGGREGATION_SCORING_OUTER_RESULT_RANKING_SIGNAL = 0; // 0x0
+ field public static final int AGGREGATION_SCORING_RESULT_COUNT = 1; // 0x1
+ field public static final int AGGREGATION_SCORING_SUM_RANKING_SIGNAL = 5; // 0x5
+ }
+
+ public static final class JoinSpec.Builder {
+ ctor @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public JoinSpec.Builder(androidx.appsearch.app.JoinSpec);
+ ctor public JoinSpec.Builder(String);
+ method public androidx.appsearch.app.JoinSpec build();
+ method public androidx.appsearch.app.JoinSpec.Builder setAggregationScoringStrategy(int);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.JoinSpec.Builder setChildPropertyExpression(String);
+ method public androidx.appsearch.app.JoinSpec.Builder setMaxJoinedResultCount(int);
+ method public androidx.appsearch.app.JoinSpec.Builder setNestedSearch(String, androidx.appsearch.app.SearchSpec);
+ }
+
+ public interface LongSerializer<T> {
+ method public T? deserialize(long);
+ method public long serialize(T);
+ }
+
+ public abstract class Migrator {
+ ctor public Migrator();
+ method @WorkerThread public abstract androidx.appsearch.app.GenericDocument onDowngrade(int, int, androidx.appsearch.app.GenericDocument);
+ method @WorkerThread public abstract androidx.appsearch.app.GenericDocument onUpgrade(int, int, androidx.appsearch.app.GenericDocument);
+ method public abstract boolean shouldMigrate(int, int);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class OpenBlobForReadResponse implements java.io.Closeable {
+ ctor public OpenBlobForReadResponse(androidx.appsearch.app.AppSearchBatchResult<androidx.appsearch.app.AppSearchBlobHandle!,android.os.ParcelFileDescriptor!>);
+ method public void close();
+ method public androidx.appsearch.app.AppSearchBatchResult<androidx.appsearch.app.AppSearchBlobHandle!,android.os.ParcelFileDescriptor!> getResult();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<androidx.appsearch.app.OpenBlobForReadResponse!> CREATOR;
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class OpenBlobForWriteResponse implements java.io.Closeable {
+ ctor public OpenBlobForWriteResponse(androidx.appsearch.app.AppSearchBatchResult<androidx.appsearch.app.AppSearchBlobHandle!,android.os.ParcelFileDescriptor!>);
+ method public void close();
+ method public androidx.appsearch.app.AppSearchBatchResult<androidx.appsearch.app.AppSearchBlobHandle!,android.os.ParcelFileDescriptor!> getResult();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<androidx.appsearch.app.OpenBlobForWriteResponse!> CREATOR;
+ }
+
+ public class PackageIdentifier {
+ ctor public PackageIdentifier(String, byte[]);
+ method public String getPackageName();
+ method public byte[] getSha256Certificate();
+ }
+
+ public class PropertyPath implements java.lang.Iterable<androidx.appsearch.app.PropertyPath.PathSegment!> {
+ ctor public PropertyPath(String);
+ ctor public PropertyPath(java.util.List<androidx.appsearch.app.PropertyPath.PathSegment!>);
+ method public androidx.appsearch.app.PropertyPath.PathSegment get(int);
+ method public java.util.Iterator<androidx.appsearch.app.PropertyPath.PathSegment!> iterator();
+ method public int size();
+ }
+
+ public static class PropertyPath.PathSegment {
+ method public static androidx.appsearch.app.PropertyPath.PathSegment create(String);
+ method public static androidx.appsearch.app.PropertyPath.PathSegment create(String, int);
+ method public int getPropertyIndex();
+ method public String getPropertyName();
+ field public static final int NON_REPEATED_CARDINALITY = -1; // 0xffffffff
+ }
+
+ public final class PutDocumentsRequest {
+ method public java.util.List<androidx.appsearch.app.GenericDocument!> getGenericDocuments();
+ method public java.util.List<androidx.appsearch.app.GenericDocument!> getTakenActionGenericDocuments();
+ }
+
+ public static final class PutDocumentsRequest.Builder {
+ ctor public PutDocumentsRequest.Builder();
+ method public androidx.appsearch.app.PutDocumentsRequest.Builder addDocuments(java.lang.Object!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.PutDocumentsRequest.Builder addDocuments(java.util.Collection<? extends java.lang.Object!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.PutDocumentsRequest.Builder addGenericDocuments(androidx.appsearch.app.GenericDocument!...);
+ method public androidx.appsearch.app.PutDocumentsRequest.Builder addGenericDocuments(java.util.Collection<? extends androidx.appsearch.app.GenericDocument!>);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.PutDocumentsRequest.Builder addTakenActions(androidx.appsearch.usagereporting.TakenAction!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.PutDocumentsRequest.Builder addTakenActions(java.util.Collection<? extends androidx.appsearch.usagereporting.TakenAction!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.PutDocumentsRequest build();
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class RemoveBlobResponse {
+ ctor public RemoveBlobResponse(androidx.appsearch.app.AppSearchBatchResult<androidx.appsearch.app.AppSearchBlobHandle!,java.lang.Void!>);
+ method public androidx.appsearch.app.AppSearchBatchResult<androidx.appsearch.app.AppSearchBlobHandle!,java.lang.Void!> getResult();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<androidx.appsearch.app.RemoveBlobResponse!> CREATOR;
+ }
+
+ public final class RemoveByDocumentIdRequest {
+ method public java.util.Set<java.lang.String!> getIds();
+ method public String getNamespace();
+ }
+
+ public static final class RemoveByDocumentIdRequest.Builder {
+ ctor public RemoveByDocumentIdRequest.Builder(String);
+ method public androidx.appsearch.app.RemoveByDocumentIdRequest.Builder addIds(java.lang.String!...);
+ method public androidx.appsearch.app.RemoveByDocumentIdRequest.Builder addIds(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.RemoveByDocumentIdRequest build();
+ }
+
+ public final class ReportSystemUsageRequest {
+ method public String getDatabaseName();
+ method public String getDocumentId();
+ method public String getNamespace();
+ method public String getPackageName();
+ method public long getUsageTimestampMillis();
+ }
+
+ public static final class ReportSystemUsageRequest.Builder {
+ ctor public ReportSystemUsageRequest.Builder(String, String, String, String);
+ method public androidx.appsearch.app.ReportSystemUsageRequest build();
+ method public androidx.appsearch.app.ReportSystemUsageRequest.Builder setUsageTimestampMillis(long);
+ }
+
+ public final class ReportUsageRequest {
+ method public String getDocumentId();
+ method public String getNamespace();
+ method public long getUsageTimestampMillis();
+ }
+
+ public static final class ReportUsageRequest.Builder {
+ ctor public ReportUsageRequest.Builder(String, String);
+ method public androidx.appsearch.app.ReportUsageRequest build();
+ method public androidx.appsearch.app.ReportUsageRequest.Builder setUsageTimestampMillis(long);
+ }
+
+ public final class SchemaVisibilityConfig {
+ method public java.util.List<androidx.appsearch.app.PackageIdentifier!> getAllowedPackages();
+ method public androidx.appsearch.app.PackageIdentifier? getPubliclyVisibleTargetPackage();
+ method public java.util.Set<java.util.Set<java.lang.Integer!>!> getRequiredPermissions();
+ }
+
+ public static final class SchemaVisibilityConfig.Builder {
+ ctor public SchemaVisibilityConfig.Builder();
+ method public androidx.appsearch.app.SchemaVisibilityConfig.Builder addAllowedPackage(androidx.appsearch.app.PackageIdentifier);
+ method public androidx.appsearch.app.SchemaVisibilityConfig.Builder addRequiredPermissions(java.util.Set<java.lang.Integer!>);
+ method public androidx.appsearch.app.SchemaVisibilityConfig build();
+ method public androidx.appsearch.app.SchemaVisibilityConfig.Builder clearAllowedPackages();
+ method public androidx.appsearch.app.SchemaVisibilityConfig.Builder clearRequiredPermissions();
+ method public androidx.appsearch.app.SchemaVisibilityConfig.Builder setPubliclyVisibleTargetPackage(androidx.appsearch.app.PackageIdentifier?);
+ }
+
+ public final class SearchResult {
+ method public String getDatabaseName();
+ method public <T> T getDocument(Class<T!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public <T> T getDocument(Class<T!>, java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!>?) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.GenericDocument getGenericDocument();
+ method public java.util.List<java.lang.Double!> getInformationalRankingSignals();
+ method public java.util.List<androidx.appsearch.app.SearchResult!> getJoinedResults();
+ method public java.util.List<androidx.appsearch.app.SearchResult.MatchInfo!> getMatchInfos();
+ method public String getPackageName();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getParentTypeMap();
+ method public double getRankingSignal();
+ }
+
+ public static final class SearchResult.Builder {
+ ctor public SearchResult.Builder(String, String);
+ method public androidx.appsearch.app.SearchResult.Builder addInformationalRankingSignal(double);
+ method public androidx.appsearch.app.SearchResult.Builder addJoinedResult(androidx.appsearch.app.SearchResult);
+ method public androidx.appsearch.app.SearchResult.Builder addMatchInfo(androidx.appsearch.app.SearchResult.MatchInfo);
+ method public androidx.appsearch.app.SearchResult build();
+ method public androidx.appsearch.app.SearchResult.Builder setDocument(Object) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SearchResult.Builder setGenericDocument(androidx.appsearch.app.GenericDocument);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchResult.Builder setParentTypeMap(java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!>);
+ method public androidx.appsearch.app.SearchResult.Builder setRankingSignal(double);
+ }
+
+ public static final class SearchResult.MatchInfo {
+ method public CharSequence getExactMatch();
+ method public androidx.appsearch.app.SearchResult.MatchRange getExactMatchRange();
+ method public String getFullText();
+ method public String getPropertyPath();
+ method public androidx.appsearch.app.PropertyPath getPropertyPathObject();
+ method public CharSequence getSnippet();
+ method public androidx.appsearch.app.SearchResult.MatchRange getSnippetRange();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_RESULT_MATCH_INFO_SUBMATCH) public CharSequence getSubmatch();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_RESULT_MATCH_INFO_SUBMATCH) public androidx.appsearch.app.SearchResult.MatchRange getSubmatchRange();
+ }
+
+ public static final class SearchResult.MatchInfo.Builder {
+ ctor public SearchResult.MatchInfo.Builder(String);
+ method public androidx.appsearch.app.SearchResult.MatchInfo build();
+ method public androidx.appsearch.app.SearchResult.MatchInfo.Builder setExactMatchRange(androidx.appsearch.app.SearchResult.MatchRange);
+ method public androidx.appsearch.app.SearchResult.MatchInfo.Builder setSnippetRange(androidx.appsearch.app.SearchResult.MatchRange);
+ method public androidx.appsearch.app.SearchResult.MatchInfo.Builder setSubmatchRange(androidx.appsearch.app.SearchResult.MatchRange);
+ }
+
+ public static final class SearchResult.MatchRange {
+ ctor public SearchResult.MatchRange(int, int);
+ method public int getEnd();
+ method public int getStart();
+ }
+
+ public interface SearchResults extends java.io.Closeable {
+ method public void close();
+ method public com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.appsearch.app.SearchResult!>!> getNextPageAsync();
+ }
+
+ public final class SearchSpec {
+ method public String getAdvancedRankingExpression();
+ method public int getDefaultEmbeddingSearchMetricType();
+ method public java.util.List<androidx.appsearch.app.EmbeddingVector!> getEmbeddingParameters();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public java.util.List<java.lang.String!> getFilterDocumentIds();
+ method public java.util.List<java.lang.String!> getFilterNamespaces();
+ method public java.util.List<java.lang.String!> getFilterPackageNames();
+ method public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getFilterProperties();
+ method public java.util.List<java.lang.String!> getFilterSchemas();
+ method public java.util.List<java.lang.String!> getInformationalRankingExpressions();
+ method public androidx.appsearch.app.JoinSpec? getJoinSpec();
+ method public int getMaxSnippetSize();
+ method public int getOrder();
+ method public java.util.Map<java.lang.String!,java.util.List<androidx.appsearch.app.PropertyPath!>!> getProjectionPaths();
+ method public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getProjections();
+ method public java.util.Map<java.lang.String!,java.util.Map<androidx.appsearch.app.PropertyPath!,java.lang.Double!>!> getPropertyWeightPaths();
+ method public java.util.Map<java.lang.String!,java.util.Map<java.lang.String!,java.lang.Double!>!> getPropertyWeights();
+ method public int getRankingStrategy();
+ method public int getResultCountPerPage();
+ method public int getResultGroupingLimit();
+ method public int getResultGroupingTypeFlags();
+ method public String? getSearchSourceLogTag();
+ method public java.util.List<java.lang.String!> getSearchStringParameters();
+ method public int getSnippetCount();
+ method public int getSnippetCountPerProperty();
+ method public int getTermMatch();
+ method public boolean isListFilterHasPropertyFunctionEnabled();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public boolean isListFilterMatchScoreExpressionFunctionEnabled();
+ method public boolean isListFilterQueryLanguageEnabled();
+ method public boolean isNumericSearchEnabled();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public boolean isScorablePropertyRankingEnabled();
+ method public boolean isVerbatimSearchEnabled();
+ field public static final int EMBEDDING_SEARCH_METRIC_TYPE_COSINE = 1; // 0x1
+ field public static final int EMBEDDING_SEARCH_METRIC_TYPE_DEFAULT = 0; // 0x0
+ field public static final int EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT = 2; // 0x2
+ field public static final int EMBEDDING_SEARCH_METRIC_TYPE_EUCLIDEAN = 3; // 0x3
+ field public static final int GROUPING_TYPE_PER_NAMESPACE = 2; // 0x2
+ field public static final int GROUPING_TYPE_PER_PACKAGE = 1; // 0x1
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_GROUPING_TYPE_PER_SCHEMA) public static final int GROUPING_TYPE_PER_SCHEMA = 4; // 0x4
+ field public static final int ORDER_ASCENDING = 1; // 0x1
+ field public static final int ORDER_DESCENDING = 0; // 0x0
+ field public static final int RANKING_STRATEGY_ADVANCED_RANKING_EXPRESSION = 9; // 0x9
+ field public static final int RANKING_STRATEGY_CREATION_TIMESTAMP = 2; // 0x2
+ field public static final int RANKING_STRATEGY_DOCUMENT_SCORE = 1; // 0x1
+ field public static final int RANKING_STRATEGY_JOIN_AGGREGATE_SCORE = 8; // 0x8
+ field public static final int RANKING_STRATEGY_NONE = 0; // 0x0
+ field public static final int RANKING_STRATEGY_RELEVANCE_SCORE = 3; // 0x3
+ field public static final int RANKING_STRATEGY_SYSTEM_USAGE_COUNT = 6; // 0x6
+ field public static final int RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP = 7; // 0x7
+ field public static final int RANKING_STRATEGY_USAGE_COUNT = 4; // 0x4
+ field public static final int RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP = 5; // 0x5
+ field public static final String SCHEMA_TYPE_WILDCARD = "*";
+ field public static final int TERM_MATCH_EXACT_ONLY = 1; // 0x1
+ field public static final int TERM_MATCH_PREFIX = 2; // 0x2
+ }
+
+ public static final class SearchSpec.Builder {
+ ctor public SearchSpec.Builder();
+ ctor @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public SearchSpec.Builder(androidx.appsearch.app.SearchSpec);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addEmbeddingParameters(androidx.appsearch.app.EmbeddingVector!...);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addEmbeddingParameters(java.util.Collection<androidx.appsearch.app.EmbeddingVector!>);
+ method public androidx.appsearch.app.SearchSpec.Builder addFilterDocumentClasses(Class<? extends java.lang.Object!>!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SearchSpec.Builder addFilterDocumentClasses(java.util.Collection<? extends java.lang.Class<? extends java.lang.Object!>!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_DOCUMENT_IDS) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder addFilterDocumentIds(java.lang.String!...);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_DOCUMENT_IDS) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder addFilterDocumentIds(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.SearchSpec.Builder addFilterNamespaces(java.lang.String!...);
+ method public androidx.appsearch.app.SearchSpec.Builder addFilterNamespaces(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.SearchSpec.Builder addFilterPackageNames(java.lang.String!...);
+ method public androidx.appsearch.app.SearchSpec.Builder addFilterPackageNames(java.util.Collection<java.lang.String!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) public androidx.appsearch.app.SearchSpec.Builder addFilterProperties(Class<? extends java.lang.Object!>, java.util.Collection<java.lang.String!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) public androidx.appsearch.app.SearchSpec.Builder addFilterProperties(String, java.util.Collection<java.lang.String!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) public androidx.appsearch.app.SearchSpec.Builder addFilterPropertyPaths(Class<? extends java.lang.Object!>, java.util.Collection<androidx.appsearch.app.PropertyPath!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) public androidx.appsearch.app.SearchSpec.Builder addFilterPropertyPaths(String, java.util.Collection<androidx.appsearch.app.PropertyPath!>);
+ method public androidx.appsearch.app.SearchSpec.Builder addFilterSchemas(java.lang.String!...);
+ method public androidx.appsearch.app.SearchSpec.Builder addFilterSchemas(java.util.Collection<java.lang.String!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_INFORMATIONAL_RANKING_EXPRESSIONS) public androidx.appsearch.app.SearchSpec.Builder addInformationalRankingExpressions(java.lang.String!...);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_INFORMATIONAL_RANKING_EXPRESSIONS) public androidx.appsearch.app.SearchSpec.Builder addInformationalRankingExpressions(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.SearchSpec.Builder addProjection(String, java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.SearchSpec.Builder addProjectionPaths(String, java.util.Collection<androidx.appsearch.app.PropertyPath!>);
+ method public androidx.appsearch.app.SearchSpec.Builder addProjectionPathsForDocumentClass(Class<? extends java.lang.Object!>, java.util.Collection<androidx.appsearch.app.PropertyPath!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SearchSpec.Builder addProjectionsForDocumentClass(Class<? extends java.lang.Object!>, java.util.Collection<java.lang.String!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSpec.Builder addSearchStringParameters(java.lang.String!...);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSpec.Builder addSearchStringParameters(java.util.List<java.lang.String!>);
+ method public androidx.appsearch.app.SearchSpec build();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearEmbeddingParameters();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearFilterDocumentIds();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearFilterNamespaces();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearFilterPackageNames();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearFilterProperties();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearFilterSchemas();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearInformationalRankingExpressions();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearJoinSpec();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearProjections();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearPropertyWeights();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearResultGrouping();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearSearchSourceLogTag();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder clearSearchStringParameters();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder setDefaultEmbeddingSearchMetricType(int);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.JOIN_SPEC_AND_QUALIFIED_ID) public androidx.appsearch.app.SearchSpec.Builder setJoinSpec(androidx.appsearch.app.JoinSpec);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.LIST_FILTER_HAS_PROPERTY_FUNCTION) public androidx.appsearch.app.SearchSpec.Builder setListFilterHasPropertyFunctionEnabled(boolean);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.LIST_FILTER_MATCH_SCORE_EXPRESSION_FUNCTION) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder setListFilterMatchScoreExpressionFunctionEnabled(boolean);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.LIST_FILTER_QUERY_LANGUAGE) public androidx.appsearch.app.SearchSpec.Builder setListFilterQueryLanguageEnabled(boolean);
+ method public androidx.appsearch.app.SearchSpec.Builder setMaxSnippetSize(@IntRange(from=0, to=0x2710) int);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.NUMERIC_SEARCH) public androidx.appsearch.app.SearchSpec.Builder setNumericSearchEnabled(boolean);
+ method public androidx.appsearch.app.SearchSpec.Builder setOrder(int);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_PROPERTY_WEIGHTS) public androidx.appsearch.app.SearchSpec.Builder setPropertyWeightPaths(String, java.util.Map<androidx.appsearch.app.PropertyPath!,java.lang.Double!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_PROPERTY_WEIGHTS) public androidx.appsearch.app.SearchSpec.Builder setPropertyWeightPathsForDocumentClass(Class<? extends java.lang.Object!>, java.util.Map<androidx.appsearch.app.PropertyPath!,java.lang.Double!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_PROPERTY_WEIGHTS) public androidx.appsearch.app.SearchSpec.Builder setPropertyWeights(String, java.util.Map<java.lang.String!,java.lang.Double!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_PROPERTY_WEIGHTS) public androidx.appsearch.app.SearchSpec.Builder setPropertyWeightsForDocumentClass(Class<? extends java.lang.Object!>, java.util.Map<java.lang.String!,java.lang.Double!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SearchSpec.Builder setRankingStrategy(int);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION) public androidx.appsearch.app.SearchSpec.Builder setRankingStrategy(String);
+ method public androidx.appsearch.app.SearchSpec.Builder setResultCountPerPage(@IntRange(from=0, to=0x2710) int);
+ method public androidx.appsearch.app.SearchSpec.Builder setResultGrouping(int, int);
+ method @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_SCORABLE_PROPERTY_CONFIG) @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchSpec.Builder setScorablePropertyRankingEnabled(boolean);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG) public androidx.appsearch.app.SearchSpec.Builder setSearchSourceLogTag(String);
+ method public androidx.appsearch.app.SearchSpec.Builder setSnippetCount(@IntRange(from=0, to=0x2710) int);
+ method public androidx.appsearch.app.SearchSpec.Builder setSnippetCountPerProperty(@IntRange(from=0, to=0x2710) int);
+ method public androidx.appsearch.app.SearchSpec.Builder setTermMatch(int);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.VERBATIM_SEARCH) public androidx.appsearch.app.SearchSpec.Builder setVerbatimSearchEnabled(boolean);
+ }
+
+ public final class SearchSuggestionResult {
+ method public String getSuggestedResult();
+ }
+
+ public static final class SearchSuggestionResult.Builder {
+ ctor public SearchSuggestionResult.Builder();
+ method public androidx.appsearch.app.SearchSuggestionResult build();
+ method public androidx.appsearch.app.SearchSuggestionResult.Builder setSuggestedResult(String);
+ }
+
+ public final class SearchSuggestionSpec {
+ method public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getFilterDocumentIds();
+ method public java.util.List<java.lang.String!> getFilterNamespaces();
+ method public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getFilterProperties();
+ method public java.util.List<java.lang.String!> getFilterSchemas();
+ method public int getMaximumResultCount();
+ method public int getRankingStrategy();
+ method public java.util.List<java.lang.String!> getSearchStringParameters();
+ field public static final int SUGGESTION_RANKING_STRATEGY_DOCUMENT_COUNT = 0; // 0x0
+ field public static final int SUGGESTION_RANKING_STRATEGY_NONE = 2; // 0x2
+ field public static final int SUGGESTION_RANKING_STRATEGY_TERM_FREQUENCY = 1; // 0x1
+ }
+
+ public static final class SearchSuggestionSpec.Builder {
+ ctor public SearchSuggestionSpec.Builder(@IntRange(from=1) int);
+ method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterDocumentClasses(Class<? extends java.lang.Object!>!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterDocumentClasses(java.util.Collection<? extends java.lang.Class<? extends java.lang.Object!>!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterDocumentIds(String, java.lang.String!...);
+ method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterDocumentIds(String, java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterNamespaces(java.lang.String!...);
+ method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterNamespaces(java.util.Collection<java.lang.String!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterProperties(Class<? extends java.lang.Object!>, java.util.Collection<java.lang.String!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterProperties(String, java.util.Collection<java.lang.String!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterPropertyPaths(Class<? extends java.lang.Object!>, java.util.Collection<androidx.appsearch.app.PropertyPath!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterPropertyPaths(String, java.util.Collection<androidx.appsearch.app.PropertyPath!>);
+ method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterSchemas(java.lang.String!...);
+ method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterSchemas(java.util.Collection<java.lang.String!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSuggestionSpec.Builder addSearchStringParameters(java.lang.String!...);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSuggestionSpec.Builder addSearchStringParameters(java.util.List<java.lang.String!>);
+ method public androidx.appsearch.app.SearchSuggestionSpec build();
+ method public androidx.appsearch.app.SearchSuggestionSpec.Builder setRankingStrategy(int);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public class SetBlobVisibilityRequest {
+ method public java.util.Set<java.lang.String!> getNamespacesNotDisplayedBySystem();
+ method public java.util.Map<java.lang.String!,java.util.Set<androidx.appsearch.app.SchemaVisibilityConfig!>!> getNamespacesVisibleToConfigs();
+ }
+
+ public static final class SetBlobVisibilityRequest.Builder {
+ ctor public SetBlobVisibilityRequest.Builder();
+ method public androidx.appsearch.app.SetBlobVisibilityRequest.Builder addNamespaceVisibleToConfig(String, androidx.appsearch.app.SchemaVisibilityConfig);
+ method public androidx.appsearch.app.SetBlobVisibilityRequest build();
+ method public androidx.appsearch.app.SetBlobVisibilityRequest.Builder clearNamespaceVisibleToConfigs(String);
+ method public androidx.appsearch.app.SetBlobVisibilityRequest.Builder setNamespaceDisplayedBySystem(String, boolean);
+ }
+
+ public final class SetSchemaRequest {
+ method public java.util.Map<java.lang.String!,androidx.appsearch.app.Migrator!> getMigrators();
+ method public java.util.Map<java.lang.String!,androidx.appsearch.app.PackageIdentifier!> getPubliclyVisibleSchemas();
+ method public java.util.Map<java.lang.String!,java.util.Set<java.util.Set<java.lang.Integer!>!>!> getRequiredPermissionsForSchemaTypeVisibility();
+ method public java.util.Set<androidx.appsearch.app.AppSearchSchema!> getSchemas();
+ method public java.util.Set<java.lang.String!> getSchemasNotDisplayedBySystem();
+ method public java.util.Map<java.lang.String!,java.util.Set<androidx.appsearch.app.SchemaVisibilityConfig!>!> getSchemasVisibleToConfigs();
+ method public java.util.Map<java.lang.String!,java.util.Set<androidx.appsearch.app.PackageIdentifier!>!> getSchemasVisibleToPackages();
+ method @IntRange(from=1) public int getVersion();
+ method public boolean isForceOverride();
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public static final int READ_ASSISTANT_APP_SEARCH_DATA = 6; // 0x6
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public static final int READ_CALENDAR = 2; // 0x2
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public static final int READ_CONTACTS = 3; // 0x3
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public static final int READ_EXTERNAL_STORAGE = 4; // 0x4
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public static final int READ_HOME_APP_SEARCH_DATA = 5; // 0x5
+ field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public static final int READ_SMS = 1; // 0x1
+ }
+
+ public static final class SetSchemaRequest.Builder {
+ ctor public SetSchemaRequest.Builder();
+ ctor @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public SetSchemaRequest.Builder(androidx.appsearch.app.SetSchemaRequest);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG) public androidx.appsearch.app.SetSchemaRequest.Builder addDocumentClassVisibleToConfig(Class<? extends java.lang.Object!>, androidx.appsearch.app.SchemaVisibilityConfig) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SetSchemaRequest.Builder addDocumentClasses(Class<? extends java.lang.Object!>!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SetSchemaRequest.Builder addDocumentClasses(java.util.Collection<? extends java.lang.Class<? extends java.lang.Object!>!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public androidx.appsearch.app.SetSchemaRequest.Builder addRequiredPermissionsForDocumentClassVisibility(Class<? extends java.lang.Object!>, java.util.Set<java.lang.Integer!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public androidx.appsearch.app.SetSchemaRequest.Builder addRequiredPermissionsForSchemaTypeVisibility(String, java.util.Set<java.lang.Integer!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG) public androidx.appsearch.app.SetSchemaRequest.Builder addSchemaTypeVisibleToConfig(String, androidx.appsearch.app.SchemaVisibilityConfig);
+ method public androidx.appsearch.app.SetSchemaRequest.Builder addSchemas(androidx.appsearch.app.AppSearchSchema!...);
+ method public androidx.appsearch.app.SetSchemaRequest.Builder addSchemas(java.util.Collection<androidx.appsearch.app.AppSearchSchema!>);
+ method public androidx.appsearch.app.SetSchemaRequest build();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG) public androidx.appsearch.app.SetSchemaRequest.Builder clearDocumentClassVisibleToConfigs(Class<? extends java.lang.Object!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SetSchemaRequest.Builder clearMigrators();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public androidx.appsearch.app.SetSchemaRequest.Builder clearRequiredPermissionsForDocumentClassVisibility(Class<? extends java.lang.Object!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.ADD_PERMISSIONS_AND_GET_VISIBILITY) public androidx.appsearch.app.SetSchemaRequest.Builder clearRequiredPermissionsForSchemaTypeVisibility(String);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG) public androidx.appsearch.app.SetSchemaRequest.Builder clearSchemaTypeVisibleToConfigs(String);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SetSchemaRequest.Builder clearSchemas();
+ method public androidx.appsearch.app.SetSchemaRequest.Builder setDocumentClassDisplayedBySystem(Class<? extends java.lang.Object!>, boolean) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SetSchemaRequest.Builder setDocumentClassVisibilityForPackage(Class<? extends java.lang.Object!>, boolean, androidx.appsearch.app.PackageIdentifier) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SetSchemaRequest.Builder setForceOverride(boolean);
+ method public androidx.appsearch.app.SetSchemaRequest.Builder setMigrator(String, androidx.appsearch.app.Migrator);
+ method public androidx.appsearch.app.SetSchemaRequest.Builder setMigrators(java.util.Map<java.lang.String!,androidx.appsearch.app.Migrator!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SET_SCHEMA_REQUEST_SET_PUBLICLY_VISIBLE) public androidx.appsearch.app.SetSchemaRequest.Builder setPubliclyVisibleDocumentClass(Class<? extends java.lang.Object!>, androidx.appsearch.app.PackageIdentifier?) throws androidx.appsearch.exceptions.AppSearchException;
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SET_SCHEMA_REQUEST_SET_PUBLICLY_VISIBLE) public androidx.appsearch.app.SetSchemaRequest.Builder setPubliclyVisibleSchema(String, androidx.appsearch.app.PackageIdentifier?);
+ method public androidx.appsearch.app.SetSchemaRequest.Builder setSchemaTypeDisplayedBySystem(String, boolean);
+ method public androidx.appsearch.app.SetSchemaRequest.Builder setSchemaTypeVisibilityForPackage(String, boolean, androidx.appsearch.app.PackageIdentifier);
+ method public androidx.appsearch.app.SetSchemaRequest.Builder setVersion(@IntRange(from=1) int);
+ }
+
+ public final class SetSchemaResponse {
+ method public java.util.Set<java.lang.String!> getDeletedTypes();
+ method public java.util.Set<java.lang.String!> getIncompatibleTypes();
+ method public java.util.Set<java.lang.String!> getMigratedTypes();
+ method public java.util.List<androidx.appsearch.app.SetSchemaResponse.MigrationFailure!> getMigrationFailures();
+ }
+
+ public static final class SetSchemaResponse.Builder {
+ ctor public SetSchemaResponse.Builder();
+ method public androidx.appsearch.app.SetSchemaResponse.Builder addDeletedType(String);
+ method public androidx.appsearch.app.SetSchemaResponse.Builder addDeletedTypes(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.SetSchemaResponse.Builder addIncompatibleType(String);
+ method public androidx.appsearch.app.SetSchemaResponse.Builder addIncompatibleTypes(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.SetSchemaResponse.Builder addMigratedType(String);
+ method public androidx.appsearch.app.SetSchemaResponse.Builder addMigratedTypes(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.SetSchemaResponse.Builder addMigrationFailure(androidx.appsearch.app.SetSchemaResponse.MigrationFailure);
+ method public androidx.appsearch.app.SetSchemaResponse.Builder addMigrationFailures(java.util.Collection<androidx.appsearch.app.SetSchemaResponse.MigrationFailure!>);
+ method public androidx.appsearch.app.SetSchemaResponse build();
+ }
+
+ public static class SetSchemaResponse.MigrationFailure {
+ ctor public SetSchemaResponse.MigrationFailure(String, String, String, androidx.appsearch.app.AppSearchResult<? extends java.lang.Object!>);
+ method public androidx.appsearch.app.AppSearchResult<java.lang.Void!> getAppSearchResult();
+ method public String getDocumentId();
+ method public String getNamespace();
+ method public String getSchemaType();
+ }
+
+ public final class StorageInfo {
+ method public int getAliveDocumentsCount();
+ method public int getAliveNamespacesCount();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public int getBlobsCount();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public long getBlobsSizeBytes();
+ method public long getSizeBytes();
+ }
+
+ public static final class StorageInfo.Builder {
+ ctor public StorageInfo.Builder();
+ method public androidx.appsearch.app.StorageInfo build();
+ method public androidx.appsearch.app.StorageInfo.Builder setAliveDocumentsCount(int);
+ method public androidx.appsearch.app.StorageInfo.Builder setAliveNamespacesCount(int);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.StorageInfo.Builder setBlobsCount(int);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.StorageInfo.Builder setBlobsSizeBytes(long);
+ method public androidx.appsearch.app.StorageInfo.Builder setSizeBytes(long);
+ }
+
+ public interface StringSerializer<T> {
+ method public T? deserialize(String);
+ method public String serialize(T);
+ }
+
+}
+
+package androidx.appsearch.ast {
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public interface FunctionNode extends androidx.appsearch.ast.Node {
+ method public String getFunctionName();
+ field public static final String FUNCTION_NAME_GET_SEARCH_STRING_PARAMETER = "getSearchStringParameter";
+ field public static final String FUNCTION_NAME_HAS_PROPERTY = "hasProperty";
+ field public static final String FUNCTION_NAME_PROPERTY_DEFINED = "propertyDefined";
+ field public static final String FUNCTION_NAME_SEARCH = "search";
+ field public static final String FUNCTION_NAME_SEMANTIC_SEARCH = "semanticSearch";
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class NegationNode implements androidx.appsearch.ast.Node {
+ ctor public NegationNode(androidx.appsearch.ast.Node);
+ method public androidx.appsearch.ast.Node getChild();
+ method public void setChild(androidx.appsearch.ast.Node);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public interface Node {
+ method public default java.util.List<androidx.appsearch.ast.Node!> getChildren();
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class TextNode implements androidx.appsearch.ast.Node {
+ ctor public TextNode(androidx.appsearch.ast.TextNode);
+ ctor public TextNode(String);
+ method public String getValue();
+ method public boolean isPrefix();
+ method public boolean isVerbatim();
+ method public void setPrefix(boolean);
+ method public void setValue(String);
+ method public void setVerbatim(boolean);
+ }
+
+}
+
+package androidx.appsearch.ast.operators {
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class AndNode implements androidx.appsearch.ast.Node {
+ ctor public AndNode(androidx.appsearch.ast.Node, androidx.appsearch.ast.Node, androidx.appsearch.ast.Node!...);
+ ctor public AndNode(java.util.List<androidx.appsearch.ast.Node!>);
+ method public void addChild(androidx.appsearch.ast.Node);
+ method public int getIndexOfChild(androidx.appsearch.ast.Node);
+ method public boolean removeChild(androidx.appsearch.ast.Node);
+ method public void setChild(int, androidx.appsearch.ast.Node);
+ method public void setChildren(java.util.List<androidx.appsearch.ast.Node!>);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class ComparatorNode implements androidx.appsearch.ast.Node {
+ ctor public ComparatorNode(int, androidx.appsearch.app.PropertyPath, long);
+ method public int getComparator();
+ method public androidx.appsearch.app.PropertyPath getPropertyPath();
+ method public long getValue();
+ method public void setComparator(int);
+ method public void setPropertyPath(androidx.appsearch.app.PropertyPath);
+ method public void setValue(long);
+ field public static final int EQUALS = 0; // 0x0
+ field public static final int GREATER_EQUALS = 4; // 0x4
+ field public static final int GREATER_THAN = 3; // 0x3
+ field public static final int LESS_EQUALS = 2; // 0x2
+ field public static final int LESS_THAN = 1; // 0x1
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class OrNode implements androidx.appsearch.ast.Node {
+ ctor public OrNode(androidx.appsearch.ast.Node, androidx.appsearch.ast.Node, androidx.appsearch.ast.Node!...);
+ ctor public OrNode(java.util.List<androidx.appsearch.ast.Node!>);
+ method public void addChild(androidx.appsearch.ast.Node);
+ method public int getIndexOfChild(androidx.appsearch.ast.Node);
+ method public boolean removeChild(androidx.appsearch.ast.Node);
+ method public void setChild(int, androidx.appsearch.ast.Node);
+ method public void setChildren(java.util.List<androidx.appsearch.ast.Node!>);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class PropertyRestrictNode implements androidx.appsearch.ast.Node {
+ ctor public PropertyRestrictNode(androidx.appsearch.app.PropertyPath, androidx.appsearch.ast.Node);
+ method public androidx.appsearch.ast.Node getChild();
+ method public androidx.appsearch.app.PropertyPath getPropertyPath();
+ method public void setChild(androidx.appsearch.ast.Node);
+ method public void setPropertyPath(androidx.appsearch.app.PropertyPath);
+ }
+
+}
+
+package androidx.appsearch.ast.query {
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class GetSearchStringParameterNode implements androidx.appsearch.ast.FunctionNode {
+ ctor public GetSearchStringParameterNode(int);
+ method public String getFunctionName();
+ method public int getSearchStringIndex();
+ method public void setSearchStringIndex(int);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class HasPropertyNode implements androidx.appsearch.ast.FunctionNode {
+ ctor public HasPropertyNode(androidx.appsearch.app.PropertyPath);
+ method public String getFunctionName();
+ method public androidx.appsearch.app.PropertyPath getPropertyPath();
+ method public void setPropertyPath(androidx.appsearch.app.PropertyPath);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class PropertyDefinedNode implements androidx.appsearch.ast.FunctionNode {
+ ctor public PropertyDefinedNode(androidx.appsearch.app.PropertyPath);
+ method public String getFunctionName();
+ method public androidx.appsearch.app.PropertyPath getPropertyPath();
+ method public void setPropertyPath(androidx.appsearch.app.PropertyPath);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class SearchNode implements androidx.appsearch.ast.FunctionNode {
+ ctor public SearchNode(androidx.appsearch.ast.Node);
+ ctor public SearchNode(androidx.appsearch.ast.Node, java.util.List<androidx.appsearch.app.PropertyPath!>);
+ method public void addPropertyPath(androidx.appsearch.app.PropertyPath);
+ method public androidx.appsearch.ast.Node getChild();
+ method public String getFunctionName();
+ method public java.util.List<androidx.appsearch.app.PropertyPath!> getPropertyPaths();
+ method public void setChild(androidx.appsearch.ast.Node);
+ method public void setPropertyPaths(java.util.List<androidx.appsearch.app.PropertyPath!>);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class SemanticSearchNode implements androidx.appsearch.ast.FunctionNode {
+ ctor public SemanticSearchNode(int);
+ ctor public SemanticSearchNode(int, float);
+ ctor public SemanticSearchNode(int, float, float);
+ ctor public SemanticSearchNode(int, float, float, int);
+ method public int getDistanceMetric();
+ method public String getFunctionName();
+ method public float getLowerBound();
+ method public float getUpperBound();
+ method public int getVectorIndex();
+ method public void setBounds(float, float);
+ method public void setDistanceMetric(int);
+ method public void setVectorIndex(int);
+ }
+
+}
+
+package androidx.appsearch.exceptions {
+
+ public class AppSearchException extends java.lang.Exception {
+ ctor public AppSearchException(int);
+ ctor public AppSearchException(int, String?);
+ ctor public AppSearchException(int, String?, Throwable?);
+ method public int getResultCode();
+ method public <T> androidx.appsearch.app.AppSearchResult<T!> toAppSearchResult();
+ }
+
+}
+
+package androidx.appsearch.observer {
+
+ public final class DocumentChangeInfo {
+ ctor public DocumentChangeInfo(String, String, String, String, java.util.Set<java.lang.String!>);
+ method public java.util.Set<java.lang.String!> getChangedDocumentIds();
+ method public String getDatabaseName();
+ method public String getNamespace();
+ method public String getPackageName();
+ method public String getSchemaName();
+ }
+
+ public interface ObserverCallback {
+ method public void onDocumentChanged(androidx.appsearch.observer.DocumentChangeInfo);
+ method public void onSchemaChanged(androidx.appsearch.observer.SchemaChangeInfo);
+ }
+
+ public final class ObserverSpec {
+ method public java.util.Set<java.lang.String!> getFilterSchemas();
+ }
+
+ public static final class ObserverSpec.Builder {
+ ctor public ObserverSpec.Builder();
+ method public androidx.appsearch.observer.ObserverSpec.Builder addFilterDocumentClasses(Class<? extends java.lang.Object!>!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.observer.ObserverSpec.Builder addFilterDocumentClasses(java.util.Collection<? extends java.lang.Class<? extends java.lang.Object!>!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.observer.ObserverSpec.Builder addFilterSchemas(java.lang.String!...);
+ method public androidx.appsearch.observer.ObserverSpec.Builder addFilterSchemas(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.observer.ObserverSpec build();
+ }
+
+ public final class SchemaChangeInfo {
+ ctor public SchemaChangeInfo(String, String, java.util.Set<java.lang.String!>);
+ method public java.util.Set<java.lang.String!> getChangedSchemaNames();
+ method public String getDatabaseName();
+ method public String getPackageName();
+ }
+
+}
+
+package androidx.appsearch.usagereporting {
+
+ @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.JOIN_SPEC_AND_QUALIFIED_ID) @androidx.appsearch.annotation.Document(name="builtin:ClickAction") @androidx.appsearch.app.ExperimentalAppSearchApi public class ClickAction extends androidx.appsearch.usagereporting.TakenAction {
+ method public String? getQuery();
+ method public String? getReferencedQualifiedId();
+ method public int getResultRankGlobal();
+ method public int getResultRankInBlock();
+ method public long getTimeStayOnResultMillis();
+ }
+
+ @androidx.appsearch.annotation.Document.BuilderProducer public static final class ClickAction.Builder {
+ ctor public ClickAction.Builder(androidx.appsearch.usagereporting.ClickAction);
+ ctor public ClickAction.Builder(String, String, long);
+ method public androidx.appsearch.usagereporting.ClickAction build();
+ method public androidx.appsearch.usagereporting.ClickAction.Builder setDocumentTtlMillis(long);
+ method public androidx.appsearch.usagereporting.ClickAction.Builder setQuery(String?);
+ method public androidx.appsearch.usagereporting.ClickAction.Builder setReferencedQualifiedId(String?);
+ method public androidx.appsearch.usagereporting.ClickAction.Builder setResultRankGlobal(int);
+ method public androidx.appsearch.usagereporting.ClickAction.Builder setResultRankInBlock(int);
+ method public androidx.appsearch.usagereporting.ClickAction.Builder setTimeStayOnResultMillis(long);
+ }
+
+ @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.JOIN_SPEC_AND_QUALIFIED_ID) @androidx.appsearch.annotation.Document(name="builtin:DismissAction") @androidx.appsearch.app.ExperimentalAppSearchApi public class DismissAction extends androidx.appsearch.usagereporting.TakenAction {
+ method public String? getQuery();
+ method public String? getReferencedQualifiedId();
+ method public int getResultRankGlobal();
+ method public int getResultRankInBlock();
+ }
+
+ @androidx.appsearch.annotation.Document.BuilderProducer public static final class DismissAction.Builder {
+ ctor public DismissAction.Builder(androidx.appsearch.usagereporting.DismissAction);
+ ctor public DismissAction.Builder(String, String, long);
+ method public androidx.appsearch.usagereporting.DismissAction build();
+ method public androidx.appsearch.usagereporting.DismissAction.Builder setDocumentTtlMillis(long);
+ method public androidx.appsearch.usagereporting.DismissAction.Builder setQuery(String?);
+ method public androidx.appsearch.usagereporting.DismissAction.Builder setReferencedQualifiedId(String?);
+ method public androidx.appsearch.usagereporting.DismissAction.Builder setResultRankGlobal(int);
+ method public androidx.appsearch.usagereporting.DismissAction.Builder setResultRankInBlock(int);
+ }
+
+ @SuppressCompatibility @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.JOIN_SPEC_AND_QUALIFIED_ID) @androidx.appsearch.annotation.Document(name="builtin:ImpressionAction") @androidx.appsearch.app.ExperimentalAppSearchApi public class ImpressionAction extends androidx.appsearch.usagereporting.TakenAction {
+ method public String? getQuery();
+ method public String? getReferencedQualifiedId();
+ method public int getResultRankGlobal();
+ method public int getResultRankInBlock();
+ }
+
+ @androidx.appsearch.annotation.Document.BuilderProducer public static final class ImpressionAction.Builder {
+ ctor public ImpressionAction.Builder(androidx.appsearch.usagereporting.ImpressionAction);
+ ctor public ImpressionAction.Builder(String, String, long);
+ method public androidx.appsearch.usagereporting.ImpressionAction build();
+ method public androidx.appsearch.usagereporting.ImpressionAction.Builder setDocumentTtlMillis(long);
+ method public androidx.appsearch.usagereporting.ImpressionAction.Builder setQuery(String?);
+ method public androidx.appsearch.usagereporting.ImpressionAction.Builder setReferencedQualifiedId(String?);
+ method public androidx.appsearch.usagereporting.ImpressionAction.Builder setResultRankGlobal(int);
+ method public androidx.appsearch.usagereporting.ImpressionAction.Builder setResultRankInBlock(int);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.annotation.Document(name="builtin:SearchAction") @androidx.appsearch.app.ExperimentalAppSearchApi public class SearchAction extends androidx.appsearch.usagereporting.TakenAction {
+ method public int getFetchedResultCount();
+ method public String? getQuery();
+ }
+
+ @androidx.appsearch.annotation.Document.BuilderProducer public static final class SearchAction.Builder {
+ ctor public SearchAction.Builder(androidx.appsearch.usagereporting.SearchAction);
+ ctor public SearchAction.Builder(String, String, long);
+ method public androidx.appsearch.usagereporting.SearchAction build();
+ method public androidx.appsearch.usagereporting.SearchAction.Builder setDocumentTtlMillis(long);
+ method public androidx.appsearch.usagereporting.SearchAction.Builder setFetchedResultCount(int);
+ method public androidx.appsearch.usagereporting.SearchAction.Builder setQuery(String?);
+ }
+
+ @SuppressCompatibility @androidx.appsearch.annotation.Document(name="builtin:TakenAction") @androidx.appsearch.app.ExperimentalAppSearchApi public abstract class TakenAction {
+ method public long getActionTimestampMillis();
+ method public long getDocumentTtlMillis();
+ method public String getId();
+ method public String getNamespace();
+ }
+
+}
+
+package androidx.appsearch.util {
+
+ public class DocumentIdUtil {
+ method public static String createQualifiedId(String, String, androidx.appsearch.app.GenericDocument);
+ method public static String createQualifiedId(String, String, String, String);
+ }
+
+}
+
diff --git a/browser/browser/src/main/java/androidx/browser/auth/AuthTabIntent.java b/browser/browser/src/main/java/androidx/browser/auth/AuthTabIntent.java
index f1b964e..c2ee7bbb 100644
--- a/browser/browser/src/main/java/androidx/browser/auth/AuthTabIntent.java
+++ b/browser/browser/src/main/java/androidx/browser/auth/AuthTabIntent.java
@@ -264,6 +264,8 @@
*
* Guarantees that the {@link Intent} will be sent to the same component as the one the
* session is associated with.
+ *
+ * @param session The {@link AuthTabSession} to associate the intent with.
*/
public @NonNull Builder setSession(@NonNull AuthTabSession session) {
mSession = session;
@@ -275,6 +277,8 @@
/**
* Associates the {@link Intent} with the given {@link AuthTabSession.PendingSession}.
* Overrides the effect of {@link #setSession}.
+ *
+ * @param session The {@link AuthTabSession.PendingSession} to associate the intent with.
*/
@ExperimentalPendingSession
public @NonNull Builder setPendingSession(AuthTabSession.@NonNull PendingSession session) {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index aea21e9..7322275 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -698,9 +698,14 @@
project,
kotlinMultiplatformAndroidComponentsExtension
)
- // Propagate the compileSdk value into minCompileSdk.
- kotlinMultiplatformAndroidTarget.aarMetadata.minCompileSdk =
- kotlinMultiplatformAndroidTarget.compileSdk
+ kotlinMultiplatformAndroidComponentsExtension.apply {
+ finalizeDsl {
+ // Propagate the compileSdk value into minCompileSdk. Must be done after the DSL in
+ // build.gradle files (that sets compileSdk in the first place) is evaluated.
+ kotlinMultiplatformAndroidTarget.aarMetadata.minCompileSdk =
+ kotlinMultiplatformAndroidTarget.compileSdk
+ }
+ }
project.disableStrictVersionConstraints()
project.configureProjectForApiTasks(AndroidMultiplatformApiTaskConfig, androidXExtension)
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/kythe/KzipTasks.kt b/buildSrc/private/src/main/kotlin/androidx/build/kythe/KzipTasks.kt
index 56d327b..63f3431 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/kythe/KzipTasks.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/kythe/KzipTasks.kt
@@ -44,9 +44,6 @@
// KSP generated folders not visible to AGP variant api (b/380363756)
":privacysandbox:tools:integration-tests:testsdk",
":room:room-runtime",
- // Javac extractor throws an error: package exists in another module:
- // jdk.unsupported
- ":performance:performance-unsafe",
// Depends on the generated output of the proto project
// :wear:protolayout:protolayout-proto
// which we haven't captured for Java Kzip generation.
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
index 9a496a9..580c652 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
@@ -205,20 +205,21 @@
currentVersion: Version,
outputLocation: File
): List<String> {
- val versions = getVersionsForApiLevels(apiFiles) + currentVersion
+ val versions = getVersionsForApiLevels(apiFiles)
- val args =
- listOf(
- "--generate-api-version-history",
- outputLocation.absolutePath,
- "--api-version-names",
- versions.joinToString(" ")
- )
-
- return if (apiFiles.isEmpty()) {
- args
- } else {
- args + listOf("--api-version-signature-files", apiFiles.joinToString(":"))
+ return buildList {
+ add("--generate-api-version-history")
+ add(outputLocation.absolutePath)
+ if (versions.isNotEmpty()) {
+ add("--api-version-names")
+ add(versions.joinToString(" "))
+ }
+ add("--current-version")
+ add(currentVersion.toString())
+ if (apiFiles.isNotEmpty()) {
+ add("--api-version-signature-files")
+ add(apiFiles.joinToString(":"))
+ }
}
}
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
index 28281b1..dfeb5d2 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
@@ -102,6 +102,7 @@
cameraQuirks,
ZslControlNoOpImpl(),
NoOpTemplateParamsOverride,
+ cameraMetadata,
)
val cameraGraph = cameraPipe.create(cameraGraphConfig)
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt
index 3ac490c..026065e 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt
@@ -169,6 +169,10 @@
zslControl.addZslConfig(sessionConfigBuilder)
}
+ override fun clearZslConfig() {
+ zslControl.clearZslConfig()
+ }
+
override fun submitStillCaptureRequests(
captureConfigs: List<CaptureConfig>,
@ImageCapture.CaptureMode captureMode: Int,
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryProvider.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryProvider.kt
index 0c7fec1..2f84338 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryProvider.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryProvider.kt
@@ -96,6 +96,9 @@
Debug.traceStart { "Create CameraPipe" }
val timeSource = SystemTimeSource()
val start = Timestamps.now(timeSource)
+ // Enable pruning device manager when tested in the MH lab.
+ val usePruningDeviceManager =
+ android.util.Log.isLoggable(CAMERA_PIPE_MH_FLAG, android.util.Log.DEBUG)
val cameraPipe =
CameraPipe(
@@ -106,11 +109,17 @@
sharedInteropCallbacks.deviceStateCallback,
sharedInteropCallbacks.sessionStateCallback,
openRetryMaxTimeout
- )
+ ),
+ usePruningDeviceManager = usePruningDeviceManager
)
)
Log.debug { "Created CameraPipe in ${start.measureNow(timeSource).formatMs()}" }
Debug.traceStop()
return cameraPipe
}
+
+ private companion object {
+ // Flag set when being tested in the lab. Refer to CameraPipeConfigTestRule for more info.
+ const val CAMERA_PIPE_MH_FLAG = "CameraPipeMH"
+ }
}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/ZslControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/ZslControl.kt
index 5d46f97..40f257e 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/ZslControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/ZslControl.kt
@@ -52,6 +52,9 @@
*/
public fun addZslConfig(sessionConfigBuilder: SessionConfig.Builder)
+ /** This method cleans up all resources used for ZSL capture. */
+ public fun clearZslConfig()
+
/**
* Determines whether the provided [DeferrableSurface] belongs to ZSL.
*
@@ -222,6 +225,9 @@
}
override fun setZslDisabledByUserCaseConfig(disabled: Boolean) {
+ if (isZslDisabledByUseCaseConfig != disabled && disabled) {
+ clearRingBuffer()
+ }
isZslDisabledByUseCaseConfig = disabled
}
@@ -246,6 +252,10 @@
}
}
+ override fun clearZslConfig() {
+ reset()
+ }
+
private fun reset() {
val reprocImageDeferrableSurface = reprocessingImageDeferrableSurface
if (reprocImageDeferrableSurface != null) {
@@ -263,6 +273,10 @@
reprocessingImageDeferrableSurface = null
}
+ clearRingBuffer()
+ }
+
+ private fun clearRingBuffer() {
val ringBuffer = zslRingBuffer
while (!ringBuffer.isEmpty) {
ringBuffer.dequeue().close()
@@ -284,6 +298,8 @@
public class ZslControlNoOpImpl @Inject constructor() : ZslControl {
override fun addZslConfig(sessionConfigBuilder: SessionConfig.Builder) {}
+ override fun clearZslConfig() {}
+
override fun isZslSurface(surface: DeferrableSurface, sessionConfig: SessionConfig): Boolean =
false
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
index 0a33480..a999884 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.graphics.ImageFormat
+import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW
import android.hardware.camera2.CaptureRequest
import android.hardware.camera2.params.OutputConfiguration
@@ -32,6 +33,7 @@
import androidx.camera.camera2.pipe.CameraGraph.OperatingMode
import androidx.camera.camera2.pipe.CameraGraph.RepeatingRequestRequirementsBeforeCapture.CompletionBehavior.AT_LEAST
import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.CameraMetadata
import androidx.camera.camera2.pipe.CameraPipe
import androidx.camera.camera2.pipe.CameraStream
import androidx.camera.camera2.pipe.InputStream
@@ -125,6 +127,7 @@
constructor(
private val cameraPipe: CameraPipe,
private val cameraDevices: CameraDevices,
+ private val cameraMetadata: CameraMetadata?,
@GuardedBy("lock") private val cameraCoordinator: CameraCoordinator,
private val callbackMap: CameraCallbackMap,
private val requestListener: ComboRequestListener,
@@ -658,7 +661,7 @@
streamConfigMap: MutableMap<CameraStream.Config, DeferrableSurface>,
isExtensions: Boolean = false,
): CameraGraph.Config {
- return Companion.createCameraGraphConfig(
+ return createCameraGraphConfig(
sessionConfigAdapter,
streamConfigMap,
callbackMap,
@@ -667,6 +670,7 @@
cameraQuirks,
zslControl,
templateParamsOverride,
+ cameraMetadata,
isExtensions,
)
}
@@ -909,6 +913,7 @@
cameraQuirks: CameraQuirks,
zslControl: ZslControl,
templateParamsOverride: TemplateParamsOverride,
+ cameraMetadata: CameraMetadata?,
isExtensions: Boolean = false,
): CameraGraph.Config {
var containsVideo = false
@@ -964,7 +969,8 @@
streamUseCase =
getStreamUseCase(
deferrableSurface,
- sessionConfigAdapter.surfaceToStreamUseCaseMap
+ sessionConfigAdapter.surfaceToStreamUseCaseMap,
+ cameraMetadata,
),
streamUseHint =
getStreamUseHint(
@@ -1058,9 +1064,26 @@
private fun getStreamUseCase(
deferrableSurface: DeferrableSurface,
- mapping: Map<DeferrableSurface, Long>
+ mapping: Map<DeferrableSurface, Long>,
+ cameraMetadata: CameraMetadata?,
): OutputStream.StreamUseCase? {
- return mapping[deferrableSurface]?.let { OutputStream.StreamUseCase(it) }
+ val expectedStreamUseCase =
+ mapping[deferrableSurface]?.let { OutputStream.StreamUseCase(it) }
+ return if (
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
+ expectedStreamUseCase != null &&
+ cameraMetadata
+ ?.get(CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES)
+ ?.contains(expectedStreamUseCase.value) == true
+ ) {
+ expectedStreamUseCase
+ } else {
+ Log.warn {
+ "Expected stream use case for $deferrableSurface, " +
+ "$expectedStreamUseCase cannot be set!"
+ }
+ null
+ }
}
private fun getStreamUseHint(
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
index a9011ff..43ffaac 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
@@ -259,6 +259,10 @@
// Do nothing
}
+ override fun clearZslConfig() {
+ // Do nothing
+ }
+
override fun isZslSurface(
surface: DeferrableSurface,
sessionConfig: SessionConfig
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
index de2b4c5..743e748 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
@@ -689,6 +689,7 @@
emptySet(),
mapOf(fakeCameraBackend.id to listOf(fakeCameraMetadata))
),
+ cameraMetadata = fakeCameraMetadata,
cameraCoordinator = CameraCoordinatorAdapter(cameraPipe, cameraPipe.cameras()),
callbackMap = CameraCallbackMap(),
requestListener = ComboRequestListener(),
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt
index 5f432c88..65025d9 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt
@@ -91,6 +91,7 @@
val cameraBackendConfig: CameraBackendConfig = CameraBackendConfig(),
val cameraInteropConfig: CameraInteropConfig = CameraInteropConfig(),
val imageSources: ImageSources? = null,
+ val usePruningDeviceManager: Boolean = false,
)
/**
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Streams.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Streams.kt
index bb2cb3f..cf93534 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Streams.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Streams.kt
@@ -439,6 +439,7 @@
public val VIDEO_RECORD: StreamUseCase = StreamUseCase(3)
public val PREVIEW_VIDEO_STILL: StreamUseCase = StreamUseCase(4)
public val VIDEO_CALL: StreamUseCase = StreamUseCase(5)
+ public val CROPPED_RAW: StreamUseCase = StreamUseCase(6)
}
}
@@ -466,12 +467,12 @@
*/
public fun isValidForHighSpeedOperatingMode(): Boolean {
return this.streamUseCase == null ||
- this.streamUseCase == OutputStream.StreamUseCase.DEFAULT ||
- this.streamUseCase == OutputStream.StreamUseCase.PREVIEW ||
- this.streamUseCase == OutputStream.StreamUseCase.VIDEO_RECORD ||
+ this.streamUseCase == DEFAULT ||
+ this.streamUseCase == StreamUseCase.PREVIEW ||
+ this.streamUseCase == StreamUseCase.VIDEO_RECORD ||
this.streamUseHint == null ||
- this.streamUseHint == OutputStream.StreamUseHint.DEFAULT ||
- this.streamUseHint == OutputStream.StreamUseHint.VIDEO_RECORD
+ this.streamUseHint == StreamUseHint.DEFAULT ||
+ this.streamUseHint == StreamUseHint.VIDEO_RECORD
}
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Backend.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Backend.kt
index 39ed241..7d991cc 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Backend.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Backend.kt
@@ -31,7 +31,6 @@
import androidx.camera.camera2.pipe.graph.GraphListener
import androidx.camera.camera2.pipe.graph.StreamGraphImpl
import javax.inject.Inject
-import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
/** This is the default [CameraBackend] implementation for CameraPipe based on Camera2. */
@@ -64,28 +63,19 @@
}
override fun disconnectAsync(cameraId: CameraId): Deferred<Unit> {
- TODO(
- "b/324142928 - Add support in Camera2DeviceManager for closing a camera " +
- "with a deferred result."
- )
+ return camera2DeviceManager.close(cameraId)
}
override fun disconnectAll() {
- return camera2DeviceManager.closeAll()
+ camera2DeviceManager.closeAll()
}
override fun disconnectAllAsync(): Deferred<Unit> {
- TODO(
- "b/324142928 - Add support in Camera2DeviceManager for closing a camera " +
- "with a deferred result."
- )
+ return camera2DeviceManager.closeAll()
}
override fun shutdownAsync(): Deferred<Unit> {
- // TODO: Camera2DeviceManager needs to be extended to support a suspendable future that can
- // be used to wait until close has been called on all camera devices.
- camera2DeviceManager.closeAll()
- return CompletableDeferred(Unit)
+ return camera2DeviceManager.closeAll()
}
override fun createCameraController(
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt
index f1a515b..2d9e601 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt
@@ -397,14 +397,21 @@
"inputSurface is required to create instance of imageWriter."
}
val androidImageWriter =
- AndroidImageWriter.create(
- sessionInputSurface,
- inputStream.id,
- inputStream.maxImages,
- inputStream.format,
- threads.camera2Handler
- )
- Log.debug { "Created ImageWriter $androidImageWriter for session $session" }
+ try {
+ AndroidImageWriter.create(
+ sessionInputSurface,
+ inputStream.id,
+ inputStream.maxImages,
+ inputStream.format,
+ threads.camera2Handler
+ )
+ } catch (e: RuntimeException) {
+ Log.warn(e) { "Failed to create ImageWriter for session $session" }
+ null
+ }
+ if (androidImageWriter != null) {
+ Log.debug { "Created ImageWriter $androidImageWriter for session $session" }
+ }
androidImageWriter
} else {
null
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2DeviceManager.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2DeviceManager.kt
index f154261..0a28700 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2DeviceManager.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2DeviceManager.kt
@@ -30,8 +30,10 @@
import androidx.camera.camera2.pipe.graph.GraphRequestProcessor
import javax.inject.Inject
import javax.inject.Singleton
+import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.first
@@ -54,9 +56,17 @@
*/
internal data class RequestClose(val activeCamera: ActiveCamera) : CameraRequest()
-internal data class RequestCloseById(val activeCameraId: CameraId) : CameraRequest()
+internal class RequestCloseById(val activeCameraId: CameraId) : CameraRequest() {
+ val deferred: CompletableDeferred<Unit> = CompletableDeferred()
-internal object RequestCloseAll : CameraRequest()
+ override fun toString() = "RequestCloseById($activeCameraId)"
+}
+
+internal class RequestCloseAll() : CameraRequest() {
+ val deferred: CompletableDeferred<Unit> = CompletableDeferred()
+
+ override fun toString() = "RequestCloseAll"
+}
internal object NoOpGraphListener : GraphListener {
override fun onGraphStarted(requestProcessor: GraphRequestProcessor) {}
@@ -90,10 +100,10 @@
fun prewarm(cameraId: CameraId)
/** Submits a request to close the underlying camera. */
- fun close(cameraId: CameraId)
+ fun close(cameraId: CameraId): Deferred<Unit>
/** Instructs Camera2DeviceManager to close all cameras. */
- fun closeAll()
+ fun closeAll(): Deferred<Unit>
}
internal class ActiveCamera(
@@ -211,16 +221,22 @@
}
}
- override fun close(cameraId: CameraId) {
- if (!queue.tryEmit(RequestCloseById(cameraId))) {
+ override fun close(cameraId: CameraId): Deferred<Unit> {
+ val request = RequestCloseById(cameraId)
+ if (!queue.tryEmit(request)) {
Log.error { "Camera close by ID request failed for $cameraId!" }
+ request.deferred.complete(Unit)
}
+ return request.deferred
}
- override fun closeAll() {
- if (!queue.tryEmit(RequestCloseAll)) {
+ override fun closeAll(): Deferred<Unit> {
+ val request = RequestCloseAll()
+ if (!queue.tryEmit(request)) {
Log.error { "Camera close all request failed!" }
+ request.deferred.complete(Unit)
}
+ return request.deferred
}
@VisibleForTesting
@@ -234,9 +250,28 @@
}
// Step 2: Handle RequestCloseAll. The last one would nullify all preceding requests.
- val numRequestsBeforeCloseAll = requests.indexOfLast { it is RequestCloseAll }
- if (numRequestsBeforeCloseAll > 0) {
- repeat(numRequestsBeforeCloseAll) { requests.removeAt(0).onRemoved() }
+ val lastRequestCloseAllIdx = requests.indexOfLast { it is RequestCloseAll }
+ if (lastRequestCloseAllIdx > 0) {
+ val lastRequestCloseAll = requests[lastRequestCloseAllIdx] as RequestCloseAll
+ repeat(lastRequestCloseAllIdx) {
+ val request = requests.removeAt(0)
+
+ // When RequestCloseById or RequestCloseAll is removed, make sure to complete their
+ // deferred when the latter RequestCloseAll is completed.
+ val deferredToPropagate =
+ when (request) {
+ is RequestCloseById -> request.deferred
+ is RequestCloseAll -> request.deferred
+ else -> null
+ }
+ if (deferredToPropagate != null) {
+ lastRequestCloseAll.deferred.invokeOnCompletion {
+ deferredToPropagate.complete(Unit)
+ }
+ }
+
+ request.onRemoved()
+ }
}
// Step 3: Handle RequestOpen and RequestCloseById pruning.
@@ -277,8 +312,15 @@
else -> null
}
if (prunedByIdx != null) {
- Log.debug { "$request is pruned by ${requests[prunedByIdx]}" }
+ val prunedByRequest = requests[prunedByIdx]
+ Log.debug { "$request is pruned by $prunedByRequest" }
prunedIndices.add(idx)
+
+ // Make sure to complete the deferred of the pruned RequestCloseById when the latter
+ // RequestCloseById is completed.
+ if (request is RequestCloseById && prunedByRequest is RequestCloseById) {
+ prunedByRequest.deferred.invokeOnCompletion { request.deferred.complete(Unit) }
+ }
}
}
requests.removeIndices(prunedIndices).forEach { it.onRemoved() }
@@ -295,7 +337,7 @@
is RequestOpen -> processRequestOpen(request)
is RequestClose -> processRequestClose(request)
is RequestCloseById -> processRequestCloseById(request)
- is RequestCloseAll -> processRequestCloseAll()
+ is RequestCloseAll -> processRequestCloseAll(request)
}
}
@@ -413,13 +455,15 @@
}
}
val activeCamera = activeCameras.firstOrNull { it.cameraId == cameraId }
- if (activeCamera == null) return
- activeCameras.remove(activeCamera)
- activeCamera.close()
- activeCamera.awaitClosed()
+ if (activeCamera != null) {
+ activeCameras.remove(activeCamera)
+ activeCamera.close()
+ activeCamera.awaitClosed()
+ }
+ request.deferred.complete(Unit)
}
- private suspend fun processRequestCloseAll() {
+ private suspend fun processRequestCloseAll(requestCloseAll: RequestCloseAll) {
Log.info { "PruningCamera2DeviceManager#processRequestCloseAll()" }
pendingRequestOpens.clear()
@@ -431,6 +475,7 @@
activeCamera.awaitClosed()
}
activeCameras.clear()
+ requestCloseAll.deferred.complete(Unit)
}
private suspend fun openCameraWithRetry(
@@ -571,15 +616,20 @@
}
}
- override fun close(cameraId: CameraId) {
- offerChecked(RequestCloseById(cameraId))
+ override fun close(cameraId: CameraId): Deferred<Unit> {
+ val request = RequestCloseById(cameraId)
+ offerChecked(request)
+ request.deferred.complete(Unit)
+ return request.deferred
}
- override fun closeAll() {
- if (!offerChecked(RequestCloseAll)) {
+ override fun closeAll(): Deferred<Unit> {
+ val request = RequestCloseAll()
+ if (!offerChecked(request)) {
Log.warn { "Failed to close all cameras: Close request submission failed" }
- return
}
+ request.deferred.complete(Unit)
+ return request.deferred
}
private fun offerChecked(request: CameraRequest): Boolean {
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactory.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactory.kt
index 028a1eb..5618494 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactory.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactory.kt
@@ -22,7 +22,6 @@
import android.view.Surface
import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.CameraGraph
-import androidx.camera.camera2.pipe.CameraId
import androidx.camera.camera2.pipe.StreamId
import androidx.camera.camera2.pipe.compat.OutputConfigurationWrapper.Companion.SURFACE_GROUP_ID_NONE
import androidx.camera.camera2.pipe.config.Camera2ControllerScope
@@ -184,21 +183,13 @@
private val threads: Threads,
private val streamGraph: StreamGraphImpl,
private val graphConfig: CameraGraph.Config,
- private val camera2MetadataProvider: Camera2MetadataProvider
) : CaptureSessionFactory {
override fun create(
cameraDevice: CameraDeviceWrapper,
surfaces: Map<StreamId, Surface>,
captureSessionState: CaptureSessionState
): Map<StreamId, OutputConfigurationWrapper> {
- val outputs =
- buildOutputConfigurations(
- graphConfig,
- streamGraph,
- surfaces,
- camera2MetadataProvider,
- cameraDevice.cameraId
- )
+ val outputs = buildOutputConfigurations(graphConfig, streamGraph, surfaces)
if (outputs.all.isEmpty()) {
Log.warn { "Failed to create OutputConfigurations for $graphConfig" }
captureSessionState.onSessionFinalized()
@@ -240,7 +231,6 @@
private val threads: Threads,
private val graphConfig: CameraGraph.Config,
private val streamGraph: StreamGraphImpl,
- private val camera2MetadataProvider: Camera2MetadataProvider
) : CaptureSessionFactory {
override fun create(
cameraDevice: CameraDeviceWrapper,
@@ -259,14 +249,7 @@
else -> graphConfig.sessionMode.mode
}
- val outputs =
- buildOutputConfigurations(
- graphConfig,
- streamGraph,
- surfaces,
- camera2MetadataProvider,
- cameraDevice.cameraId
- )
+ val outputs = buildOutputConfigurations(graphConfig, streamGraph, surfaces)
if (outputs.all.isEmpty()) {
Log.warn { "Failed to create OutputConfigurations for $graphConfig" }
captureSessionState.onSessionFinalized()
@@ -315,8 +298,6 @@
graphConfig: CameraGraph.Config,
streamGraph: StreamGraphImpl,
surfaces: Map<StreamId, Surface>,
- camera2MetadataProvider: Camera2MetadataProvider,
- cameraId: CameraId
): OutputConfigurations {
val allOutputs = arrayListOf<OutputConfigurationWrapper>()
val deferredOutputs = mutableMapOf<StreamId, OutputConfigurationWrapper>()
@@ -362,8 +343,6 @@
} else {
null
},
- cameraId = cameraId,
- camera2MetadataProvider = camera2MetadataProvider
)
if (output == null) {
Log.warn { "Failed to create AndroidOutputConfiguration for $outputConfig" }
@@ -399,8 +378,6 @@
} else {
null
},
- cameraId = cameraId,
- camera2MetadataProvider = camera2MetadataProvider
)
if (output == null) {
Log.warn { "Failed to create AndroidOutputConfiguration for $outputConfig" }
@@ -476,14 +453,7 @@
}
}
- val outputs =
- buildOutputConfigurations(
- graphConfig,
- streamGraph,
- surfaces,
- camera2MetadataProvider,
- cameraDevice.cameraId
- )
+ val outputs = buildOutputConfigurations(graphConfig, streamGraph, surfaces)
if (outputs.all.isEmpty()) {
Log.warn { "Failed to create OutputConfigurations for $graphConfig" }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Configuration.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Configuration.kt
index 90d3918..c823908 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Configuration.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Configuration.kt
@@ -161,8 +161,6 @@
surfaceSharing: Boolean = false,
surfaceGroupId: Int = SURFACE_GROUP_ID_NONE,
physicalCameraId: CameraId? = null,
- cameraId: CameraId? = null,
- camera2MetadataProvider: Camera2MetadataProvider? = null,
): OutputConfigurationWrapper? {
check(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
@@ -261,14 +259,9 @@
}
}
- if (streamUseCase != null && cameraId != null && camera2MetadataProvider != null) {
+ if (streamUseCase != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- val cameraMetadata = camera2MetadataProvider.awaitCameraMetadata(cameraId)
- val availableStreamUseCases =
- Api33Compat.getAvailableStreamUseCases(cameraMetadata)
- if (availableStreamUseCases?.contains(streamUseCase.value) == true) {
- Api33Compat.setStreamUseCase(configuration, streamUseCase.value)
- }
+ Api33Compat.setStreamUseCase(configuration, streamUseCase.value)
}
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/Camera2Component.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/Camera2Component.kt
index 47af0bf..42de0fa 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/Camera2Component.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/Camera2Component.kt
@@ -17,11 +17,11 @@
package androidx.camera.camera2.pipe.config
import android.hardware.camera2.CameraManager
-import android.util.Log
import androidx.camera.camera2.pipe.CameraBackend
import androidx.camera.camera2.pipe.CameraController
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.CameraGraphId
+import androidx.camera.camera2.pipe.CameraPipe
import androidx.camera.camera2.pipe.StreamGraph
import androidx.camera.camera2.pipe.SurfaceTracker
import androidx.camera.camera2.pipe.compat.AudioRestrictionController
@@ -99,16 +99,15 @@
): AudioRestrictionController
companion object {
- // TODO: b/369684573 - Enable PruningCamera2DeviceManager for all users.
- const val CAMERA_PIPE_MH_FLAG = "CameraPipeMH"
- val ENABLE_PRUNING_DEVICE_MANAGER = Log.isLoggable(CAMERA_PIPE_MH_FLAG, Log.DEBUG)
@Provides
fun provideCamera2DeviceManager(
camera2DeviceManager: Provider<Camera2DeviceManagerImpl>,
pruningCamera2DeviceManager: Provider<PruningCamera2DeviceManager>,
+ cameraPipeConfig: CameraPipe.Config,
): Camera2DeviceManager {
- return if (ENABLE_PRUNING_DEVICE_MANAGER) {
+ // TODO: b/369684573 - Enable PruningCamera2DeviceManager for all users.
+ return if (cameraPipeConfig.usePruningDeviceManager) {
pruningCamera2DeviceManager.get()
} else {
camera2DeviceManager.get()
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/Camera2DeviceManagerTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/Camera2DeviceManagerTest.kt
index 2ccb5c5..cb7ac0b 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/Camera2DeviceManagerTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/Camera2DeviceManagerTest.kt
@@ -32,9 +32,11 @@
import androidx.camera.camera2.pipe.testing.RobolectricCameraPipeTestRunner
import kotlin.test.Test
import kotlin.test.assertEquals
+import kotlin.test.assertFalse
import kotlin.test.assertIs
import kotlin.test.assertIsNot
import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -464,8 +466,8 @@
RequestCloseById(cameraId0),
createFakeRequestOpen(cameraId1, emptyList(), fakeGraphListener2),
createFakeRequestClose(cameraId1),
- RequestCloseAll,
- RequestCloseAll,
+ RequestCloseAll(),
+ RequestCloseAll(),
createFakeRequestOpen(cameraId0, emptyList(), fakeGraphListener1),
)
deviceManager.prune(requestList)
@@ -547,6 +549,56 @@
assertEquals((requestList[1] as RequestCloseById).activeCameraId, cameraId1)
}
+ @Test
+ fun deferredStillCompletesForPrunedRequests() =
+ testScope.runTest {
+ val requestCloseById1 = RequestCloseById(cameraId0)
+ val requestCloseById2 = RequestCloseById(cameraId1)
+ val requestCloseById3 = RequestCloseById(cameraId1)
+ val requestCloseById4 = RequestCloseById(cameraId1)
+ val requestCloseAll1 = RequestCloseAll()
+ val requestCloseAll2 = RequestCloseAll()
+ val requestCloseAll3 = RequestCloseAll()
+ val requestList =
+ mutableListOf<CameraRequest>(
+ createFakeRequestOpen(cameraId0, emptyList(), fakeGraphListener1),
+ requestCloseById1,
+ requestCloseAll1,
+ requestCloseAll2,
+ requestCloseAll3,
+ createFakeRequestOpen(cameraId1, emptyList(), fakeGraphListener1),
+ requestCloseById2,
+ requestCloseById3,
+ requestCloseById4,
+ )
+ deviceManager.prune(requestList)
+ assertEquals(requestList, listOf(requestCloseAll3, requestCloseById4))
+
+ advanceUntilIdle()
+ assertFalse(requestCloseById1.deferred.isCompleted)
+ assertFalse(requestCloseById2.deferred.isCompleted)
+ assertFalse(requestCloseById3.deferred.isCompleted)
+ assertFalse(requestCloseById4.deferred.isCompleted)
+ assertFalse(requestCloseAll1.deferred.isCompleted)
+ assertFalse(requestCloseAll2.deferred.isCompleted)
+ assertFalse(requestCloseAll3.deferred.isCompleted)
+
+ // Simulate completion of requestCloseAll3, which should also complete all requests it
+ // has pruned previously.
+ requestCloseAll3.deferred.complete(Unit)
+ advanceUntilIdle()
+ assertTrue(requestCloseById1.deferred.isCompleted)
+ assertTrue(requestCloseAll1.deferred.isCompleted)
+ assertTrue(requestCloseAll2.deferred.isCompleted)
+
+ // Simulate completion of requestCloseById4, which should also complete all requests it
+ // has pruned previously.
+ requestCloseById4.deferred.complete(Unit)
+ advanceUntilIdle()
+ assertTrue(requestCloseById2.deferred.isCompleted)
+ assertTrue(requestCloseById3.deferred.isCompleted)
+ }
+
private fun createFakeRequestOpen(
cameraId: CameraId,
sharedCameraIds: List<CameraId>,
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCamera2DeviceManager.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCamera2DeviceManager.kt
index 25af751..504b5e0 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCamera2DeviceManager.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCamera2DeviceManager.kt
@@ -21,6 +21,8 @@
import androidx.camera.camera2.pipe.compat.Camera2DeviceManager
import androidx.camera.camera2.pipe.compat.VirtualCamera
import androidx.camera.camera2.pipe.graph.GraphListener
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Deferred
internal class FakeCamera2DeviceManager : Camera2DeviceManager {
@@ -40,12 +42,14 @@
// No-op.
}
- override fun close(cameraId: CameraId) {
+ override fun close(cameraId: CameraId): Deferred<Unit> {
fakeVirtualCameraMap.remove(cameraId)
+ return CompletableDeferred(Unit)
}
- override fun closeAll() {
+ override fun closeAll(): Deferred<Unit> {
fakeVirtualCameraMap.clear()
+ return CompletableDeferred(Unit)
}
suspend fun simulateCameraOpen(cameraId: CameraId) {
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraControlImplDeviceTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraControlImplDeviceTest.java
index 96d36ea..ab6cf33 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraControlImplDeviceTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraControlImplDeviceTest.java
@@ -50,6 +50,7 @@
import android.app.Instrumentation;
import android.content.Context;
import android.graphics.Rect;
+import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
@@ -64,12 +65,14 @@
import androidx.camera.camera2.internal.compat.quirk.CameraQuirks;
import androidx.camera.camera2.internal.compat.workaround.AutoFlashAEModeDisabler;
import androidx.camera.camera2.internal.util.TestUtil;
+import androidx.camera.camera2.interop.Camera2Interop;
import androidx.camera.core.CameraControl;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.CameraXConfig;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageCapture;
+import androidx.camera.core.ImageProxy;
import androidx.camera.core.SurfaceOrientedMeteringPointFactory;
import androidx.camera.core.impl.CameraCaptureCallback;
import androidx.camera.core.impl.CameraCaptureResult;
@@ -431,6 +434,92 @@
}
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void setTorchStrengthLevel_valueUpdated()
+ throws ExecutionException, InterruptedException {
+ assumeTrue(mHasFlashUnit);
+
+ // Arrange
+ ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().build();
+ imageAnalysis.setAnalyzer(CameraXExecutors.mainThreadExecutor(), ImageProxy::close);
+ mCamera = CameraUtil.createCameraAndAttachUseCase(
+ ApplicationProvider.getApplicationContext(), CameraSelector.DEFAULT_BACK_CAMERA,
+ imageAnalysis);
+ Camera2CameraControlImpl camera2CameraControlImpl =
+ TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl());
+ camera2CameraControlImpl.enableTorch(true).get();
+
+ // Act
+ int maxStrength = mCamera.getCameraInfo().getMaxTorchStrengthLevel();
+ int defaultStrength = mCamera.getCameraInfo().getTorchStrengthLevel().getValue();
+ // If the default strength is the max, set the strength to 1, otherwise, set to max.
+ int customizedStrength = defaultStrength == maxStrength ? 1 : maxStrength;
+ camera2CameraControlImpl.setTorchStrengthLevelAsync(customizedStrength).get();
+
+ // Assert: the customized strength is applied
+ Camera2ImplConfig camera2Config = new Camera2ImplConfig(
+ camera2CameraControlImpl.getSessionConfig().getImplementationOptions());
+ assertThat(camera2Config.getCaptureRequestOption(
+ CaptureRequest.FLASH_STRENGTH_LEVEL, -1))
+ .isEqualTo(customizedStrength);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void setTorchStrengthLevel_throwExceptionIfLessThanOne()
+ throws ExecutionException, InterruptedException {
+ assumeTrue(mHasFlashUnit);
+
+ // Arrange
+ ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().build();
+ imageAnalysis.setAnalyzer(CameraXExecutors.mainThreadExecutor(), ImageProxy::close);
+ mCamera = CameraUtil.createCameraAndAttachUseCase(
+ ApplicationProvider.getApplicationContext(), CameraSelector.DEFAULT_BACK_CAMERA,
+ imageAnalysis);
+ Camera2CameraControlImpl camera2CameraControlImpl =
+ TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl());
+ camera2CameraControlImpl.enableTorch(true).get();
+
+ // Act & Assert
+ try {
+ camera2CameraControlImpl.setTorchStrengthLevelAsync(0).get();
+ } catch (ExecutionException e) {
+ assertThat(e.getCause()).isInstanceOf(IllegalArgumentException.class);
+ return;
+ }
+
+ fail("setTorchStrength didn't fail with an IllegalArgumentException.");
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void setTorchStrengthLevel_throwExceptionIfLargerThanMax()
+ throws ExecutionException, InterruptedException {
+ assumeTrue(mHasFlashUnit);
+
+ // Arrange
+ ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().build();
+ imageAnalysis.setAnalyzer(CameraXExecutors.mainThreadExecutor(), ImageProxy::close);
+ mCamera = CameraUtil.createCameraAndAttachUseCase(
+ ApplicationProvider.getApplicationContext(), CameraSelector.DEFAULT_BACK_CAMERA,
+ imageAnalysis);
+ Camera2CameraControlImpl camera2CameraControlImpl =
+ TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl());
+ camera2CameraControlImpl.enableTorch(true).get();
+
+ // Act & Assert
+ try {
+ camera2CameraControlImpl.setTorchStrengthLevelAsync(
+ mCamera.getCameraInfo().getMaxTorchStrengthLevel() + 1).get();
+ } catch (ExecutionException e) {
+ assertThat(e.getCause()).isInstanceOf(IllegalArgumentException.class);
+ return;
+ }
+
+ fail("setTorchStrength didn't fail with an IllegalArgumentException.");
+ }
+
@SdkSuppress(minSdkVersion = 35)
@Test
public void enableLowLightBoost_aeModeSetAndRequestUpdated() throws InterruptedException {
@@ -516,6 +605,57 @@
ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH);
}
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void capture_torchAsFlash_shouldUseDefaultTorchStrength()
+ throws ExecutionException, InterruptedException, TimeoutException {
+ assumeTrue(mHasFlashUnit);
+
+ // Arrange: explicitly set flash type to use torch as flash
+ ImageCapture.Builder imageCaptureBuilder = new ImageCapture.Builder().setFlashType(
+ ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH).setFlashMode(
+ ImageCapture.FLASH_MODE_ON);
+ CameraCaptureSession.CaptureCallback captureCallback = mock(
+ CameraCaptureSession.CaptureCallback.class);
+ new Camera2Interop.Extender<>(imageCaptureBuilder).setSessionCaptureCallback(
+ captureCallback);
+ ImageCapture imageCapture = imageCaptureBuilder.build();
+ mCamera = CameraUtil.createCameraAndAttachUseCase(
+ ApplicationProvider.getApplicationContext(), CameraSelector.DEFAULT_BACK_CAMERA,
+ imageCapture);
+ Camera2CameraControlImpl camera2CameraControlImpl =
+ TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl());
+
+ // Act
+ int maxStrength = mCamera.getCameraInfo().getMaxTorchStrengthLevel();
+ int defaultStrength = mCamera.getCameraInfo().getTorchStrengthLevel().getValue();
+ // If the default strength is the max, set the strength to 1, otherwise, set to max.
+ int customizedStrength = defaultStrength == maxStrength ? 1 : maxStrength;
+ camera2CameraControlImpl.setTorchStrengthLevelAsync(customizedStrength).get();
+
+ // Assert: the capture uses default torch strength
+ CaptureConfig.Builder captureConfigBuilder = new CaptureConfig.Builder();
+ captureConfigBuilder.setTemplateType(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ captureConfigBuilder.addSurface(imageCapture.getSessionConfig().getSurfaces().get(0));
+
+ camera2CameraControlImpl.setFlashMode(ImageCapture.FLASH_MODE_ON);
+ camera2CameraControlImpl.submitStillCaptureRequests(
+ Arrays.asList(captureConfigBuilder.build()),
+ ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY,
+ ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH)
+ .get(5, TimeUnit.SECONDS);
+ ArgumentCaptor<CaptureRequest> captureRequestCaptor =
+ ArgumentCaptor.forClass(CaptureRequest.class);
+ verify(captureCallback, timeout(5000).atLeastOnce())
+ .onCaptureCompleted(any(), captureRequestCaptor.capture(), any());
+ List<CaptureRequest> results = captureRequestCaptor.getAllValues();
+ for (CaptureRequest result : results) {
+ // None of the capture capture should be sent with the customized strength.
+ assertThat(result.get(CaptureRequest.FLASH_STRENGTH_LEVEL)).isNotEqualTo(
+ customizedStrength);
+ }
+ }
+
private void captureTest(int captureMode, int flashType)
throws ExecutionException, InterruptedException, TimeoutException {
ImageCapture imageCapture = new ImageCapture.Builder().build();
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/TorchControlDeviceTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/TorchControlDeviceTest.java
index a1b57bc..de994f6 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/TorchControlDeviceTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/TorchControlDeviceTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assume.assumeTrue;
import android.content.Context;
+import android.os.Build;
import androidx.camera.camera2.Camera2Config;
import androidx.camera.camera2.internal.util.TestUtil;
@@ -63,6 +64,8 @@
private TorchControl mTorchControl;
private CameraUseCaseAdapter mCamera;
+ private Camera2CameraControlImpl mCameraControl;
+ private boolean mIsTorchStrengthSupported;
@Before
public void setUp() {
@@ -83,10 +86,9 @@
// Make ImageAnalysis active.
imageAnalysis.setAnalyzer(CameraXExecutors.mainThreadExecutor(), ImageProxy::close);
mCamera = CameraUtil.createCameraAndAttachUseCase(context, cameraSelector, imageAnalysis);
- Camera2CameraControlImpl cameraControl =
- TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl());
-
- mTorchControl = cameraControl.getTorchControl();
+ mCameraControl = TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl());
+ mTorchControl = mCameraControl.getTorchControl();
+ mIsTorchStrengthSupported = mCamera.getCameraInfo().getMaxTorchStrengthLevel() > 1;
}
@After
@@ -113,4 +115,30 @@
// Future should return with no exception
future.get();
}
+
+ @Test(timeout = 5000L)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void setTorchStrengthLevel_futureCompleteWhenTorchIsOnLevel()
+ throws ExecutionException, InterruptedException {
+ assumeTrue(mIsTorchStrengthSupported);
+
+ // Arrange: turn on the torch
+ mTorchControl.enableTorch(true).get();
+
+ // Act & Assert: the future completes
+ mTorchControl.setTorchStrengthLevel(
+ mCamera.getCameraInfo().getMaxTorchStrengthLevel()).get();
+ }
+
+ @Test(timeout = 5000L)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void setTorchStrengthLevel_futureCompleteWhenTorchIsOffLevel()
+ throws ExecutionException, InterruptedException {
+ assumeTrue(mIsTorchStrengthSupported);
+
+ // Arrange: the torch is default off
+ // Act & Assert: the future completes
+ mTorchControl.setTorchStrengthLevel(
+ mCamera.getCameraInfo().getMaxTorchStrengthLevel()).get();
+ }
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraControlImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraControlImpl.java
index f6db921..8a2a439 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraControlImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraControlImpl.java
@@ -34,6 +34,7 @@
import android.util.Rational;
import androidx.annotation.GuardedBy;
+import androidx.annotation.IntRange;
import androidx.annotation.OptIn;
import androidx.annotation.VisibleForTesting;
import androidx.camera.camera2.impl.Camera2ImplConfig;
@@ -139,7 +140,10 @@
private ImageCapture.ScreenFlash mScreenFlash;
// use volatile modifier to make these variables in sync in all threads.
- private volatile boolean mIsTorchOn = false;
+ @TorchControl.TorchStateInternal
+ private volatile int mTorchState = TorchControl.OFF;
+ @IntRange(from = 1)
+ private volatile int mTorchStrength;
private volatile boolean mIsLowLightBoostOn = false;
@ImageCapture.FlashMode
private volatile int mFlashMode = FLASH_MODE_OFF;
@@ -205,6 +209,7 @@
this, scheduler, mExecutor, cameraQuirks);
mZoomControl = new ZoomControl(this, mCameraCharacteristics, mExecutor);
mTorchControl = new TorchControl(this, mCameraCharacteristics, mExecutor);
+ mTorchStrength = mCameraCharacteristics.getDefaultTorchStrengthLevel();
mLowLightBoostControl = new LowLightBoostControl(this, mCameraCharacteristics, mExecutor);
if (Build.VERSION.SDK_INT >= 23) {
mZslControl = new ZslControlImpl(mCameraCharacteristics, mExecutor);
@@ -415,6 +420,11 @@
}
@Override
+ public void clearZslConfig() {
+ mZslControl.clearZslConfig();
+ }
+
+ @Override
public void setZslDisabledByUserCaseConfig(boolean disabled) {
mZslControl.setZslDisabledByUserCaseConfig(disabled);
}
@@ -499,6 +509,30 @@
return mExposureControl.setExposureCompensationIndex(exposure);
}
+ @Override
+ public @NonNull ListenableFuture<Void> setTorchStrengthLevelAsync(
+ @IntRange(from = 1) int torchStrengthLevel) {
+ if (!isControlInUse()) {
+ return Futures.immediateFailedFuture(
+ new OperationCanceledException("Camera is not active."));
+ }
+ if (torchStrengthLevel < 1
+ || torchStrengthLevel > mCameraCharacteristics.getMaxTorchStrengthLevel()) {
+ return Futures.immediateFailedFuture(new IllegalArgumentException(
+ "The specified torch strength is not within the valid range."));
+ }
+ return Futures.nonCancellationPropagating(mTorchControl.setTorchStrengthLevel(
+ Math.min(torchStrengthLevel, mCameraCharacteristics.getMaxTorchStrengthLevel())));
+ }
+
+ @ExecutedBy("mExecutor")
+ void setTorchStrengthLevelInternal(@IntRange(from = 1) int torchStrengthLevel) {
+ mTorchStrength = torchStrengthLevel;
+ if (isTorchOn()) {
+ updateSessionConfigSynchronous();
+ }
+ }
+
/** {@inheritDoc} */
@Override
public @NonNull ListenableFuture<List<Void>> submitStillCaptureRequests(
@@ -649,14 +683,14 @@
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
@ExecutedBy("mExecutor")
- void enableTorchInternal(boolean torch) {
+ void enableTorchInternal(@TorchControl.TorchStateInternal int torchState) {
// When low-light boost is on, any torch related operations will be ignored.
if (mIsLowLightBoostOn) {
return;
}
- mIsTorchOn = torch;
- if (!torch) {
+ mTorchState = torchState;
+ if (torchState == TorchControl.OFF) {
// On some devices, needs to reset the AE/flash state to ensure that the torch can be
// turned off.
resetAeFlashState();
@@ -672,11 +706,11 @@
}
// Forces turn off torch before enabling low-light boost.
- if (lowLightBoost && mIsTorchOn) {
+ if (lowLightBoost && isTorchOn()) {
// On some devices, needs to reset the AE/flash state to ensure that the torch can be
// turned off.
resetAeFlashState();
- mIsTorchOn = false;
+ mTorchState = TorchControl.OFF;
mTorchControl.forceUpdateTorchStateToOff();
}
@@ -701,7 +735,7 @@
@ExecutedBy("mExecutor")
boolean isTorchOn() {
- return mIsTorchOn;
+ return mTorchState != TorchControl.OFF;
}
@ExecutedBy("mExecutor")
@@ -741,9 +775,20 @@
if (mIsLowLightBoostOn) {
aeMode = CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY;
- } else if (mIsTorchOn) {
+ } else if (isTorchOn()) {
builder.setCaptureRequestOptionWithPriority(CaptureRequest.FLASH_MODE,
CaptureRequest.FLASH_MODE_TORCH, Config.OptionPriority.REQUIRED);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ if (mTorchState == TorchControl.ON) {
+ builder.setCaptureRequestOptionWithPriority(CaptureRequest.FLASH_STRENGTH_LEVEL,
+ mTorchStrength, Config.OptionPriority.REQUIRED);
+ } else if (mTorchState == TorchControl.USED_AS_FLASH) {
+ // If torch is used as flash, use the default torch strength instead.
+ builder.setCaptureRequestOptionWithPriority(CaptureRequest.FLASH_STRENGTH_LEVEL,
+ mCameraCharacteristics.getDefaultTorchStrengthLevel(),
+ Config.OptionPriority.REQUIRED);
+ }
+ }
} else {
switch (mFlashMode) {
case FLASH_MODE_OFF:
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
index 81a5d90..aeb7058 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
@@ -38,6 +38,7 @@
import androidx.annotation.FloatRange;
import androidx.annotation.GuardedBy;
+import androidx.annotation.IntRange;
import androidx.annotation.OptIn;
import androidx.camera.camera2.internal.compat.CameraAccessExceptionCompat;
import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
@@ -110,6 +111,8 @@
@GuardedBy("mLock")
private @Nullable RedirectableLiveData<Integer> mRedirectTorchStateLiveData = null;
@GuardedBy("mLock")
+ private @Nullable RedirectableLiveData<Integer> mRedirectTorchStrengthLiveData = null;
+ @GuardedBy("mLock")
private @Nullable RedirectableLiveData<Integer> mRedirectLowLightBoostStateLiveData = null;
@GuardedBy("mLock")
private @Nullable RedirectableLiveData<ZoomState> mRedirectZoomStateLiveData = null;
@@ -161,6 +164,11 @@
mCamera2CameraControlImpl.getTorchControl().getTorchState());
}
+ if (mRedirectTorchStrengthLiveData != null) {
+ mRedirectTorchStrengthLiveData.redirectTo(
+ mCamera2CameraControlImpl.getTorchControl().getTorchStrengthLevel());
+ }
+
if (mRedirectLowLightBoostStateLiveData != null) {
mRedirectLowLightBoostStateLiveData.redirectTo(mCamera2CameraControlImpl
.getLowLightBoostControl().getLowLightBoostState());
@@ -709,4 +717,29 @@
return mPhysicalCameraInfos;
}
+
+ @Override
+ @IntRange(from = 1)
+ public int getMaxTorchStrengthLevel() {
+ return mCameraCharacteristicsCompat.getMaxTorchStrengthLevel();
+ }
+
+ @Override
+ public @NonNull LiveData<Integer> getTorchStrengthLevel() {
+ synchronized (mLock) {
+ if (mCamera2CameraControlImpl == null) {
+ if (mRedirectTorchStrengthLiveData == null) {
+ mRedirectTorchStrengthLiveData = new RedirectableLiveData<>(
+ mCameraCharacteristicsCompat.getDefaultTorchStrengthLevel());
+ }
+ return mRedirectTorchStrengthLiveData;
+ }
+
+ if (mRedirectTorchStrengthLiveData != null) {
+ return mRedirectTorchStrengthLiveData;
+ }
+
+ return mCamera2CameraControlImpl.getTorchControl().getTorchStrengthLevel();
+ }
+ }
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CapturePipeline.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CapturePipeline.java
index 42a38eb..c05cdc7 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CapturePipeline.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CapturePipeline.java
@@ -635,7 +635,8 @@
mIsExecuted = true;
ListenableFuture<Void> future = CallbackToFutureAdapter.getFuture(completer -> {
- mCameraControl.getTorchControl().enableTorchInternal(completer, true);
+ mCameraControl.getTorchControl().enableTorchInternal(completer,
+ TorchControl.USED_AS_FLASH);
return "TorchOn";
});
return FutureChain.from(future).transformAsync(
@@ -667,7 +668,7 @@
@Override
public void postCapture() {
if (mIsExecuted) {
- mCameraControl.getTorchControl().enableTorchInternal(null, false);
+ mCameraControl.getTorchControl().enableTorchInternal(null, TorchControl.OFF);
Logger.d(TAG, "Turning off torch");
if (mTriggerAePrecapture) {
mCameraControl.getFocusMeteringControl().cancelAfAeTrigger(false, true);
@@ -795,7 +796,7 @@
return "EnableTorchInternal";
}
Logger.d(TAG, "ScreenFlashTask#preCapture: enable torch");
- mCameraControl.enableTorchInternal(true);
+ mCameraControl.enableTorchInternal(TorchControl.USED_AS_FLASH);
completer.set(null);
return "EnableTorchInternal";
}),
@@ -828,7 +829,7 @@
public void postCapture() {
Logger.d(TAG, "ScreenFlashTask#postCapture");
if (mUseFlashModeTorchFor3aUpdate.shouldUseFlashModeTorch()) {
- mCameraControl.enableTorchInternal(false);
+ mCameraControl.enableTorchInternal(TorchControl.OFF);
}
mCameraControl.getFocusMeteringControl().enableExternalFlashAeMode(false).addListener(
() -> Log.d(TAG, "enableExternalFlashAeMode disabled"), mExecutor
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/TorchControl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/TorchControl.java
index d4c634e..7ea9db9 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/TorchControl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/TorchControl.java
@@ -18,7 +18,11 @@
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.os.Build;
+import androidx.annotation.IntDef;
+import androidx.annotation.IntRange;
import androidx.camera.camera2.internal.annotation.CameraExecutor;
import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
import androidx.camera.camera2.internal.compat.workaround.FlashAvailabilityChecker;
@@ -38,6 +42,8 @@
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.Executor;
/**
@@ -52,14 +58,32 @@
private static final String TAG = "TorchControl";
static final int DEFAULT_TORCH_STATE = TorchState.OFF;
+ /** Torch is off. */
+ static final int OFF = 0;
+ /** Torch is turned on explicitly by {@link #enableTorch(boolean)}. */
+ static final int ON = 1;
+ /** Torch is turned on as flash by the capture pipeline. */
+ static final int USED_AS_FLASH = 2;
+
+ /** The internal torch state. */
+ @IntDef({OFF, ON, USED_AS_FLASH})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface TorchStateInternal {
+ }
+
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
private final Camera2CameraControlImpl mCamera2CameraControlImpl;
private final MutableLiveData<Integer> mTorchState;
+ private final MutableLiveData<Integer> mTorchStrength;
private final boolean mHasFlashUnit;
@CameraExecutor
private final Executor mExecutor;
private boolean mIsActive;
+ private boolean mIsTorchStrengthSupported;
+ private int mDefaultTorchStrength;
+ private int mTargetTorchStrength;
+ private CallbackToFutureAdapter.Completer<Void> mTorchStrengthCompleter;
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
CallbackToFutureAdapter.Completer<Void> mEnableTorchCompleter;
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
@@ -79,7 +103,13 @@
mExecutor = executor;
mHasFlashUnit = FlashAvailabilityChecker.isFlashAvailable(cameraCharacteristics::get);
+ mIsTorchStrengthSupported =
+ mHasFlashUnit && Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM
+ && cameraCharacteristics.getMaxTorchStrengthLevel() > 1;
+ mDefaultTorchStrength = cameraCharacteristics.getDefaultTorchStrengthLevel();
+ mTargetTorchStrength = mDefaultTorchStrength;
mTorchState = new MutableLiveData<>(DEFAULT_TORCH_STATE);
+ mTorchStrength = new MutableLiveData<>(mDefaultTorchStrength);
Camera2CameraControlImpl.CaptureResultListener captureResultListener = captureResult -> {
if (mEnableTorchCompleter != null) {
CaptureRequest captureRequest = captureResult.getRequest();
@@ -92,6 +122,16 @@
mEnableTorchCompleter = null;
}
}
+ if (mIsTorchStrengthSupported
+ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM
+ && mTorchStrengthCompleter != null) {
+ Integer torchStrength = captureResult.get(CaptureResult.FLASH_STRENGTH_LEVEL);
+
+ if (torchStrength != null && torchStrength == mTargetTorchStrength) {
+ mTorchStrengthCompleter.set(null);
+ mTorchStrengthCompleter = null;
+ }
+ }
// Return false to keep getting captureResult.
return false;
};
@@ -114,8 +154,10 @@
if (!isActive) {
if (mTargetTorchEnabled) {
mTargetTorchEnabled = false;
- mCamera2CameraControlImpl.enableTorchInternal(false);
- setLiveDataValue(mTorchState, TorchState.OFF);
+ mTargetTorchStrength = mDefaultTorchStrength;
+ mCamera2CameraControlImpl.enableTorchInternal(OFF);
+ setTorchState(OFF);
+ setLiveDataValue(mTorchStrength, mDefaultTorchStrength);
}
if (mEnableTorchCompleter != null) {
@@ -123,6 +165,12 @@
new OperationCanceledException("Camera is not active."));
mEnableTorchCompleter = null;
}
+
+ if (mTorchStrengthCompleter != null) {
+ mTorchStrengthCompleter.setException(
+ new OperationCanceledException("Camera is not active."));
+ mTorchStrengthCompleter = null;
+ }
}
}
@@ -154,11 +202,11 @@
return Futures.immediateFailedFuture(new IllegalStateException("No flash unit"));
}
- setLiveDataValue(mTorchState, enabled ? TorchState.ON : TorchState.OFF);
+ @TorchStateInternal int torchState = enabled ? ON : OFF;
+ setTorchState(torchState);
return CallbackToFutureAdapter.getFuture(completer -> {
- mExecutor.execute(
- () -> enableTorchInternal(completer, enabled));
+ mExecutor.execute(() -> enableTorchInternal(completer, torchState));
return "enableTorch: " + enabled;
});
}
@@ -175,9 +223,14 @@
return mTorchState;
}
+ @NonNull LiveData<Integer> getTorchStrengthLevel() {
+ return mTorchStrength;
+ }
+
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
@ExecutedBy("mExecutor")
- void enableTorchInternal(@Nullable Completer<Void> completer, boolean enabled) {
+ void enableTorchInternal(@Nullable Completer<Void> completer,
+ @TorchStateInternal int torchState) {
if (!mHasFlashUnit) {
if (completer != null) {
completer.setException(new IllegalStateException("No flash unit"));
@@ -186,7 +239,7 @@
}
if (!mIsActive) {
- setLiveDataValue(mTorchState, TorchState.OFF);
+ setTorchState(OFF);
if (completer != null) {
completer.setException(new OperationCanceledException("Camera is not active."));
}
@@ -201,9 +254,9 @@
return;
}
- mTargetTorchEnabled = enabled;
- mCamera2CameraControlImpl.enableTorchInternal(enabled);
- setLiveDataValue(mTorchState, enabled ? TorchState.ON : TorchState.OFF);
+ mTargetTorchEnabled = torchState != OFF;
+ mCamera2CameraControlImpl.enableTorchInternal(torchState);
+ setTorchState(torchState);
if (mEnableTorchCompleter != null) {
mEnableTorchCompleter.setException(new OperationCanceledException(
"There is a new enableTorch being set"));
@@ -211,6 +264,54 @@
mEnableTorchCompleter = completer;
}
+ ListenableFuture<Void> setTorchStrengthLevel(@IntRange(from = 1) int torchStrengthLevel) {
+ if (!mIsTorchStrengthSupported) {
+ return Futures.immediateFailedFuture(new UnsupportedOperationException(
+ "Setting torch strength is not supported on the device."));
+ }
+
+ setLiveDataValue(mTorchStrength, torchStrengthLevel);
+
+ return CallbackToFutureAdapter.getFuture(completer -> {
+ mExecutor.execute(
+ () -> setTorchStrengthLevelInternal(completer, torchStrengthLevel));
+ return "setTorchStrength: " + torchStrengthLevel;
+ });
+ }
+
+ @ExecutedBy("mExecutor")
+ void setTorchStrengthLevelInternal(@Nullable Completer<Void> completer,
+ @IntRange(from = 1) int torchStrengthLevel) {
+ if (!mIsTorchStrengthSupported) {
+ if (completer != null) {
+ completer.setException(new UnsupportedOperationException(
+ "Setting torch strength is not supported on the device."));
+ }
+ return;
+ }
+
+ if (!mIsActive) {
+ if (completer != null) {
+ completer.setException(new OperationCanceledException("Camera is not active."));
+ }
+ return;
+ }
+
+ mTargetTorchStrength = torchStrengthLevel;
+ mCamera2CameraControlImpl.setTorchStrengthLevelInternal(torchStrengthLevel);
+ if (!mCamera2CameraControlImpl.isTorchOn() && completer != null) {
+ // Complete the future if the torch is not on. The new strength will be applied next
+ // time it's turned on.
+ completer.set(null);
+ } else {
+ if (mTorchStrengthCompleter != null) {
+ mTorchStrengthCompleter.setException(new OperationCanceledException(
+ "There is a new torch strength being set."));
+ }
+ mTorchStrengthCompleter = completer;
+ }
+ }
+
/**
* Force update the torch state to OFF.
*
@@ -225,7 +326,25 @@
}
mTargetTorchEnabled = false;
- setLiveDataValue(mTorchState, TorchState.OFF);
+ setTorchState(OFF);
+ }
+
+ private void setTorchState(@TorchStateInternal int internalState) {
+ @TorchState.State int state;
+ switch (internalState) {
+ case ON:
+ state = TorchState.ON;
+ break;
+ case USED_AS_FLASH:
+ // If torch is turned on as flash, it's considered off because it's not used for
+ // torch purpose.
+ // Fall-through
+ case OFF:
+ // Fall-through
+ default:
+ state = TorchState.OFF;
+ }
+ setLiveDataValue(mTorchState, state);
}
private <T> void setLiveDataValue(@NonNull MutableLiveData<T> liveData, T value) {
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControl.java
index a5cc70d..db673e4 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControl.java
@@ -37,6 +37,11 @@
void addZslConfig(SessionConfig.@NonNull Builder sessionConfigBuilder);
/**
+ * This method cleans up all resources used for ZSL capture.
+ */
+ void clearZslConfig();
+
+ /**
* Sets the flag if zero-shutter lag needs to be disabled by user case config.
*
* <p> Zero-shutter lag will be disabled when any of the following conditions:
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControlImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControlImpl.java
index f64b89e..9713bd8 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControlImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControlImpl.java
@@ -113,6 +113,9 @@
@Override
public void setZslDisabledByUserCaseConfig(boolean disabled) {
+ if (mIsZslDisabledByUseCaseConfig != disabled && disabled) {
+ clearRingBuffer();
+ }
mIsZslDisabledByUseCaseConfig = disabled;
}
@@ -232,6 +235,11 @@
}
@Override
+ public void clearZslConfig() {
+ cleanup();
+ }
+
+ @Override
public @Nullable ImageProxy dequeueImageFromBuffer() {
ImageProxy imageProxy = null;
try {
@@ -269,11 +277,7 @@
// We might need synchronization here when clearing ring buffer while image is enqueued
// at the same time. Will test this case.
- ZslRingBuffer imageRingBuffer = mImageRingBuffer;
- while (!imageRingBuffer.isEmpty()) {
- ImageProxy imageProxy = imageRingBuffer.dequeue();
- imageProxy.close();
- }
+ clearRingBuffer();
if (mReprocessingImageDeferrableSurface != null) {
// Termination is triggered when the use count reaches 0.
@@ -282,6 +286,14 @@
}
}
+ private void clearRingBuffer() {
+ ZslRingBuffer imageRingBuffer = mImageRingBuffer;
+ while (!imageRingBuffer.isEmpty()) {
+ ImageProxy imageProxy = imageRingBuffer.dequeue();
+ imageProxy.close();
+ }
+ }
+
private @NonNull Map<Integer, Size> createReprocessingInputSizeMap(
@NonNull CameraCharacteristicsCompat cameraCharacteristicsCompat) {
StreamConfigurationMap map = null;
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControlNoOpImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControlNoOpImpl.java
index 45244ba..6a76ce1 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControlNoOpImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ZslControlNoOpImpl.java
@@ -31,6 +31,10 @@
}
@Override
+ public void clearZslConfig() {
+ }
+
+ @Override
public void setZslDisabledByUserCaseConfig(boolean disabled) {
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsCompat.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsCompat.java
index d92dd68..1f2cc60 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsCompat.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsCompat.java
@@ -131,6 +131,28 @@
}
/**
+ * Returns the default torch strength level.
+ */
+ public int getDefaultTorchStrengthLevel() {
+ Integer defaultLevel = null;
+ if (Build.VERSION.SDK_INT >= 35) {
+ defaultLevel = get(CameraCharacteristics.FLASH_TORCH_STRENGTH_DEFAULT_LEVEL);
+ }
+ return defaultLevel == null ? 1 : defaultLevel;
+ }
+
+ /**
+ * Returns the maximum torch strength level.
+ */
+ public int getMaxTorchStrengthLevel() {
+ Integer maxLevel = null;
+ if (Build.VERSION.SDK_INT >= 35) {
+ maxLevel = get(CameraCharacteristics.FLASH_TORCH_STRENGTH_MAX_LEVEL);
+ }
+ return maxLevel == null ? 1 : maxLevel;
+ }
+
+ /**
* Obtains the {@link StreamConfigurationMapCompat} which contains the output sizes related
* workarounds in it.
*/
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java
index 529c505..e7fcc70 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java
@@ -137,6 +137,8 @@
@RequiresApi(33)
private static final DynamicRangeProfiles CAMERA0_DYNAMIC_RANGE_PROFILES =
new DynamicRangeProfiles(new long[]{DynamicRangeProfiles.HLG10, 0, 0});
+ private static final int CAMERA0_DEFAULT_TORCH_STRENGTH = 25;
+ private static final int CAMERA0_MAX_TORCH_STRENGTH = 50;
private static final String CAMERA1_ID = "1";
private static final int CAMERA1_SUPPORTED_HARDWARE_LEVEL =
@@ -876,6 +878,44 @@
assertThat(supportedDynamicRanges).containsExactly(SDR, HLG_10_BIT);
}
+ @Config(minSdk = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @Test
+ public void apiVersionMet_torchStrengthPropagateCorrectly()
+ throws CameraAccessExceptionCompat {
+ init(/* hasAvailableCapabilities = */ true);
+
+ final Camera2CameraInfoImpl cameraInfo = new Camera2CameraInfoImpl(CAMERA0_ID,
+ mCameraManagerCompat);
+ int strength = 30;
+ when(mMockTorchControl.getTorchStrengthLevel()).thenReturn(new MutableLiveData<>(strength));
+ cameraInfo.linkWithCameraControl(mMockCameraControl);
+
+ assertThat(cameraInfo.getTorchStrengthLevel().getValue()).isEqualTo(strength);
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @Test
+ public void apiVersionMet_canReturnDefaultTorchStrength()
+ throws CameraAccessExceptionCompat {
+ init(/* hasAvailableCapabilities = */ true);
+
+ final CameraInfo cameraInfo = new Camera2CameraInfoImpl(CAMERA0_ID, mCameraManagerCompat);
+
+ assertThat(cameraInfo.getTorchStrengthLevel().getValue()).isEqualTo(
+ CAMERA0_DEFAULT_TORCH_STRENGTH);
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @Test
+ public void apiVersionMet_canReturnMaxTorchStrength()
+ throws CameraAccessExceptionCompat {
+ init(/* hasAvailableCapabilities = */ true);
+
+ final CameraInfo cameraInfo = new Camera2CameraInfoImpl(CAMERA0_ID, mCameraManagerCompat);
+
+ assertThat(cameraInfo.getMaxTorchStrengthLevel()).isEqualTo(CAMERA0_MAX_TORCH_STRENGTH);
+ }
+
@Config(minSdk = 33)
@Test
public void apiVersionMet_canReturnSupportedDynamicRanges_fromFullySpecified()
@@ -902,6 +942,17 @@
assertThat(supportedDynamicRanges).containsExactly(SDR);
}
+ @Config(maxSdk = Build.VERSION_CODES.VANILLA_ICE_CREAM - 1)
+ @Test
+ public void apiVersionNotMet_returnMaxTorchStrengthOne()
+ throws CameraAccessExceptionCompat {
+ init(/* hasAvailableCapabilities = */ true);
+
+ final CameraInfo cameraInfo = new Camera2CameraInfoImpl(CAMERA0_ID, mCameraManagerCompat);
+
+ assertThat(cameraInfo.getMaxTorchStrengthLevel()).isEqualTo(1);
+ }
+
@Config(maxSdk = 32)
@Test
public void apiVersionNotMet_queryHdrDynamicRangeNotSupported()
@@ -1096,6 +1147,10 @@
CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY
}
);
+ shadowCharacteristics0.set(CameraCharacteristics.FLASH_TORCH_STRENGTH_DEFAULT_LEVEL,
+ CAMERA0_DEFAULT_TORCH_STRENGTH);
+ shadowCharacteristics0.set(CameraCharacteristics.FLASH_TORCH_STRENGTH_MAX_LEVEL,
+ CAMERA0_MAX_TORCH_STRENGTH);
}
// Mock the request capability
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/ScreenFlashTaskTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/ScreenFlashTaskTest.kt
index 39b8743..87ddf67 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/ScreenFlashTaskTest.kt
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/ScreenFlashTaskTest.kt
@@ -353,8 +353,8 @@
return screenFlash
}
- override fun enableTorchInternal(torch: Boolean) {
- isTorchEnabled = torch
+ override fun enableTorchInternal(torchState: Int) {
+ isTorchEnabled = torchState != TorchControl.OFF
}
override fun addCaptureResultListener(listener: CaptureResultListener) {
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageAnalysisAbstractAnalyzerTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageAnalysisAbstractAnalyzerTest.java
index 0825137..317df73 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageAnalysisAbstractAnalyzerTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageAnalysisAbstractAnalyzerTest.java
@@ -23,7 +23,9 @@
import static androidx.camera.core.ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888;
import static androidx.camera.core.ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888;
import static androidx.camera.core.ImageAnalysisAbstractAnalyzer.getAdditionalTransformMatrixAppliedByProcessor;
+import static androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_VGA;
import static androidx.camera.testing.impl.ImageProxyUtil.createYUV420ImagePlanes;
+import static androidx.camera.testing.impl.ImageProxyUtil.getDefaultYuvFormatPlaneDataType;
import static com.google.common.truth.Truth.assertThat;
@@ -65,8 +67,6 @@
private static final int WIDTH = 8;
private static final int HEIGHT = 4;
private static final int MAX_IMAGES = 4;
- private static final int PIXEL_STRIDE_Y = 1;
- private static final int PIXEL_STRIDE_UV = 1;
private FakeImageAnalysisAnalyzer mImageAnalysisAbstractAnalyzer;
private FakeImageProxy mImageProxy;
@@ -80,6 +80,8 @@
@Before
public void setup() {
+ int yuvFormatPlaneDataType = getDefaultYuvFormatPlaneDataType(RESOLUTION_VGA.getWidth(),
+ RESOLUTION_VGA.getHeight());
mImageProxy = new FakeImageProxy(new FakeImageInfo());
mImageProxy.setWidth(WIDTH);
mImageProxy.setHeight(HEIGHT);
@@ -87,9 +89,7 @@
mImageProxy.setPlanes(createYUV420ImagePlanes(
WIDTH,
HEIGHT,
- PIXEL_STRIDE_Y,
- PIXEL_STRIDE_UV,
- /*flipUV=*/true,
+ yuvFormatPlaneDataType,
/*incrementValue=*/true));
mSecondImageProxy = new FakeImageProxy(new FakeImageInfo());
@@ -99,9 +99,7 @@
mSecondImageProxy.setPlanes(createYUV420ImagePlanes(
WIDTH,
HEIGHT,
- PIXEL_STRIDE_Y,
- PIXEL_STRIDE_UV,
- /*flipUV=*/true,
+ yuvFormatPlaneDataType,
/*incrementValue=*/true));
mYUVImageReaderProxy = new SafeCloseImageReaderProxy(
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageProcessingUtilTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageProcessingUtilTest.java
index 89ed840..af6ad56 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageProcessingUtilTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageProcessingUtilTest.java
@@ -22,8 +22,10 @@
import static androidx.camera.core.ImageProcessingUtil.rotateYUV;
import static androidx.camera.core.ImageProcessingUtil.rotateYUVAndConvertToNV21;
import static androidx.camera.core.ImageProcessingUtil.writeJpegBytesToSurface;
+import static androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_VGA;
import static androidx.camera.testing.impl.IgnoreProblematicDeviceRule.Companion;
import static androidx.camera.testing.impl.ImageProxyUtil.createYUV420ImagePlanes;
+import static androidx.camera.testing.impl.ImageProxyUtil.getDefaultYuvFormatPlaneDataType;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -42,6 +44,7 @@
import androidx.annotation.IntRange;
import androidx.camera.core.impl.utils.Exif;
import androidx.camera.core.internal.utils.ImageUtil;
+import androidx.camera.testing.impl.ImageProxyUtil;
import androidx.camera.testing.impl.TestImageUtil;
import androidx.camera.testing.impl.fakes.FakeImageInfo;
import androidx.camera.testing.impl.fakes.FakeImageProxy;
@@ -72,10 +75,6 @@
private static final int WIDTH = 8;
private static final int HEIGHT = 4;
- private static final int PIXEL_STRIDE_Y = 1;
- private static final int PIXEL_STRIDE_UV = 1;
- private static final int PIXEL_STRIDE_Y_UNSUPPORTED = 1;
- private static final int PIXEL_STRIDE_UV_UNSUPPORTED = 3;
private static final int MAX_IMAGES = 4;
private static final int JPEG_ENCODE_ERROR_TOLERANCE = 3;
@@ -93,6 +92,8 @@
private static final int[] YUV_RED_STUDIO_SWING_BT601 = {/*y=*/16, /*u=*/128, /*v=*/240};
private FakeImageProxy mYUVImageProxy;
+ @ImageProxyUtil.YuvFormatPlaneDataType
+ private int mYUVImageProxyPlaneDataType;
private SafeCloseImageReaderProxy mRGBImageReaderProxy;
private SafeCloseImageReaderProxy mRotatedRGBImageReaderProxy;
private SafeCloseImageReaderProxy mRotatedYUVImageReaderProxy;
@@ -100,6 +101,8 @@
@Before
public void setUp() {
+ mYUVImageProxyPlaneDataType = getDefaultYuvFormatPlaneDataType(RESOLUTION_VGA.getWidth(),
+ RESOLUTION_VGA.getHeight());
createTestResources(WIDTH, HEIGHT, 90);
}
@@ -110,7 +113,7 @@
private void createTestResources(int width, int height, int rotation) {
mYUVImageProxy = TestImageUtil.createYuvFakeImageProxy(new FakeImageInfo(), width, height,
- true, true);
+ mYUVImageProxyPlaneDataType, true);
mYUVImageProxy.setWidth(width);
mYUVImageProxy.setHeight(height);
mYUVImageProxy.setFormat(ImageFormat.YUV_420_888);
@@ -209,14 +212,6 @@
@Test
public void convertYuvToJpegBytesIntoSurface_sizeAndRotationAreCorrect() throws IOException {
final int expectedRotation = 270;
- // Arrange: create a YUV_420_888 image
- mYUVImageProxy.setPlanes(createYUV420ImagePlanes(
- WIDTH,
- HEIGHT,
- PIXEL_STRIDE_Y,
- PIXEL_STRIDE_UV,
- /*flipUV=*/false,
- /*incrementValue=*/false));
// Act: convert it into JPEG and write into the surface.
ImageProcessingUtil.convertYuvToJpegBytesIntoSurface(mYUVImageProxy,
@@ -255,41 +250,7 @@
}
@Test
- public void convertYUVToRGBWhenNotFlipUV() {
- // Arrange.
- mYUVImageProxy.setPlanes(createYUV420ImagePlanes(
- WIDTH,
- HEIGHT,
- PIXEL_STRIDE_Y,
- PIXEL_STRIDE_UV,
- /*flipUV=*/false,
- /*incrementValue=*/false));
-
- // Act.
- ImageProxy rgbImageProxy = ImageProcessingUtil.convertYUVToRGB(
- mYUVImageProxy,
- mRGBImageReaderProxy,
- mRgbConvertedBuffer,
- /*rotation=*/0,
- /*onePixelShiftRequested=*/false);
-
- // Assert.
- assertThat(rgbImageProxy.getFormat()).isEqualTo(PixelFormat.RGBA_8888);
- assertThat(rgbImageProxy.getPlanes().length).isEqualTo(1);
- rgbImageProxy.close();
- }
-
- @Test
- public void convertYUVToRGBWhenFlipUV() {
- // Arrange.
- mYUVImageProxy.setPlanes(createYUV420ImagePlanes(
- WIDTH,
- HEIGHT,
- PIXEL_STRIDE_Y,
- PIXEL_STRIDE_UV,
- /*flipUV=*/true,
- /*incrementValue=*/false));
-
+ public void convertYUVToRGB_withNV12InputImageProxy() {
// Act.
ImageProxy rgbImageProxy = ImageProcessingUtil.convertYUVToRGB(
mYUVImageProxy,
@@ -306,14 +267,14 @@
@Test
public void convertYUVToRGBWhenUnsupportedYUVFormat() {
- // Arrange.
- mYUVImageProxy.setPlanes(createYUV420ImagePlanes(
- WIDTH,
- HEIGHT,
- PIXEL_STRIDE_Y_UNSUPPORTED,
- PIXEL_STRIDE_UV_UNSUPPORTED,
- /*flipUV=*/true,
- /*incrementValue=*/false));
+ mYUVImageProxy.close();
+ int pixelStrideY = 1;
+ int unsupportedPixelStrideUV = 3;
+ mYUVImageProxy = TestImageUtil.createYuvFakeImageProxy(new FakeImageInfo(), WIDTH, HEIGHT,
+ pixelStrideY, unsupportedPixelStrideUV, false, true);
+ mYUVImageProxy.setWidth(WIDTH);
+ mYUVImageProxy.setHeight(HEIGHT);
+ mYUVImageProxy.setFormat(ImageFormat.YUV_420_888);
// Act.
ImageProxy rgbImageProxy = ImageProcessingUtil.convertYUVToRGB(
@@ -326,6 +287,13 @@
// Assert.
assertThat(rgbImageProxy.getFormat()).isEqualTo(PixelFormat.RGBA_8888);
assertThat(rgbImageProxy.getPlanes().length).isEqualTo(1);
+
+ // Verifies the color value diff between the input image and the decoded output image proxy.
+ Bitmap inputDecodedBitmap = ImageUtil.createBitmapFromImageProxy(mYUVImageProxy);
+ Bitmap outputBitmap = ImageUtil.createBitmapFromImageProxy(rgbImageProxy);
+ assertThat(TestImageUtil.getAverageDiff(inputDecodedBitmap, outputBitmap))
+ .isEqualTo(0);
+
rgbImageProxy.close();
}
@@ -335,9 +303,7 @@
mYUVImageProxy.setPlanes(createYUV420ImagePlanes(
WIDTH,
HEIGHT,
- PIXEL_STRIDE_Y,
- PIXEL_STRIDE_UV,
- /*flipUV=*/false,
+ mYUVImageProxyPlaneDataType,
/*incrementValue=*/true));
// Assert.
@@ -357,15 +323,6 @@
@Test
public void closeYUVImageProxyWhenRGBImageProxyClosed() {
- // Arrange.
- mYUVImageProxy.setPlanes(createYUV420ImagePlanes(
- WIDTH,
- HEIGHT,
- PIXEL_STRIDE_Y,
- PIXEL_STRIDE_UV,
- /*flipUV=*/false,
- /*incrementValue=*/false));
-
// Act.
ImageProxy rgbImageProxy = ImageProcessingUtil.convertYUVToRGB(
mYUVImageProxy,
@@ -386,15 +343,6 @@
@Test
public void rotateRGB_imageRotated() {
- // Arrange.
- mYUVImageProxy.setPlanes(createYUV420ImagePlanes(
- WIDTH,
- HEIGHT,
- PIXEL_STRIDE_Y,
- PIXEL_STRIDE_UV,
- /*flipUV=*/true,
- /*incrementValue=*/false));
-
// Act.
ImageProxy rgbImageProxy = ImageProcessingUtil.convertYUVToRGB(
mYUVImageProxy,
@@ -437,7 +385,8 @@
@Test
public void rotateYUV_imageRotated_0_outputNV21() {
- rotateYUV_imageRotated(OUTPUT_IMAGE_FORMAT_NV21, 0, false);
+ rotateYUV_imageRotated(OUTPUT_IMAGE_FORMAT_NV21, 0,
+ ImageProcessingUtil.isNV21FormatImage(mYUVImageProxy));
}
@Test
@@ -462,8 +411,8 @@
// Pixel2 API28 emulator has problem to run the test
assumeFalse(Companion.isPixel2Api28Emulator());
// Arrange.
- int width = 640;
- int height = 480;
+ int width = 64;
+ int height = 32;
closeTestResources();
createTestResources(width, height, rotation);
@@ -505,6 +454,10 @@
assertThat(yuvImageProxy.getWidth()).isEqualTo(flipWh ? height : width);
assertThat(yuvImageProxy.getHeight()).isEqualTo(flipWh ? width : height);
+ if (outputImageFormat == OUTPUT_IMAGE_FORMAT_NV21) {
+ assertThat(ImageProcessingUtil.isNV21FormatImage(yuvImageProxy)).isTrue();
+ }
+
// Verifies the color value diff between the rotated input image and the decoded output
// image proxy.
Bitmap inputDecodedBitmap = ImageUtil.createBitmapFromImageProxy(mYUVImageProxy);
@@ -631,9 +584,7 @@
yuvImageProxy.setPlanes(createYUV420ImagePlanes(
WIDTH,
HEIGHT,
- PIXEL_STRIDE_Y,
- PIXEL_STRIDE_UV,
- /*flipUV=*/true,
+ mYUVImageProxyPlaneDataType,
/*incrementValue=*/false));
return yuvImageProxy;
diff --git a/camera/camera-core/src/main/cpp/image_processing_util_jni.cc b/camera/camera-core/src/main/cpp/image_processing_util_jni.cc
index 66d9c5b..b7becfa 100644
--- a/camera/camera-core/src/main/cpp/image_processing_util_jni.cc
+++ b/camera/camera-core/src/main/cpp/image_processing_util_jni.cc
@@ -193,24 +193,50 @@
src_y_ptr[src_stride_y - start_offset_y + i * src_stride_y];
}
- // U
- for (int i = 0; i < height / 2; i++) {
- memmove(&src_u_ptr[0 + i * src_stride_u],
- &src_u_ptr[start_offset_u + i * src_stride_u],
- width / 2 - 1);
+ const ptrdiff_t vu_off = src_v_ptr - src_u_ptr;
- src_u_ptr[width / 2 - start_offset_u + i * src_stride_u] =
- src_u_ptr[src_stride_u - start_offset_u + i * src_stride_u];
- }
+ // Note that if the data format is not I420, NV12 or NV21 cases, the data copy result might be
+ // incorrect. That should be a very special format. If the data-shift issue also on that
+ // device, the correct copy logic needs to be added here.
+ if (src_pixel_stride_uv == 2 && (vu_off == 1 || vu_off == -1)) {
+ // NV12 or NV21 cases
+ // The U and V data are interleaved in a continuous array. Determines the start pointer by
+ // the vu_off value.
+ uint8_t *src_uv_ptr = vu_off == 1 ? src_u_ptr : src_v_ptr;
- // V
- for (int i = 0; i < height / 2; i++) {
- memmove(&src_v_ptr[0 + i * src_stride_v],
- &src_v_ptr[start_offset_v + i * src_stride_v],
- width / 2 - 1);
+ // Because U/V data are interleaved and continuous, the data-copy process only need to be
+ // done once. Both U/V data will be shifted together.
+ for (int i = 0; i < height / 2; i++) {
+ memmove(&src_uv_ptr[0 + i * src_stride_u],
+ &src_uv_ptr[src_pixel_stride_uv + i * src_stride_u],
+ width / 2 - src_pixel_stride_uv);
- src_v_ptr[width / 2 - start_offset_v + i * src_stride_v] =
- src_v_ptr[src_stride_v - start_offset_v + i * src_stride_v];
+ src_uv_ptr[width / 2 - src_pixel_stride_uv + i * src_stride_u] =
+ src_uv_ptr[src_stride_u - src_pixel_stride_uv + i * src_stride_u];
+ src_uv_ptr[width / 2 - src_pixel_stride_uv + i * src_stride_u + 1] =
+ src_uv_ptr[src_stride_u - src_pixel_stride_uv + i * src_stride_u + 1];
+ }
+ } else {
+ // I420
+ // U
+ for (int i = 0; i < height / 2; i++) {
+ memmove(&src_u_ptr[0 + i * src_stride_u],
+ &src_u_ptr[start_offset_u + i * src_stride_u],
+ width / 2 - 1);
+
+ src_u_ptr[width / 2 - start_offset_u + i * src_stride_u] =
+ src_u_ptr[src_stride_u - start_offset_u + i * src_stride_u];
+ }
+
+ // V
+ for (int i = 0; i < height / 2; i++) {
+ memmove(&src_v_ptr[0 + i * src_stride_v],
+ &src_v_ptr[start_offset_v + i * src_stride_v],
+ width / 2 - 1);
+
+ src_v_ptr[width / 2 - start_offset_v + i * src_stride_v] =
+ src_v_ptr[src_stride_v - start_offset_v + i * src_stride_v];
+ }
}
return 0;
@@ -670,24 +696,22 @@
return byte_buffer_v_ptr - byte_buffer_u_ptr;
}
-JNIEXPORT jobject Java_androidx_camera_core_ImageProcessingUtil_nativeCreateNV21ByteBuffers(
+/**
+ * Creates ByteBuffer from the offset of the input byte buffer.
+ */
+JNIEXPORT jobject
+Java_androidx_camera_core_ImageProcessingUtil_nativeNewDirectByteBuffer(
JNIEnv *env,
jclass,
jobject byte_buffer,
- jint vu_data_length) {
+ jint offset,
+ jint capacity) {
uint8_t *byte_buffer_ptr =
static_cast<uint8_t *>(env->GetDirectBufferAddress(byte_buffer));
// Create the ByteBuffers
- jobject vByteBuffer = env->NewDirectByteBuffer(byte_buffer_ptr, vu_data_length);
- jobject uByteBuffer = env->NewDirectByteBuffer(byte_buffer_ptr + 1, vu_data_length);
-
- jclass pairClass = env->FindClass("android/util/Pair");
- jmethodID pairConstructor = env->GetMethodID(pairClass, "<init>",
- "(Ljava/lang/Object;Ljava/lang/Object;)V");
-
- return env->NewObject(pairClass, pairConstructor, uByteBuffer, vByteBuffer);
+ return env->NewDirectByteBuffer(byte_buffer_ptr + offset, capacity);
}
} // extern "C"
\ No newline at end of file
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraControl.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraControl.java
index 6b25593..09718fb 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraControl.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraControl.java
@@ -17,6 +17,7 @@
package androidx.camera.core;
import androidx.annotation.FloatRange;
+import androidx.annotation.IntRange;
import androidx.annotation.RestrictTo;
import androidx.camera.core.impl.utils.futures.Futures;
@@ -233,6 +234,29 @@
@NonNull ListenableFuture<Integer> setExposureCompensationIndex(int value);
/**
+ * Sets torch strength level.
+ *
+ * <p>The torch strength level only applies on the case that torch is turned on by
+ * {@link #enableTorch(boolean)} and doesn't affect other usages of the flash unit.
+ *
+ * <p>Use the value returned by {@link CameraInfo#getMaxTorchStrengthLevel()} to set the maximum
+ * level the device can provide and use {@code 1} to set the minimum level. If a level
+ * greater than the maximum value or less than {@code 1} is set, the returned
+ * {@link ListenableFuture} will fail with an {@link IllegalArgumentException} and it won't
+ * modify the torch strength.
+ *
+ * @param torchStrengthLevel The desired torch strength level.
+ * @return a {@link ListenableFuture} that is completed when the torch strength has been
+ * applied.
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ default @NonNull ListenableFuture<Void> setTorchStrengthLevelAsync(
+ @IntRange(from = 1) int torchStrengthLevel) {
+ return Futures.immediateFailedFuture(new UnsupportedOperationException(
+ "Setting torch strength is not supported on the device."));
+ }
+
+ /**
* An exception representing a failure that the operation is canceled which might be caused by
* a new value is set or camera is closed.
*
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraInfo.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraInfo.java
index efbb3c3..91b2f00 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraInfo.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraInfo.java
@@ -23,6 +23,7 @@
import android.view.Surface;
import androidx.annotation.FloatRange;
+import androidx.annotation.IntRange;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.annotation.StringDef;
@@ -425,6 +426,29 @@
return Collections.emptySet();
}
+ /**
+ * Returns the maximum torch strength level.
+ *
+ * @return The maximum strength level. If the device doesn't support configuring torch
+ * strength, returns {@code 1}.
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @IntRange(from = 1)
+ default int getMaxTorchStrengthLevel() {
+ return 1;
+ }
+
+ /**
+ * Returns the {@link LiveData} of the torch strength level.
+ *
+ * <p>The value of the {@link LiveData} will be the default torch strength level of this
+ * device if {@link CameraControl#setTorchStrengthLevelAsync(int)} hasn't been called.
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ default @NonNull LiveData<Integer> getTorchStrengthLevel() {
+ return new MutableLiveData<>(1);
+ }
+
@StringDef(open = true, value = {IMPLEMENTATION_TYPE_UNKNOWN,
IMPLEMENTATION_TYPE_CAMERA2_LEGACY, IMPLEMENTATION_TYPE_CAMERA2,
IMPLEMENTATION_TYPE_FAKE})
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
index 31a690e..aa35cc5 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
@@ -230,18 +230,25 @@
private static final Boolean DEFAULT_ONE_PIXEL_SHIFT_ENABLED = null;
// Default to disabled for rotation.
private static final boolean DEFAULT_OUTPUT_IMAGE_ROTATION_ENABLED = false;
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final ImageAnalysisAbstractAnalyzer mImageAnalysisAbstractAnalyzer;
private final Object mAnalysisLock = new Object();
+ @GuardedBy("mAnalysisLock")
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ ImageAnalysisAbstractAnalyzer mImageAnalysisAbstractAnalyzer;
+
////////////////////////////////////////////////////////////////////////////////////////////
// [UseCase lifetime dynamic] - Dynamic variables which could change during anytime during
// the UseCase lifetime.
////////////////////////////////////////////////////////////////////////////////////////////
@GuardedBy("mAnalysisLock")
+ private Executor mSubscribedAnalyzerExecutor;
+ @GuardedBy("mAnalysisLock")
private ImageAnalysis.Analyzer mSubscribedAnalyzer;
+ @GuardedBy("mAnalysisLock")
+ private Rect mViewPortCropRect;
+ @GuardedBy("mAnalysisLock")
+ private Matrix mSensorToBufferTransformMatrix;
////////////////////////////////////////////////////////////////////////////////////////////
// [UseCase attached dynamic] - Can change but is only available when the UseCase is attached.
@@ -261,20 +268,6 @@
@SuppressWarnings("WeakerAccess")
ImageAnalysis(@NonNull ImageAnalysisConfig config) {
super(config);
-
- // Get the combined configuration with defaults
- ImageAnalysisConfig combinedConfig = (ImageAnalysisConfig) getCurrentConfig();
-
- if (combinedConfig.getBackpressureStrategy(DEFAULT_BACKPRESSURE_STRATEGY)
- == STRATEGY_BLOCK_PRODUCER) {
- mImageAnalysisAbstractAnalyzer = new ImageAnalysisBlockingAnalyzer();
- } else {
- mImageAnalysisAbstractAnalyzer = new ImageAnalysisNonBlockingAnalyzer(
- config.getBackgroundExecutor(CameraXExecutors.highPriorityExecutor()));
- }
- mImageAnalysisAbstractAnalyzer.setOutputImageFormat(getOutputImageFormat());
- mImageAnalysisAbstractAnalyzer.setOutputImageRotationEnabled(
- isOutputImageRotationEnabled());
}
/**
@@ -284,18 +277,6 @@
@Override
protected @NonNull UseCaseConfig<?> onMergeConfig(@NonNull CameraInfoInternal cameraInfo,
UseCaseConfig.@NonNull Builder<?, ?, ?> builder) {
-
- // Flag to enable or disable one pixel shift. It will override the flag set by device info.
- // If enabled, the workaround will be applied for all devices.
- // If disabled, the workaround will be disabled for all devices.
- // If not configured, the workaround will be applied to the problem devices only.
- Boolean isOnePixelShiftEnabled = getOnePixelShiftEnabled();
- boolean isOnePixelShiftIssueDevice = cameraInfo.getCameraQuirks().contains(
- OnePixelShiftQuirk.class) ? true : false;
- mImageAnalysisAbstractAnalyzer.setOnePixelShiftEnabled(
- isOnePixelShiftEnabled == null ? isOnePixelShiftIssueDevice
- : isOnePixelShiftEnabled);
-
// Override the target resolution with the value provided by the analyzer.
Size analyzerResolution;
synchronized (mAnalysisLock) {
@@ -397,6 +378,12 @@
imageQueueDepth));
}
+ ImageAnalysisAbstractAnalyzer imageAnalysisAbstractAnalyzer;
+ synchronized (mAnalysisLock) {
+ recreateImageAnalysisAbstractAnalyzer();
+ imageAnalysisAbstractAnalyzer = mImageAnalysisAbstractAnalyzer;
+ }
+
boolean flipWH = getCamera() != null ? isFlipWH(getCamera()) : false;
int width = flipWH ? resolution.getHeight() : resolution.getWidth();
int height = flipWH ? resolution.getWidth() : resolution.getHeight();
@@ -423,12 +410,12 @@
format,
imageReaderProxy.getMaxImages())) : null;
if (processedImageReaderProxy != null) {
- mImageAnalysisAbstractAnalyzer.setProcessedImageReaderProxy(processedImageReaderProxy);
+ imageAnalysisAbstractAnalyzer.setProcessedImageReaderProxy(processedImageReaderProxy);
}
tryUpdateRelativeRotation();
- imageReaderProxy.setOnImageAvailableListener(mImageAnalysisAbstractAnalyzer,
+ imageReaderProxy.setOnImageAvailableListener(imageAnalysisAbstractAnalyzer,
backgroundExecutor);
SessionConfig.Builder sessionConfigBuilder = SessionConfig.Builder.createFrom(config,
@@ -472,7 +459,7 @@
clearPipeline();
// Clear cache so app won't get a outdated image.
- mImageAnalysisAbstractAnalyzer.clearCache();
+ imageAnalysisAbstractAnalyzer.clearCache();
// Only reset the pipeline when the bound camera is the same.
mSessionConfigBuilder = createPipeline(getCameraId(),
(ImageAnalysisConfig) getCurrentConfig(),
@@ -486,6 +473,63 @@
return sessionConfigBuilder;
}
+ private void recreateImageAnalysisAbstractAnalyzer() {
+ synchronized (mAnalysisLock) {
+ ImageAnalysisConfig config = (ImageAnalysisConfig) getCurrentConfig();
+
+ if (config.getBackpressureStrategy(DEFAULT_BACKPRESSURE_STRATEGY)
+ == STRATEGY_BLOCK_PRODUCER) {
+ mImageAnalysisAbstractAnalyzer = new ImageAnalysisBlockingAnalyzer();
+ } else {
+ mImageAnalysisAbstractAnalyzer = new ImageAnalysisNonBlockingAnalyzer(
+ config.getBackgroundExecutor(CameraXExecutors.highPriorityExecutor()));
+ }
+ mImageAnalysisAbstractAnalyzer.setOutputImageFormat(getOutputImageFormat());
+ mImageAnalysisAbstractAnalyzer.setOutputImageRotationEnabled(
+ isOutputImageRotationEnabled());
+
+ CameraInternal cameraInternal = getCamera();
+
+ // Flag to enable or disable one pixel shift. It will override the flag set by device
+ // info.
+ // If enabled, the workaround will be applied for all devices.
+ // If disabled, the workaround will be disabled for all devices.
+ // If not configured, the workaround will be applied to the problem devices only.
+ Boolean isOnePixelShiftEnabled = getOnePixelShiftEnabled();
+ boolean isOnePixelShiftIssueDevice = false;
+ if (cameraInternal != null) {
+ isOnePixelShiftIssueDevice =
+ cameraInternal.getCameraInfoInternal().getCameraQuirks().contains(
+ OnePixelShiftQuirk.class);
+ }
+ mImageAnalysisAbstractAnalyzer.setOnePixelShiftEnabled(
+ isOnePixelShiftEnabled == null ? isOnePixelShiftIssueDevice
+ : isOnePixelShiftEnabled);
+
+ // Sets relative rotation
+ if (cameraInternal != null) {
+ mImageAnalysisAbstractAnalyzer.setRelativeRotation(
+ getRelativeRotation(cameraInternal));
+ }
+
+ // Sets view port crop rect
+ if (mViewPortCropRect != null) {
+ mImageAnalysisAbstractAnalyzer.setViewPortCropRect(mViewPortCropRect);
+ }
+
+ // Sets sensor to buffer transform matrix
+ if (mSensorToBufferTransformMatrix != null) {
+ mImageAnalysisAbstractAnalyzer.setSensorToBufferTransformMatrix(
+ mSensorToBufferTransformMatrix);
+ }
+
+ if (mSubscribedAnalyzerExecutor != null && mSubscribedAnalyzer != null) {
+ mImageAnalysisAbstractAnalyzer.setAnalyzer(mSubscribedAnalyzerExecutor,
+ mSubscribedAnalyzer);
+ }
+ }
+ }
+
/**
* Clear the internal pipeline so that the pipeline can be set up again.
*/
@@ -512,10 +556,13 @@
*/
public void clearAnalyzer() {
synchronized (mAnalysisLock) {
- mImageAnalysisAbstractAnalyzer.setAnalyzer(null, null);
+ if (mImageAnalysisAbstractAnalyzer != null) {
+ mImageAnalysisAbstractAnalyzer.setAnalyzer(null, null);
+ }
if (mSubscribedAnalyzer != null) {
notifyInactive();
}
+ mSubscribedAnalyzerExecutor = null;
mSubscribedAnalyzer = null;
}
}
@@ -601,10 +648,14 @@
*/
public void setAnalyzer(@NonNull Executor executor, @NonNull Analyzer analyzer) {
synchronized (mAnalysisLock) {
- mImageAnalysisAbstractAnalyzer.setAnalyzer(executor, image -> analyzer.analyze(image));
+ if (mImageAnalysisAbstractAnalyzer != null) {
+ mImageAnalysisAbstractAnalyzer.setAnalyzer(executor,
+ image -> analyzer.analyze(image));
+ }
if (mSubscribedAnalyzer == null) {
notifyActive();
}
+ mSubscribedAnalyzerExecutor = executor;
mSubscribedAnalyzer = analyzer;
}
}
@@ -616,7 +667,12 @@
@Override
public void setViewPortCropRect(@NonNull Rect viewPortCropRect) {
super.setViewPortCropRect(viewPortCropRect);
- mImageAnalysisAbstractAnalyzer.setViewPortCropRect(viewPortCropRect);
+ synchronized (mAnalysisLock) {
+ if (mImageAnalysisAbstractAnalyzer != null) {
+ mImageAnalysisAbstractAnalyzer.setViewPortCropRect(viewPortCropRect);
+ }
+ mViewPortCropRect = viewPortCropRect;
+ }
}
/**
@@ -626,7 +682,12 @@
@Override
public void setSensorToBufferTransformMatrix(@NonNull Matrix matrix) {
super.setSensorToBufferTransformMatrix(matrix);
- mImageAnalysisAbstractAnalyzer.setSensorToBufferTransformMatrix(matrix);
+ synchronized (mAnalysisLock) {
+ if (mImageAnalysisAbstractAnalyzer != null) {
+ mImageAnalysisAbstractAnalyzer.setSensorToBufferTransformMatrix(matrix);
+ }
+ mSensorToBufferTransformMatrix = matrix;
+ }
}
private boolean isFlipWH(@NonNull CameraInternal cameraInternal) {
@@ -764,7 +825,10 @@
@Override
public void onUnbind() {
clearPipeline();
- mImageAnalysisAbstractAnalyzer.detach();
+ synchronized (mAnalysisLock) {
+ mImageAnalysisAbstractAnalyzer.detach();
+ mImageAnalysisAbstractAnalyzer = null;
+ }
}
/**
@@ -789,15 +853,6 @@
/**
* {@inheritDoc}
*/
- @Override
- @RestrictTo(Scope.LIBRARY_GROUP)
- public void onBind() {
- mImageAnalysisAbstractAnalyzer.attach();
- }
-
- /**
- * {@inheritDoc}
- */
@RestrictTo(Scope.LIBRARY_GROUP)
@Override
public UseCaseConfig.@NonNull Builder<?, ?, ?> getUseCaseConfigBuilder(@NonNull Config config) {
@@ -837,9 +892,12 @@
* Updates relative rotation if attached to a camera. No-op otherwise.
*/
private void tryUpdateRelativeRotation() {
- CameraInternal cameraInternal = getCamera();
- if (cameraInternal != null) {
- mImageAnalysisAbstractAnalyzer.setRelativeRotation(getRelativeRotation(cameraInternal));
+ synchronized (mAnalysisLock) {
+ CameraInternal cameraInternal = getCamera();
+ if (cameraInternal != null) {
+ mImageAnalysisAbstractAnalyzer.setRelativeRotation(
+ getRelativeRotation(cameraInternal));
+ }
}
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
index 5e3cb54..668767e 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
@@ -1615,6 +1615,8 @@
mTakePictureManager.abortRequests();
mTakePictureManager = null;
}
+ // Always clear ZSL resources to release the RingBuffer.
+ getCameraControl().clearZslConfig();
}
/**
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageProcessingUtil.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageProcessingUtil.java
index 432d856..c9e977a 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageProcessingUtil.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageProcessingUtil.java
@@ -25,7 +25,6 @@
import android.media.ImageWriter;
import android.os.Build;
import android.util.Log;
-import android.util.Pair;
import android.view.Surface;
import androidx.annotation.IntRange;
@@ -431,8 +430,8 @@
int rotatedHeight =
(rotationDegrees % 180 == 0) ? imageProxy.getHeight() : imageProxy.getWidth();
- Pair<ByteBuffer, ByteBuffer> nv21UVByteBuffers = nativeCreateNV21ByteBuffers(
- nv21UVDelegatedBuffer, nv21UVDelegatedBuffer.capacity());
+ ByteBuffer position1ChildByteBuffer = nativeNewDirectByteBuffer(
+ nv21UVDelegatedBuffer, 1, nv21UVDelegatedBuffer.capacity());
int result = nativeRotateYUV(
imageProxy.getPlanes()[0].getBuffer(),
@@ -445,10 +444,10 @@
nv21YDelegatedBuffer,
rotatedWidth,
1,
- nv21UVByteBuffers.first,
+ position1ChildByteBuffer,
rotatedWidth,
2,
- nv21UVByteBuffers.second,
+ nv21UVDelegatedBuffer,
rotatedWidth,
2,
yRotatedBuffer,
@@ -468,8 +467,8 @@
return new SingleCloseImageProxy(
new NV21ImageProxy(imageProxy,
nv21YDelegatedBuffer,
- nv21UVByteBuffers.first,
- nv21UVByteBuffers.second,
+ position1ChildByteBuffer,
+ nv21UVDelegatedBuffer,
rotatedWidth,
rotatedHeight,
rotationDegrees));
@@ -805,13 +804,22 @@
int height,
@ImageOutputConfig.RotationDegreesValue int rotationDegrees);
- private static native int nativeGetYUVImageVUOff(
+ /**
+ * Checks the V and U planes' ByteBuffer start position pointer distance.
+ *
+ * <p>This can be used to determine whether the YUV image is NV12 or NV21 data type.
+ */
+ public static native int nativeGetYUVImageVUOff(
@NonNull ByteBuffer srcByteBufferV,
@NonNull ByteBuffer srcByteBufferU
);
- private static native Pair<ByteBuffer, ByteBuffer> nativeCreateNV21ByteBuffers(
+ /**
+ * Creates ByteBuffer from the offset of the input byte buffer.
+ */
+ public static native @NonNull ByteBuffer nativeNewDirectByteBuffer(
@NonNull ByteBuffer byteBuffer,
- int vuDataLength
+ int offset,
+ int capacity
);
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/TorchState.java b/camera/camera-core/src/main/java/androidx/camera/core/TorchState.java
index c7da97c..23f50af 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/TorchState.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/TorchState.java
@@ -32,8 +32,7 @@
private TorchState() {
}
- /**
- */
+ /** The camera flash torch state. */
@IntDef({OFF, ON})
@Retention(RetentionPolicy.SOURCE)
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraControlInternal.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraControlInternal.java
index cd75af2..ff8e42d 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraControlInternal.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraControlInternal.java
@@ -79,6 +79,11 @@
void addZslConfig(SessionConfig.@NonNull Builder sessionConfigBuilder);
/**
+ * Clear the resource for ZSL capture.
+ */
+ void clearZslConfig();
+
+ /**
* Sets the flag if zero-shutter lag needs to be disabled by user case config.
*
* <p> Zero-shutter lag will be disabled when any of the following conditions:
@@ -227,6 +232,11 @@
}
@Override
+ public void clearZslConfig() {
+
+ }
+
+ @Override
public @NonNull ListenableFuture<Void> enableTorch(boolean torch) {
return Futures.immediateFuture(null);
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraControl.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraControl.java
index 66ea978..b39ebca 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraControl.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraControl.java
@@ -18,6 +18,7 @@
import android.graphics.Rect;
+import androidx.annotation.IntRange;
import androidx.annotation.VisibleForTesting;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.FocusMeteringResult;
@@ -83,6 +84,12 @@
}
@Override
+ public @NonNull ListenableFuture<Void> setTorchStrengthLevelAsync(
+ @IntRange(from = 1) int torchStrengthLevel) {
+ return mCameraControlInternal.setTorchStrengthLevelAsync(torchStrengthLevel);
+ }
+
+ @Override
@ImageCapture.FlashMode
public int getFlashMode() {
return mCameraControlInternal.getFlashMode();
@@ -104,6 +111,11 @@
}
@Override
+ public void clearZslConfig() {
+ mCameraControlInternal.clearZslConfig();
+ }
+
+ @Override
public void setZslDisabledByUserCaseConfig(boolean disabled) {
mCameraControlInternal.setZslDisabledByUserCaseConfig(disabled);
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraInfo.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraInfo.java
index fd752fd..10d896a 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraInfo.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraInfo.java
@@ -73,6 +73,16 @@
}
@Override
+ public @NonNull LiveData<Integer> getTorchStrengthLevel() {
+ return mCameraInfoInternal.getTorchStrengthLevel();
+ }
+
+ @Override
+ public int getMaxTorchStrengthLevel() {
+ return mCameraInfoInternal.getMaxTorchStrengthLevel();
+ }
+
+ @Override
public boolean isLowLightBoostSupported() {
return mCameraInfoInternal.isLowLightBoostSupported();
}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java
index a769d33..89c7aaf 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java
@@ -341,6 +341,12 @@
mIsZslConfigAdded = true;
}
+ @Override
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public void clearZslConfig() {
+ mIsZslConfigAdded = false;
+ }
+
/**
* Checks if {@link FakeCameraControl#addZslConfig(SessionConfig.Builder)} has been triggered.
*/
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/ImageProxyUtil.java b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/ImageProxyUtil.java
index 87f6772..28888d2 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/ImageProxyUtil.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/ImageProxyUtil.java
@@ -16,16 +16,50 @@
package androidx.camera.testing.impl;
+import static android.graphics.ImageFormat.YUV_420_888;
+
+import android.media.Image;
+import android.media.ImageReader;
+import android.media.ImageWriter;
+import android.os.Build;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.RestrictTo;
+import androidx.camera.core.ImageProcessingUtil;
import androidx.camera.core.ImageProxy;
import org.jspecify.annotations.NonNull;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
/**
* Utility class to generate image planes and buffer data in image proxy for unit test.
*/
public final class ImageProxyUtil {
+ /**
+ * I420 plane data type.
+ */
+ public static final int YUV_FORMAT_PLANE_DATA_TYPE_I420 = 0;
+ /**
+ * NV12 plane data type.
+ */
+ public static final int YUV_FORMAT_PLANE_DATA_TYPE_NV12 = 1;
+ /**
+ * NV21 plane data type.
+ */
+ public static final int YUV_FORMAT_PLANE_DATA_TYPE_NV21 = 2;
+
+ /**
+ * YUV format plane data type
+ */
+ @IntDef({YUV_FORMAT_PLANE_DATA_TYPE_I420, YUV_FORMAT_PLANE_DATA_TYPE_NV12,
+ YUV_FORMAT_PLANE_DATA_TYPE_NV21})
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public @interface YuvFormatPlaneDataType {
+ }
private ImageProxyUtil() {
}
@@ -35,6 +69,25 @@
*
* @param width image width.
* @param height image height.
+ * @param yuvFormatPlaneDataType the plane data type for the created image planes
+ * @param incrementValue true if the data value will increment by position, e.g. 1, 2, 3, etc,.
+ * @return image planes in image proxy.
+ */
+ public static ImageProxy.PlaneProxy @NonNull [] createYUV420ImagePlanes(
+ final int width,
+ final int height,
+ final @YuvFormatPlaneDataType int yuvFormatPlaneDataType,
+ final boolean incrementValue) {
+ return createYUV420ImagePlanes(width, height, 1,
+ yuvFormatPlaneDataType == YUV_FORMAT_PLANE_DATA_TYPE_I420 ? 1 : 2,
+ yuvFormatPlaneDataType == YUV_FORMAT_PLANE_DATA_TYPE_NV21, incrementValue);
+ }
+
+ /**
+ * Creates YUV_420_888 image planes.
+ *
+ * @param width image width.
+ * @param height image height.
* @param flipUV true if v data is before u data in memory, false otherwise.
* @param incrementValue true if the data value will increment by position, e.g. 1, 2, 3, etc,.
* @return image planes in image proxy.
@@ -48,28 +101,85 @@
final boolean incrementValue) {
ImageProxy.PlaneProxy[] planes = new ImageProxy.PlaneProxy[3];
- planes[0] =
- createPlane(width, height, pixelStrideY, /*dataValue=*/ 1, incrementValue);
+ planes[0] = createPlane(width, height, pixelStrideY, /*dataValue=*/ 1, incrementValue);
- if (flipUV) {
- planes[2] =
- createPlane(
- width / 2, height / 2, pixelStrideUV, /*dataValue=*/ 1, incrementValue);
- planes[1] =
- createPlane(
- width / 2, height / 2, pixelStrideUV, /*dataValue=*/ 1, incrementValue);
+ if (pixelStrideUV == 1) {
+ // I420 memory layout. U and V plane data is not interleaved. Directly create two
+ // planes with separate ByteBuffers.
+ planes[1] = createPlane(width / 2, height / 2, pixelStrideUV, /*dataValue=*/ 1,
+ incrementValue);
+ planes[2] = createPlane(width / 2, height / 2, pixelStrideUV, /*dataValue=*/ 1,
+ incrementValue);
} else {
- planes[1] =
- createPlane(
- width / 2, height / 2, pixelStrideUV, /*dataValue=*/ 1, incrementValue);
- planes[2] =
- createPlane(
- width / 2, height / 2, pixelStrideUV, /*dataValue=*/ 1, incrementValue);
+ // U and V plane data is interleaved.
+ // Plane data starts from position 0 of a ByteBuffer.
+ ImageProxy.PlaneProxy pos0StartedByteBufferPlane = createPlane(width / 2, height / 2,
+ pixelStrideUV, /*dataValue=*/ 1, incrementValue);
+ // Plane data starts from position 1 of the same ByteBuffer.
+ ImageProxy.PlaneProxy pos1StartedByteBufferPlane = createPlane(
+ ImageProcessingUtil.nativeNewDirectByteBuffer(
+ pos0StartedByteBufferPlane.getBuffer(),
+ 1,
+ pos0StartedByteBufferPlane.getBuffer().capacity() - 1),
+ pos0StartedByteBufferPlane.getRowStride(),
+ pos0StartedByteBufferPlane.getPixelStride(),
+ /*dataValue=*/ 1,
+ incrementValue
+ );
+
+ planes[1] = flipUV ? pos1StartedByteBufferPlane : pos0StartedByteBufferPlane;
+ planes[2] = flipUV ? pos0StartedByteBufferPlane : pos1StartedByteBufferPlane;
}
+
return planes;
}
/**
+ * Determines the device's default YUV format plane data type by checking the type of the image
+ * acquired from a YUV_420_888 image reader. This can make the tests more close to the real
+ * processing situation on the device.
+ */
+ public static @ImageProxyUtil.YuvFormatPlaneDataType int getDefaultYuvFormatPlaneDataType(
+ int width, int height) {
+ if (Build.VERSION.SDK_INT < 23 || "robolectric".equals(Build.FINGERPRINT)) {
+ return YUV_FORMAT_PLANE_DATA_TYPE_I420;
+ }
+
+ try (ImageReader imageReader = ImageReader.newInstance(width, height, YUV_420_888, 2);
+ ImageWriter imageWriter = ImageWriter.newInstance(imageReader.getSurface(), 2);
+ Image image = imageWriter.dequeueInputImage()) {
+
+ if (image.getPlanes().length != 3) {
+ return YUV_FORMAT_PLANE_DATA_TYPE_I420;
+ }
+
+ Image.Plane planeY = image.getPlanes()[0];
+ Image.Plane planeU = image.getPlanes()[1];
+ Image.Plane planeV = image.getPlanes()[2];
+
+ if (planeY.getPixelStride() == 1) {
+ // For all non-NV12 or non-NV21 cases, use I420 plane data type to run the test.
+ if (planeU.getPixelStride() != 2 || planeV.getPixelStride() != 2) {
+ return YUV_FORMAT_PLANE_DATA_TYPE_I420;
+ }
+
+ int vuOff = ImageProcessingUtil.nativeGetYUVImageVUOff(planeV.getBuffer(),
+ planeU.getBuffer());
+ switch (vuOff) {
+ case 1:
+ return YUV_FORMAT_PLANE_DATA_TYPE_NV12;
+ case -1:
+ return YUV_FORMAT_PLANE_DATA_TYPE_NV21;
+ default:
+ return YUV_FORMAT_PLANE_DATA_TYPE_I420;
+ }
+ }
+ }
+
+ return YUV_FORMAT_PLANE_DATA_TYPE_I420;
+ }
+
+ /**
* Creates {@link android.graphics.ImageFormat.RAW_SENSOR} image planes.
*
* @param width image width.
@@ -95,13 +205,34 @@
final int pixelStride,
final int dataValue,
final boolean incrementValue) {
+ final ByteBuffer byteBuffer =
+ createBuffer(width, height, pixelStride, dataValue, incrementValue);
+ return createPlane(byteBuffer, width * pixelStride, pixelStride, dataValue, incrementValue);
+ }
+
+ private static ImageProxy.@NonNull PlaneProxy createPlane(
+ final ByteBuffer byteBuffer,
+ final int rowStride,
+ final int pixelStride,
+ final int dataValue,
+ final boolean incrementValue) {
return new ImageProxy.PlaneProxy() {
- final ByteBuffer mBuffer =
- createBuffer(width, height, pixelStride, dataValue, incrementValue);
+ final ByteBuffer mBuffer = byteBuffer;
+
+ {
+ int value = dataValue;
+ for (int pos = 0; pos < mBuffer.capacity();) {
+ mBuffer.put(pos, (byte) value);
+ pos += pixelStride;
+ if (incrementValue) {
+ value++;
+ }
+ }
+ }
@Override
public int getRowStride() {
- return width * pixelStride;
+ return rowStride;
}
@Override
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/TestImageUtil.java b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/TestImageUtil.java
index 0c689dc..59831b1 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/TestImageUtil.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/TestImageUtil.java
@@ -22,8 +22,11 @@
import static android.graphics.ImageFormat.RAW_SENSOR;
import static android.graphics.ImageFormat.YUV_420_888;
+import static androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_VGA;
+import static androidx.camera.testing.impl.ImageProxyUtil.YUV_FORMAT_PLANE_DATA_TYPE_I420;
import static androidx.camera.testing.impl.ImageProxyUtil.createRawImagePlanes;
import static androidx.camera.testing.impl.ImageProxyUtil.createYUV420ImagePlanes;
+import static androidx.camera.testing.impl.ImageProxyUtil.getDefaultYuvFormatPlaneDataType;
import static androidx.core.util.Preconditions.checkState;
import android.graphics.Bitmap;
@@ -38,6 +41,7 @@
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.camera.core.ImageInfo;
+import androidx.camera.core.ImageProxy;
import androidx.camera.core.internal.CameraCaptureResultImageInfo;
import androidx.camera.testing.fakes.FakeCameraCaptureResult;
import androidx.camera.testing.impl.fakes.FakeImageProxy;
@@ -96,19 +100,43 @@
*/
public static @NonNull FakeImageProxy createYuvFakeImageProxy(@NonNull ImageInfo imageInfo,
int width, int height) {
- return createYuvFakeImageProxy(imageInfo, width, height, false, false);
+ return createYuvFakeImageProxy(imageInfo, width, height, getDefaultYuvFormatPlaneDataType(
+ RESOLUTION_VGA.getWidth(), RESOLUTION_VGA.getHeight()), false);
}
/**
* Creates a [FakeImageProxy] with YUV format with the content of the image to match the
* value of {@link #createBitmap}.
*/
- @NonNull
- public static FakeImageProxy createYuvFakeImageProxy(@NonNull ImageInfo imageInfo,
- int width, int height, boolean flipUV, boolean insertRgbTestData) {
+ public static @NonNull FakeImageProxy createYuvFakeImageProxy(@NonNull ImageInfo imageInfo,
+ int width, int height,
+ @ImageProxyUtil.YuvFormatPlaneDataType int yuvFormatPlaneDataType,
+ boolean insertRgbTestData) {
+ return createYuvFakeImageProxy(
+ createYUV420ImagePlanes(width, height, yuvFormatPlaneDataType, false),
+ imageInfo, width, height, 1,
+ yuvFormatPlaneDataType == YUV_FORMAT_PLANE_DATA_TYPE_I420 ? 1 : 2,
+ insertRgbTestData);
+ }
+
+ /**
+ * Creates a [FakeImageProxy] with YUV format with the content of the image to match the
+ * value of {@link #createBitmap}.
+ */
+ public static @NonNull FakeImageProxy createYuvFakeImageProxy(@NonNull ImageInfo imageInfo,
+ int width, int height, int pixelStrideY, int pixelStrideUV, boolean flipUV,
+ boolean insertRgbTestData) {
+ return createYuvFakeImageProxy(
+ createYUV420ImagePlanes(width, height, pixelStrideY, pixelStrideUV, flipUV, false),
+ imageInfo, width, height, pixelStrideY, pixelStrideUV, insertRgbTestData);
+ }
+
+ private static @NonNull FakeImageProxy createYuvFakeImageProxy(
+ ImageProxy.@NonNull PlaneProxy[] planeProxies, @NonNull ImageInfo imageInfo, int width,
+ int height, int pixelStrideY, int pixelStrideUV, boolean insertRgbTestData) {
FakeImageProxy image = new FakeImageProxy(imageInfo);
image.setFormat(YUV_420_888);
- image.setPlanes(createYUV420ImagePlanes(width, height, 1, 1, flipUV, false));
+ image.setPlanes(planeProxies);
image.setWidth(width);
image.setHeight(height);
@@ -121,7 +149,8 @@
writeBitmapToYuvByteBuffers(rgbBitmap,
image.getPlanes()[0].getBuffer(),
image.getPlanes()[1].getBuffer(),
- image.getPlanes()[2].getBuffer());
+ image.getPlanes()[2].getBuffer(),
+ pixelStrideY, pixelStrideUV);
return image;
}
@@ -130,7 +159,9 @@
@NonNull Bitmap bitmap,
@NonNull ByteBuffer yByteBuffer,
@NonNull ByteBuffer uByteBuffer,
- @NonNull ByteBuffer vByteBuffer) {
+ @NonNull ByteBuffer vByteBuffer,
+ int pixelStrideY,
+ int pixelStrideUV) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int[] argb = new int[width * height];
@@ -150,10 +181,11 @@
int u = (int) (-0.169 * r - 0.331 * g + 0.5 * b + 128);
int v = (int) (0.5 * r - 0.419 * g - 0.081 * b + 128);
- yByteBuffer.put(yIndex++, (byte) y);
+ yByteBuffer.put(yIndex, (byte) y);
+ yIndex += pixelStrideY;
if (j % 2 == 0 && i % 2 == 0) {
- uByteBuffer.put(uvIndex, (byte) u);
- vByteBuffer.put(uvIndex, (byte) v);
+ uByteBuffer.put(uvIndex * pixelStrideUV, (byte) u);
+ vByteBuffer.put(uvIndex * pixelStrideUV, (byte) v);
uvIndex++;
}
}
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
index a93558e..befe1c0 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
@@ -342,6 +342,7 @@
videoCapture.setViewPortCropRect(cropRect)
checkAndBindUseCases(preview, videoCapture)
+ val calculatedCropRect = videoCapture.cropRect!!
// TODO(b/264936115): In stream sharing (VirtualCameraAdapter), children's ViewPortCropRect
// is ignored and override to the parent size, the cropRect is also rotated. Skip the test
@@ -352,7 +353,7 @@
val result = recordingSession.createRecording(recorder = recorder).recordAndVerify()
// Verify.
- val resolution = rectToSize(videoCapture.cropRect!!)
+ val resolution = rectToSize(calculatedCropRect)
verifyVideoResolution(
context,
result.file,
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageAnalysisTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageAnalysisTest.kt
index 26a295c..b566425 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageAnalysisTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageAnalysisTest.kt
@@ -23,9 +23,11 @@
import android.util.Size
import android.view.Surface
import androidx.annotation.GuardedBy
+import androidx.annotation.RequiresApi
import androidx.camera.camera2.Camera2Config
import androidx.camera.camera2.pipe.integration.CameraPipeConfig
import androidx.camera.core.AspectRatio
+import androidx.camera.core.Camera
import androidx.camera.core.CameraSelector
import androidx.camera.core.CameraXConfig
import androidx.camera.core.ExperimentalUseCaseApi
@@ -33,6 +35,7 @@
import androidx.camera.core.ImageAnalysis.BackpressureStrategy
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageProxy
+import androidx.camera.core.Preview
import androidx.camera.core.UseCaseGroup
import androidx.camera.core.ViewPort
import androidx.camera.core.impl.ImageOutputConfig
@@ -52,10 +55,12 @@
import androidx.camera.testing.impl.CameraUtil
import androidx.camera.testing.impl.CameraUtil.PreTestCameraIdList
import androidx.camera.testing.impl.LabTestRule
+import androidx.camera.testing.impl.SurfaceTextureProvider
import androidx.camera.testing.impl.WakelockEmptyActivityRule
import androidx.camera.testing.impl.fakes.FakeLifecycleOwner
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
@@ -568,6 +573,115 @@
triggerOnErrorAndVerifyNewImageReceived(imageAnalysis.sessionConfig)
}
+ @Test
+ @SdkSuppress(minSdkVersion = 23)
+ fun analyzerAnalyzesYUVImages_withRotationEnabledAndReusedToHaveDifferentSize() {
+ analyzerAnalyzesImages_withRotationEnabledAndReusedToHaveDifferentSize(
+ ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 23)
+ fun analyzerAnalyzesYUVNV21Images_withRotationEnabledAndReusedToHaveDifferentSize() {
+ analyzerAnalyzesImages_withRotationEnabledAndReusedToHaveDifferentSize(
+ ImageAnalysis.OUTPUT_IMAGE_FORMAT_NV21
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 23)
+ fun analyzerAnalyzesRGBAImages_withRotationEnabledAndReusedToHaveDifferentSize() {
+ analyzerAnalyzesImages_withRotationEnabledAndReusedToHaveDifferentSize(
+ ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888
+ )
+ }
+
+ @RequiresApi(23)
+ private fun analyzerAnalyzesImages_withRotationEnabledAndReusedToHaveDifferentSize(
+ outputImageFormat: Int
+ ) {
+ var camera: Camera? = null
+ val resolutionSelector =
+ ResolutionSelector.Builder()
+ .setResolutionStrategy(ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY)
+ .build()
+
+ val imageAnalysis =
+ ImageAnalysis.Builder()
+ .setResolutionSelector(resolutionSelector)
+ .setOutputImageFormat(outputImageFormat)
+ .setOutputImageRotationEnabled(true)
+ .build()
+ val preview = Preview.Builder().build()
+ val imageCapture = ImageCapture.Builder().build()
+
+ // Binds three UseCase to make imageAnalysis have a PREVIEW size resolution
+ runOnMainSync {
+ preview.surfaceProvider = SurfaceTextureProvider.createSurfaceTextureProvider()
+ camera =
+ cameraProvider.bindToLifecycle(
+ fakeLifecycleOwner,
+ DEFAULT_CAMERA_SELECTOR,
+ preview,
+ imageCapture,
+ imageAnalysis
+ )
+ }
+
+ val expectedOutputResolution1 = getRotatedResolution(camera!!, imageAnalysis)
+ setAnalyzerAndVerifyNewImageReceivedWithCorrectResolution(
+ imageAnalysis,
+ expectedOutputResolution1
+ )
+
+ // Unbinds all and rebind the imageAnalysis only to make imageAnalysis have a MAXIMUM size
+ // resolution
+ runOnMainSync {
+ // Clears analyzer and analysisResults first to make sure the old resolution frame data
+ // will not be kept to cause test failure
+ imageAnalysis.clearAnalyzer()
+ synchronized(analysisResultLock) { analysisResults.clear() }
+ cameraProvider.unbindAll()
+ cameraProvider.bindToLifecycle(
+ fakeLifecycleOwner,
+ DEFAULT_CAMERA_SELECTOR,
+ imageAnalysis
+ )
+ }
+
+ val expectedOutputResolution2 = getRotatedResolution(camera!!, imageAnalysis)
+ assumeTrue(expectedOutputResolution2 != expectedOutputResolution1)
+ setAnalyzerAndVerifyNewImageReceivedWithCorrectResolution(
+ imageAnalysis,
+ expectedOutputResolution2
+ )
+ }
+
+ private fun getRotatedResolution(camera: Camera, imageAnalysis: ImageAnalysis): Size {
+ val resolution = imageAnalysis.resolutionInfo!!.resolution
+ val rotationDegrees =
+ camera.cameraInfo.getSensorRotationDegrees(imageAnalysis.targetRotation)
+ return if (rotationDegrees % 180 == 0) {
+ resolution
+ } else {
+ Size(resolution.height, resolution.width)
+ }
+ }
+
+ private fun setAnalyzerAndVerifyNewImageReceivedWithCorrectResolution(
+ imageAnalysis: ImageAnalysis,
+ expectedResolution: Size
+ ) {
+ analysisResultsSemaphore = Semaphore(0)
+ imageAnalysis.setAnalyzer(CameraXExecutors.newHandlerExecutor(handler), analyzer)
+ analysisResultsSemaphore.tryAcquire(5, TimeUnit.SECONDS)
+ synchronized(analysisResultLock) {
+ assertThat(analysisResults).isNotEmpty()
+ assertThat(analysisResults.elementAt(0).resolution).isEqualTo(expectedResolution)
+ }
+ }
+
private fun triggerOnErrorAndVerifyNewImageReceived(sessionConfig: SessionConfig) {
// Forces invoke the onError callback
runOnMainSync {
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
index 9ac3d18..233f4f8 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
@@ -299,31 +299,6 @@
}
@Test
- fun canCaptureMultipleImagesWithZsl() = runBlocking {
- val useCase =
- ImageCapture.Builder()
- .setCaptureMode(ImageCapture.CAPTURE_MODE_ZERO_SHUTTER_LAG)
- .build()
- var camera: Camera
- withContext(Dispatchers.Main) {
- camera = cameraProvider.bindToLifecycle(fakeLifecycleOwner, BACK_SELECTOR, useCase)
- }
-
- if (camera.cameraInfo.isZslSupported) {
- val numImages = 5
- val callback = FakeOnImageCapturedCallback(captureCount = numImages)
- for (i in 0 until numImages) {
- useCase.takePicture(mainExecutor, callback)
- }
-
- callback.awaitCapturesAndAssert(
- timeout = CAPTURE_TIMEOUT.times(numImages),
- capturedImagesCount = numImages
- )
- }
- }
-
- @Test
fun canCaptureImageWithFlashModeOn() {
canTakeImages(defaultBuilder.setFlashMode(ImageCapture.FLASH_MODE_ON))
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/UseCaseCombinationTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/UseCaseCombinationTest.kt
index 518bae0..45ab60c 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/UseCaseCombinationTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/UseCaseCombinationTest.kt
@@ -17,7 +17,6 @@
import android.Manifest
import android.content.Context
-import android.util.Log
import androidx.camera.camera2.Camera2Config
import androidx.camera.camera2.pipe.integration.CameraPipeConfig
import androidx.camera.core.Camera
@@ -470,28 +469,6 @@
imageCapture.waitForCapturing()
}
- @Test
- fun previewImageCaptureZSL() {
- // Arrange.
- assumeTrue(cameraInfo.isZslSupported) // Only test when ZSL is supported
-
- val imageCaptureZSL =
- ImageCapture.Builder()
- .apply { setCaptureMode(ImageCapture.CAPTURE_MODE_ZERO_SHUTTER_LAG) }
- .build()
- assumeTrue(camera.isUseCasesCombinationSupported(preview, imageCaptureZSL))
- bindUseCases(preview, imageCaptureZSL)
-
- // Capture images with ZSL and verify each capture.
- for (i in 10 downTo 0) {
- previewMonitor.waitForStream()
- imageCaptureZSL.waitForCapturing()
- Log.d("UseCaseCombinationTest", "Test ZSL capture round: $i")
- // Verifies the preview is still outputting after capture
- previewMonitor.waitForStream()
- }
- }
-
// Possible for QR code scanning use case.
@Test
fun sequentialBindPreviewAndImageAnalysis() {
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ZslDeviceTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ZslDeviceTest.kt
new file mode 100644
index 0000000..ca46142
--- /dev/null
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ZslDeviceTest.kt
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.integration.core
+
+import android.content.Context
+import android.util.Log
+import androidx.camera.camera2.Camera2Config
+import androidx.camera.camera2.pipe.integration.CameraPipeConfig
+import androidx.camera.core.Camera
+import androidx.camera.core.CameraInfo
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.CameraXConfig
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.ImageCaptureException
+import androidx.camera.core.ImageProxy
+import androidx.camera.core.Preview
+import androidx.camera.core.UseCase
+import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.testing.impl.CameraPipeConfigTestRule
+import androidx.camera.testing.impl.CameraUtil
+import androidx.camera.testing.impl.SurfaceTextureProvider.createAutoDrainingSurfaceTextureProvider
+import androidx.camera.testing.impl.WakelockEmptyActivityRule
+import androidx.camera.testing.impl.fakes.FakeLifecycleOwner
+import androidx.camera.testing.impl.fakes.FakeOnImageCapturedCallback
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+private const val TAG = "ZslDeviceTest"
+
+/** Tests ZSL capture on real devices. */
+@LargeTest
+@RunWith(Parameterized::class)
+class ZslDeviceTest(
+ private val implName: String,
+ private var cameraSelector: CameraSelector,
+ private val cameraConfig: CameraXConfig,
+) {
+
+ @get:Rule
+ val cameraPipeConfigTestRule =
+ CameraPipeConfigTestRule(
+ active = implName == CameraPipeConfig::class.simpleName,
+ )
+
+ @get:Rule
+ val cameraRule =
+ CameraUtil.grantCameraPermissionAndPreTestAndPostTest(
+ CameraUtil.PreTestCameraIdList(cameraConfig)
+ )
+
+ @get:Rule val wakelockEmptyActivityRule = WakelockEmptyActivityRule()
+
+ companion object {
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun data() =
+ listOf(
+ arrayOf(
+ "back+" + Camera2Config::class.simpleName,
+ CameraSelector.DEFAULT_BACK_CAMERA,
+ Camera2Config.defaultConfig(),
+ ),
+ arrayOf(
+ "front+" + Camera2Config::class.simpleName,
+ CameraSelector.DEFAULT_FRONT_CAMERA,
+ Camera2Config.defaultConfig(),
+ ),
+ arrayOf(
+ "back+" + CameraPipeConfig::class.simpleName,
+ CameraSelector.DEFAULT_BACK_CAMERA,
+ CameraPipeConfig.defaultConfig(),
+ ),
+ arrayOf(
+ "front+" + CameraPipeConfig::class.simpleName,
+ CameraSelector.DEFAULT_FRONT_CAMERA,
+ CameraPipeConfig.defaultConfig(),
+ ),
+ )
+ }
+
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private val captureTimeout = 15.seconds
+ private lateinit var cameraProvider: ProcessCameraProvider
+ private lateinit var fakeLifecycleOwner: FakeLifecycleOwner
+ private lateinit var camera: Camera
+ private lateinit var previewMonitor: PreviewMonitor
+ private lateinit var preview: Preview
+ private lateinit var imageCaptureZsl: ImageCapture
+ private lateinit var cameraInfo: CameraInfo
+
+ @Before
+ fun setup() {
+ assumeTrue(CameraUtil.hasCameraWithLensFacing(cameraSelector.lensFacing!!))
+ ProcessCameraProvider.configureInstance(cameraConfig)
+ cameraProvider = ProcessCameraProvider.getInstance(context)[10, TimeUnit.SECONDS]
+
+ instrumentation.runOnMainSync {
+ fakeLifecycleOwner = FakeLifecycleOwner()
+ fakeLifecycleOwner.startAndResume()
+
+ camera = cameraProvider.bindToLifecycle(fakeLifecycleOwner, cameraSelector)
+ cameraInfo = camera.cameraInfo
+ }
+
+ previewMonitor = PreviewMonitor()
+ preview = initPreview(previewMonitor)
+ imageCaptureZsl = initImageCaptureZsl()
+ }
+
+ @After
+ fun tearDown() {
+ if (::cameraProvider.isInitialized) {
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
+ }
+ }
+
+ @Test
+ fun previewImageCaptureZsl() {
+ // Arrange.
+ assumeTrue(camera.isUseCasesCombinationSupported(preview, imageCaptureZsl))
+ bindUseCases(preview, imageCaptureZsl)
+
+ // Capture images with ZSL and verify each capture.
+ for (i in 0 until 10) {
+ previewMonitor.waitForStream()
+ imageCaptureZsl.waitForCapturing()
+ Log.d(TAG, "Test ZSL capture round: $i")
+ // Assert. Verifies the preview is still outputting after capture
+ previewMonitor.waitForStream()
+ }
+ }
+
+ @Test
+ fun imageCaptureZsl() = runBlocking {
+ // Arrange.
+ bindUseCases(imageCaptureZsl)
+ val numImages = 10
+ val callback = FakeOnImageCapturedCallback(captureCount = numImages)
+
+ // Act. Capture images with ZSL.
+ for (i in 0 until numImages) {
+ imageCaptureZsl.takePicture(Dispatchers.IO.asExecutor(), callback)
+ }
+
+ // Assert. Verify captures.
+ callback.awaitCapturesAndAssert(
+ timeout = captureTimeout.times(numImages),
+ capturedImagesCount = numImages
+ )
+ }
+
+ @Test
+ fun bindUnbindImageCaptureZsl() = runBlocking {
+ val imageCapture = ImageCapture.Builder().build()
+ val numImages = 3
+
+ for (i in 0 until 5) {
+ Log.d(TAG, "Loop $i ZSL capture")
+ bindUseCases(imageCaptureZsl)
+ imageCaptureZsl.verifyCaptures(numImages) // Act & Assert. Verify ZSL captures.
+ unbindUseCases(imageCaptureZsl)
+ Log.d(TAG, "Loop $i ZSL capture done")
+
+ Log.d(TAG, "Loop $i regular capture")
+ bindUseCases(imageCapture)
+ imageCapture.verifyCaptures(numImages) // Act & Assert. Verify regular captures.
+ unbindUseCases(imageCapture)
+ Log.d(TAG, "Loop $i regular capture done")
+ }
+ }
+
+ private suspend fun ImageCapture.verifyCaptures(numImages: Int) {
+ val callback = FakeOnImageCapturedCallback(captureCount = numImages)
+
+ for (i in 0 until numImages) {
+ Log.d(TAG, "Test ZSL capture round: $i")
+ takePicture(Dispatchers.IO.asExecutor(), callback)
+ }
+
+ // Assert. Verify captures.
+ callback.awaitCapturesAndAssert(
+ timeout = captureTimeout.times(numImages),
+ capturedImagesCount = numImages
+ )
+ }
+
+ private fun initPreview(monitor: PreviewMonitor, setSurfaceProvider: Boolean = true): Preview {
+ return Preview.Builder().setTargetName("Preview").build().apply {
+ if (setSurfaceProvider) {
+ instrumentation.runOnMainSync { surfaceProvider = monitor.getSurfaceProvider() }
+ }
+ }
+ }
+
+ private fun initImageCaptureZsl(): ImageCapture {
+ return ImageCapture.Builder()
+ .setCaptureMode(ImageCapture.CAPTURE_MODE_ZERO_SHUTTER_LAG)
+ .build()
+ }
+
+ private fun ImageCapture.waitForCapturing(timeMillis: Long = 10000) {
+ val callback =
+ object : ImageCapture.OnImageCapturedCallback() {
+ val latch = CountDownLatch(1)
+ val errors = mutableListOf<ImageCaptureException>()
+
+ override fun onCaptureSuccess(image: ImageProxy) {
+ image.close()
+ latch.countDown()
+ }
+
+ override fun onError(exception: ImageCaptureException) {
+ errors.add(exception)
+ latch.countDown()
+ }
+ }
+
+ takePicture(Dispatchers.IO.asExecutor(), callback)
+
+ assertThat(
+ callback.latch.await(timeMillis, TimeUnit.MILLISECONDS) && callback.errors.isEmpty()
+ )
+ .isTrue()
+ }
+
+ class PreviewMonitor {
+ private var countDown: CountDownLatch? = null
+ private val surfaceProvider = createAutoDrainingSurfaceTextureProvider {
+ countDown?.countDown()
+ }
+
+ fun getSurfaceProvider(): Preview.SurfaceProvider = surfaceProvider
+
+ fun waitForStream(count: Int = 10, timeMillis: Long = TimeUnit.SECONDS.toMillis(8)) {
+ Truth.assertWithMessage("Preview doesn't start")
+ .that(
+ synchronized(this) {
+ countDown = CountDownLatch(count)
+ countDown
+ }!!
+ .await(timeMillis, TimeUnit.MILLISECONDS)
+ )
+ .isTrue()
+ }
+ }
+
+ private fun bindUseCases(vararg useCases: UseCase) {
+ instrumentation.runOnMainSync {
+ cameraProvider.bindToLifecycle(fakeLifecycleOwner, cameraSelector, *useCases)
+ }
+ }
+
+ private fun unbindUseCases(vararg useCases: UseCase) {
+ instrumentation.runOnMainSync { cameraProvider.unbind(*useCases) }
+ }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-fa/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-fa/strings.xml
index e15e53f..a1390b7 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-fa/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-fa/strings.xml
@@ -17,7 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="back_caps_action_title" msgid="6392829425035919422">"برگشت"</string>
+ <string name="back_caps_action_title" msgid="6392829425035919422">"برگشتن"</string>
<string name="home_caps_action_title" msgid="4853167242566949502">"خانه"</string>
<string name="exit_action_title" msgid="9086586388884500731">"خروج"</string>
<string name="refresh_action_title" msgid="3674260822403151377">"بازآوری"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-iw/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-iw/strings.xml
index 1a89e7e9..89182a6 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-iw/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-iw/strings.xml
@@ -51,9 +51,9 @@
<string name="zoomed_in_toast_msg" msgid="8915301497303842649">"הגדלת התצוגה"</string>
<string name="zoomed_out_toast_msg" msgid="6260981223227212493">"הקטנת התצוגה"</string>
<string name="triggered_toast_msg" msgid="3396166539208366382">"הופעל"</string>
- <string name="primary_toast_msg" msgid="7153771322662005447">"לחצת על הלחצן הראשי"</string>
- <string name="search_toast_msg" msgid="7826530065407699347">"לחצת על לחצן החיפוש"</string>
- <string name="options_toast_msg" msgid="2146223786877557730">"לחצת על לחצן האפשרויות"</string>
+ <string name="primary_toast_msg" msgid="7153771322662005447">"לחצת על הכפתור הראשי"</string>
+ <string name="search_toast_msg" msgid="7826530065407699347">"לחצת על כפתור החיפוש"</string>
+ <string name="options_toast_msg" msgid="2146223786877557730">"לחצת על כפתור האפשרויות"</string>
<string name="favorite_toast_msg" msgid="522064494016370117">"הוספת למועדפים!"</string>
<string name="not_favorite_toast_msg" msgid="6831181108681007428">"לא הוספת למועדפים!"</string>
<string name="nav_requested_toast_msg" msgid="6696525973145493908">"ביקשת ניווט"</string>
@@ -66,12 +66,12 @@
<string name="settings_toast_msg" msgid="7697794473002342727">"לחצת על הגדרות"</string>
<string name="parked_toast_msg" msgid="2532422265890824446">"פעולת חניה"</string>
<string name="more_toast_msg" msgid="5938288138225509885">"לחצת על \'עוד\'"</string>
- <string name="commute_toast_msg" msgid="4112684360647638688">"לחצת על לחצן הנסיעות היומיות"</string>
+ <string name="commute_toast_msg" msgid="4112684360647638688">"לחצת על כפתור הנסיעות היומיות"</string>
<string name="grant_location_permission_toast_msg" msgid="268046297444808010">"יש לתת הרשאת מיקום כדי לראות את המיקום הנוכחי"</string>
<string name="sign_in_with_google_toast_msg" msgid="5720947549233124775">"הכניסה באמצעות Google מתחילה כאן"</string>
<string name="changes_selection_to_index_toast_msg_prefix" msgid="957766225794389167">"שינית את הבחירה לאינדקס"</string>
- <string name="yes_action_toast_msg" msgid="6216215197177241247">"הלחצן \'כן\' נלחץ!"</string>
- <string name="no_action_toast_msg" msgid="6165492423831023809">"הלחצן \'לא\' נלחץ!"</string>
+ <string name="yes_action_toast_msg" msgid="6216215197177241247">"הכפתור \'כן\' נלחץ!"</string>
+ <string name="no_action_toast_msg" msgid="6165492423831023809">"הכפתור \'לא\' נלחץ!"</string>
<string name="alert_timeout_toast_msg" msgid="5568380708832805374">"פג הזמן של ההתראה!"</string>
<string name="first_row_title" msgid="219428344573165351">"שורה עם תמונה גדולה וטקסט ארוך טקסט ארוך טקסט ארוך טקסט ארוך טקסט ארוך"</string>
<string name="first_row_text" msgid="3887390298628338716">"טקסט טקסט טקסט"</string>
@@ -253,7 +253,7 @@
<string name="pin_sign_in_instruction" msgid="2288691296234360441">"עליך להזין את קוד האימות בטלפון"</string>
<string name="qr_code_sign_in_title" msgid="8137070561006464518">"יש לסרוק את קוד ה-QR כדי להיכנס"</string>
<string name="sign_in_with_google_title" msgid="8043752000786977249">"כניסה באמצעות חשבון Google"</string>
- <string name="provider_sign_in_instruction" msgid="7586815688292506743">"אפשר להשתמש בלחצן הזה כדי להשלים את הכניסה לחשבון Google שלך"</string>
+ <string name="provider_sign_in_instruction" msgid="7586815688292506743">"אפשר להשתמש בכפתור הזה כדי להשלים את הכניסה לחשבון Google שלך"</string>
<string name="sign_in_complete_text" msgid="8423984266325680606">"נכנסת לחשבון!"</string>
<string name="sign_in_complete_title" msgid="8919868148773983428">"הכניסה הושלמה"</string>
<string name="sign_in_template_demo_title" msgid="6052035424941410249">"הדגמה של תבנית הכניסה לחשבון"</string>
@@ -288,14 +288,14 @@
<string name="rows_demo_title" msgid="3198566660454251007">"הדגמת שורות"</string>
<string name="text_icons_demo_title" msgid="8732943920672143201">"הדגמות של טקסטים וסמלים"</string>
<string name="row_text_icons_demo_title" msgid="135167694047524905">"הדגמה של שורות עם טקסטים וסמלים"</string>
- <string name="radio_button_list_demo_title" msgid="9082264324855338774">"הדגמה של רשימות של לחצני בחירה"</string>
+ <string name="radio_button_list_demo_title" msgid="9082264324855338774">"הדגמה של רשימות של כפתורי בחירה"</string>
<string name="selectable_lists_demo_title" msgid="5492658731113129386">"הדגמה של הרשימות שאפשר לבחור"</string>
<string name="option_1_title" msgid="7221252541651471199">"אפשרות 1"</string>
<string name="option_2_title" msgid="1905146448697963818">"אפשרות 2"</string>
<string name="option_3_title" msgid="6319268250436119258">"אפשרות 3"</string>
- <string name="option_row_radio_title" msgid="5978617101267398181">"שורה עם לחצן בחירה"</string>
- <string name="option_row_radio_icon_title" msgid="3304229002524317977">"שורה עם לחצן בחירה וסמל"</string>
- <string name="option_row_radio_icon_colored_text_title" msgid="947641896184637026">"שורה עם לחצן בחירה, סמל, וטקסט צבוע"</string>
+ <string name="option_row_radio_title" msgid="5978617101267398181">"שורה עם כפתור בחירה"</string>
+ <string name="option_row_radio_icon_title" msgid="3304229002524317977">"שורה עם כפתור בחירה וסמל"</string>
+ <string name="option_row_radio_icon_colored_text_title" msgid="947641896184637026">"שורה עם כפתור בחירה, סמל, וטקסט צבוע"</string>
<string name="some_additional_text" msgid="4009872495806318260">"עוד טקסט"</string>
<string name="sample_additional_list" msgid="5085372891301576306">"דוגמה של רשימה שאפשר לבחור"</string>
<string name="task_restriction_demo_title" msgid="2212084350718766941">"הדגמה של הגבלת המשימות"</string>
diff --git a/compose/animation/animation-graphics/OWNERS b/compose/animation/animation-graphics/OWNERS
index b3a11a3..57d0900 100644
--- a/compose/animation/animation-graphics/OWNERS
+++ b/compose/animation/animation-graphics/OWNERS
@@ -1,4 +1,2 @@
# Bug component: 633518
[email protected]
[email protected]
[email protected]
diff --git a/compose/foundation/foundation-layout/OWNERS b/compose/foundation/foundation-layout/OWNERS
index 9b30ce2..ef044fd 100644
--- a/compose/foundation/foundation-layout/OWNERS
+++ b/compose/foundation/foundation-layout/OWNERS
@@ -2,6 +2,5 @@
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
diff --git a/compose/foundation/foundation/OWNERS b/compose/foundation/foundation/OWNERS
index a0f35b5..f70ccb1 100644
--- a/compose/foundation/foundation/OWNERS
+++ b/compose/foundation/foundation/OWNERS
@@ -1,6 +1,5 @@
# Bug component: 856887
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/EllipsizeRedrawDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/EllipsizeRedrawDemo.kt
new file mode 100644
index 0000000..a83099b
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/EllipsizeRedrawDemo.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2024 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.foundation.demos.text
+
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.rememberInfiniteTransition
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.key
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.LineHeightStyle
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+@Preview
+@Composable
+fun EllipsizeRedrawDemo() {
+ val transition = rememberInfiniteTransition("padding")
+ val padding = transition.animateFloat(0f, 50f, infiniteRepeatable(tween(10000)))
+ Box(
+ modifier =
+ Modifier.padding(top = padding.value.dp)
+ .fillMaxSize()
+ .border(1.dp, Color.Blue)
+ .padding(24.dp)
+ .border(1.dp, Color.Cyan),
+ contentAlignment = Alignment.Center
+ ) {
+ Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
+ BasicText("If b/389707025 fixed, the following should display with no animation")
+
+ // reset everything at 49
+ key(padding.value.toInt() / 49) {
+ BasicText(
+ text = "I will definitely fill the screen!".repeat(10),
+ modifier = Modifier.border(1.dp, Color.Red),
+ style =
+ TextStyle(
+ textAlign = TextAlign.Center,
+ letterSpacing = 1.sp,
+ lineHeight = 24.sp,
+ lineHeightStyle =
+ LineHeightStyle(
+ alignment = LineHeightStyle.Alignment.Center,
+ trim = LineHeightStyle.Trim.None,
+ mode = LineHeightStyle.Mode.Fixed
+ ),
+ ),
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ color = { Color.Black }
+ )
+ BasicText(
+ text = "I will definitely fill the screen!".repeat(10),
+ modifier = Modifier.border(1.dp, Color.Red),
+ style =
+ TextStyle(
+ textAlign = TextAlign.Center,
+ letterSpacing = 1.sp,
+ lineHeight = 24.sp,
+ lineHeightStyle =
+ LineHeightStyle(
+ alignment = LineHeightStyle.Alignment.Center,
+ trim = LineHeightStyle.Trim.None,
+ mode = LineHeightStyle.Mode.Fixed
+ ),
+ ),
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 3,
+ color = { Color.Black }
+ )
+ }
+ }
+ }
+}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
index fe61539..22d952e2 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
@@ -92,6 +92,9 @@
ComposableDemo("Min/max lines") { BasicTextMinMaxLinesDemo() },
ComposableDemo("Get last character after clip") {
LastClippedCharacterDemo()
+ },
+ ComposableDemo("Ellipses plays well with redraw") {
+ EllipsizeRedrawDemo()
}
)
),
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/draganddrop/DragAndDropSourceTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/draganddrop/DragAndDropSourceTest.kt
new file mode 100644
index 0000000..6ae7859
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/draganddrop/DragAndDropSourceTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2024 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.foundation.draganddrop
+
+import android.os.Build
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertPixels
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Constraints
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@MediumTest
+class DragAndDropSourceTest {
+ @get:Rule val rule = createComposeRule()
+
+ /** Regression test for b/379682458 */
+ @Test
+ fun dragAndDropSource_doesNotPreventChildInvalidations() {
+ var moveBox by mutableStateOf(false)
+ val tag = "source"
+ rule.setContent {
+ with(LocalDensity.current) {
+ Box(
+ Modifier.size(200.toDp())
+ .testTag(tag)
+ .dragAndDropSource { _ -> null }
+ .clip(RectangleShape)
+ .layout { measurable, constraints ->
+ val placeable =
+ measurable.measure(
+ constraints.copy(maxWidth = Constraints.Infinity)
+ )
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ placeable.place(x = if (moveBox) -200 else 0, y = 0)
+ }
+ }
+ .width(400.toDp())
+ .drawBehind {
+ val halfWidth = size.width / 2f
+ // Fill the full height, and half the width, since this will
+ // be translated by the layout modifier above
+ val rectSize = Size(halfWidth, size.height)
+ drawRect(Color.Blue, size = rectSize)
+ drawRect(Color.Red, topLeft = Offset(halfWidth, 0f), size = rectSize)
+ }
+ )
+ }
+ }
+
+ rule.onNodeWithTag(tag).captureToImage().assertPixels { Color.Blue }
+
+ // Make the layout move the child so that the red box is now visible
+ rule.runOnIdle { moveBox = true }
+
+ rule.onNodeWithTag(tag).captureToImage().assertPixels { Color.Red }
+ }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/BasicTextScreenshotTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/BasicTextScreenshotTest.kt
index 855071a..bdc4452 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/BasicTextScreenshotTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/BasicTextScreenshotTest.kt
@@ -18,8 +18,17 @@
import android.os.Build
import androidx.compose.foundation.GOLDEN_FOUNDATION
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.junit4.createComposeRule
@@ -30,6 +39,10 @@
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.style.LineHeightStyle
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
@@ -50,6 +63,50 @@
private val textTag = "text"
@Test
+ fun basicTextEllipsisCentered_leadingMarginCorrect_doesntMarch_b389707025() {
+ val padding = mutableStateOf(0.dp)
+ rule.setContent {
+ Box(
+ modifier =
+ Modifier.padding(top = padding.value).fillMaxSize().border(1.dp, Color.Blue),
+ contentAlignment = Alignment.Center
+ ) {
+ Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
+ BasicText(
+ text = "I will definitely fill the screen!".repeat(10),
+ modifier = Modifier.border(1.dp, Color.Red).testTag(textTag),
+ style =
+ TextStyle(
+ textAlign = TextAlign.Center,
+ letterSpacing = 1.sp,
+ lineHeight = 24.sp,
+ lineHeightStyle =
+ LineHeightStyle(
+ alignment = LineHeightStyle.Alignment.Center,
+ trim = LineHeightStyle.Trim.None,
+ mode = LineHeightStyle.Mode.Fixed
+ ),
+ ),
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ color = { Color.Black }
+ )
+ }
+ }
+ }
+
+ repeat(2) {
+ // these repaints cause the reproduction of b/389707025
+ rule.waitForIdle()
+ padding.value = padding.value + 1.dp
+ }
+ rule
+ .onNodeWithTag(textTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, "leadingMarginForEllipsis")
+ }
+
+ @Test
fun multiStyleText_setFontWeight() {
rule.setContent {
BasicText(
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/MultiParagraphLayoutCacheTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/MultiParagraphLayoutCacheTest.kt
index 5d91cd8..fb85326 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/MultiParagraphLayoutCacheTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/MultiParagraphLayoutCacheTest.kt
@@ -37,6 +37,7 @@
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import kotlin.math.roundToInt
import org.junit.Test
import org.junit.runner.RunWith
@@ -621,6 +622,84 @@
// different API levels. Either 51 or 52. Using isAtMost to anticipate future permutations.
}
+ // Regression test for b/376834366
+ @Test
+ fun autoSize_maxLines_1_fittingConstraints_overflowBiasedWindow_doesntOverflow() {
+ val constraints = Constraints(minWidth = 0, maxWidth = 100, minHeight = 0, maxHeight = 100)
+ val text = AnnotatedString("aaaaaaaaaaaa")
+ // Assuming uniform character sizing (like in our test), this is the character size that
+ // technically fits the constraints for the given text.
+ val optimalCharacterSizeForText =
+ with(density) { (constraints.maxWidth / text.length.toFloat()).toSp() }
+
+ // In this test, we want a left-hand biased search window so that the first auto size
+ // layout should not overflow. With a left-hand biased window, the midpoint of the
+ // search will start further on the left and then move right, highlighting any potential
+ // caching issues.
+ val min = optimalCharacterSizeForText * 0.3
+ val max = optimalCharacterSizeForText * 1.3
+ val autoSize = TextAutoSize.StepBased(minFontSize = min, maxFontSize = max)
+
+ val autoSizeLayoutCache = createLayoutCache(text, autoSize, maxLines = 1)
+ autoSizeLayoutCache.layoutWithConstraints(constraints, LayoutDirection.Ltr)
+ val autoSizeLayoutResult = autoSizeLayoutCache.textLayoutResult
+ assertWithMessage("Layout Result (Auto Size) overflow")
+ .that(autoSizeLayoutResult.hasVisualOverflow)
+ .isFalse()
+ assertWithMessage("Font size used for auto size")
+ .that(autoSizeLayoutResult.layoutInput.style.fontSize.value)
+ .isAtMost(optimalCharacterSizeForText.value * 1.1f) // Use a slight tolerance
+ assertThat(autoSizeLayoutResult.layoutInput.constraints).isEqualTo(constraints)
+
+ val layoutCache =
+ createLayoutCache(text, style = autoSizeLayoutResult.layoutInput.style, maxLines = 1)
+ layoutCache.layoutWithConstraints(constraints, LayoutDirection.Ltr)
+ val layoutResult = layoutCache.textLayoutResult
+ assertWithMessage("Layout Result (No Auto Size) overflow")
+ .that(autoSizeLayoutResult.hasVisualOverflow)
+ .isFalse()
+ assertThat(layoutResult.layoutInput.constraints).isEqualTo(constraints)
+ }
+
+ // Regression test for b/376834366
+ @Test
+ fun autoSize_maxLines_1_fittingConstraints_underflowBiasedWindow_doesntOverflow() {
+ val constraints = Constraints(minWidth = 0, maxWidth = 100, minHeight = 0, maxHeight = 100)
+ val text = AnnotatedString("aaaaaaaaaaaa")
+ // Assuming uniform character sizing (like in our test), this is the character size that
+ // that technically fits the constraints for the given text.
+ val optimalCharacterSizeForText =
+ with(density) { (constraints.maxWidth / text.length.toFloat()).toSp() }
+
+ // In this test, we want a right-hand biased search window so that the first auto size
+ // layout should overflow. With a right-hand biased window, the midpoint of the
+ // search will start further on the right and then move left, highlighting any potential
+ // caching issues.
+ val min = optimalCharacterSizeForText * 0.9
+ val max = optimalCharacterSizeForText * 2
+ val autoSize = TextAutoSize.StepBased(minFontSize = min, maxFontSize = max)
+
+ val autoSizeLayoutCache = createLayoutCache(text, autoSize, maxLines = 1)
+ autoSizeLayoutCache.layoutWithConstraints(constraints, LayoutDirection.Ltr)
+ val autoSizeLayoutResult = autoSizeLayoutCache.textLayoutResult
+ assertWithMessage("Layout Result (Auto Size) overflow")
+ .that(autoSizeLayoutResult.hasVisualOverflow)
+ .isFalse()
+ assertWithMessage("Font size used for auto size")
+ .that(autoSizeLayoutResult.layoutInput.style.fontSize.value)
+ .isAtMost(optimalCharacterSizeForText.value * 1.1f) // Use a slight tolerance
+ assertThat(autoSizeLayoutResult.layoutInput.constraints).isEqualTo(constraints)
+
+ val layoutCache =
+ createLayoutCache(text, style = autoSizeLayoutResult.layoutInput.style, maxLines = 1)
+ layoutCache.layoutWithConstraints(constraints, LayoutDirection.Ltr)
+ val layoutResult = layoutCache.textLayoutResult
+ assertWithMessage("Layout Result (No Auto Size) overflow")
+ .that(autoSizeLayoutResult.hasVisualOverflow)
+ .isFalse()
+ assertThat(layoutResult.layoutInput.constraints).isEqualTo(constraints)
+ }
+
@Test
fun maxHeight_hasSameHeight_asParagraph() {
val text = buildAnnotatedString {
@@ -688,4 +767,20 @@
placeholders = null,
autoSize = autoSize
)
+
+ private fun createLayoutCache(
+ text: AnnotatedString,
+ autoSize: TextAutoSize? = null,
+ style: TextStyle = TextStyle(fontFamily = fontFamily),
+ maxLines: Int = Int.MAX_VALUE
+ ): MultiParagraphLayoutCache {
+ return MultiParagraphLayoutCache(
+ text = text,
+ style = style,
+ fontFamilyResolver = fontFamilyResolver,
+ autoSize = autoSize,
+ maxLines = maxLines
+ )
+ .also { it.density = density }
+ }
}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/draganddrop/AndroidDragAndDropSource.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/draganddrop/AndroidDragAndDropSource.android.kt
index 4981f7c..d42eb0e 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/draganddrop/AndroidDragAndDropSource.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/draganddrop/AndroidDragAndDropSource.android.kt
@@ -19,15 +19,13 @@
package androidx.compose.foundation.draganddrop
-import android.graphics.Picture
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.runtime.Immutable
import androidx.compose.ui.draw.CacheDrawScope
import androidx.compose.ui.draw.DrawResult
import androidx.compose.ui.graphics.drawscope.DrawScope
-import androidx.compose.ui.graphics.drawscope.draw
-import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
-import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.graphics.layer.GraphicsLayer
+import androidx.compose.ui.graphics.layer.drawLayer
@Immutable
internal actual object DragAndDropSourceDefaults {
@@ -37,39 +35,22 @@
}
internal actual class CacheDrawScopeDragShadowCallback {
- private var cachedPicture: Picture? = null
+ private var graphicsLayer: GraphicsLayer? = null
actual fun drawDragShadow(drawScope: DrawScope) =
with(drawScope) {
- when (val picture = cachedPicture) {
+ when (val layer = graphicsLayer) {
null ->
throw IllegalArgumentException(
- "No cached drag shadow. Check if Modifier.cacheDragShadow(painter) was called."
+ "No cached drag shadow. Check if the drag source node was rendered first"
)
- else -> drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) }
+ else -> drawLayer(layer)
}
}
actual fun cachePicture(scope: CacheDrawScope): DrawResult =
with(scope) {
- val picture = Picture()
- cachedPicture = picture
- val width = this.size.width.toInt()
- val height = this.size.height.toInt()
- onDrawWithContent {
- val pictureCanvas =
- androidx.compose.ui.graphics.Canvas(picture.beginRecording(width, height))
- draw(
- density = this,
- layoutDirection = this.layoutDirection,
- canvas = pictureCanvas,
- size = this.size
- ) {
- [email protected]()
- }
- picture.endRecording()
-
- drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) }
- }
+ graphicsLayer = scope.obtainGraphicsLayer().apply { record { drawContent() } }
+ onDrawWithContent { drawLayer(graphicsLayer!!) }
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/PagerSnapLayoutInfoProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/PagerSnapLayoutInfoProvider.kt
index f1b6803..8283eb8 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/PagerSnapLayoutInfoProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/PagerSnapLayoutInfoProvider.kt
@@ -44,7 +44,8 @@
override fun calculateSnapOffset(velocity: Float): Float {
val snapPosition = pagerState.layoutInfo.snapPosition
- val (lowerBoundOffset, upperBoundOffset) = searchForSnappingBounds(snapPosition)
+ val (lowerBoundOffset, upperBoundOffset) =
+ searchForSnappingBounds(snapPosition, velocity)
val finalDistance =
calculateFinalSnappingBound(velocity, lowerBoundOffset, upperBoundOffset)
@@ -134,7 +135,10 @@
.also { debugLog { "Fling Approach Offset=$it" } }
}
- private fun searchForSnappingBounds(snapPosition: SnapPosition): Pair<Float, Float> {
+ private fun searchForSnappingBounds(
+ snapPosition: SnapPosition,
+ velocity: Float
+ ): Pair<Float, Float> {
debugLog { "Calculating Snapping Bounds" }
var lowerBoundOffset = Float.NEGATIVE_INFINITY
var upperBoundOffset = Float.POSITIVE_INFINITY
@@ -174,13 +178,11 @@
// Don't move if we are at the bounds
- val isDragging = pagerState.dragGestureDelta() != 0f
-
if (!pagerState.canScrollForward) {
upperBoundOffset = 0.0f
// If we can not scroll forward but are trying to move towards the bound, set both
// bounds to 0 as we don't want to move
- if (isDragging && pagerState.isScrollingForward()) {
+ if (pagerState.isScrollingForward(velocity)) {
lowerBoundOffset = 0.0f
}
}
@@ -189,7 +191,7 @@
lowerBoundOffset = 0.0f
// If we can not scroll backward but are trying to move towards the bound, set both
// bounds to 0 as we don't want to move
- if (isDragging && !pagerState.isScrollingForward()) {
+ if (!pagerState.isScrollingForward(velocity)) {
upperBoundOffset = 0.0f
}
}
@@ -198,12 +200,15 @@
}
}
-private fun PagerState.isLtrDragging() = dragGestureDelta() > 0
-
-private fun PagerState.isScrollingForward(): Boolean {
+private fun PagerState.isScrollingForward(velocity: Float): Boolean {
val reverseScrollDirection = layoutInfo.reverseLayout
- return (isLtrDragging() && reverseScrollDirection ||
- !isLtrDragging() && !reverseScrollDirection)
+ val isForward =
+ if (isNotGestureAction()) {
+ -velocity
+ } else {
+ dragGestureDelta()
+ } > 0
+ return (isForward && reverseScrollDirection || !isForward && !reverseScrollDirection)
}
private fun PagerState.dragGestureDelta() =
@@ -231,19 +236,19 @@
lowerBoundOffset: Float,
upperBoundOffset: Float
): Float {
-
+ val isScrollingForward = pagerState.isScrollingForward(flingVelocity)
val isForward =
if (pagerState.layoutInfo.orientation == Orientation.Vertical) {
- pagerState.isScrollingForward()
+ isScrollingForward
} else {
if (layoutDirection == LayoutDirection.Ltr) {
- pagerState.isScrollingForward()
+ isScrollingForward
} else {
- !pagerState.isScrollingForward()
+ !isScrollingForward
}
}
debugLog {
- "isLtrDragging=${pagerState.isLtrDragging()} " +
+ "isScrollingForward=${isScrollingForward} " +
"isForward=$isForward " +
"layoutDirection=$layoutDirection"
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
index c9efdcf..7c1e526 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
@@ -727,7 +727,7 @@
sign(scrollDelta) == sign(-upDownDifference.x)
} || isNotGestureAction()
- private fun isNotGestureAction(): Boolean =
+ internal fun isNotGestureAction(): Boolean =
upDownDifference.x.toInt() == 0 && upDownDifference.y.toInt() == 0
private fun notifyPrefetch(delta: Float, info: PagerLayoutInfo) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/MultiParagraphLayoutCache.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/MultiParagraphLayoutCache.kt
index 3af0c4b..b8f2704 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/MultiParagraphLayoutCache.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/MultiParagraphLayoutCache.kt
@@ -49,6 +49,7 @@
*/
internal class MultiParagraphLayoutCache(
private var text: AnnotatedString,
+ /** The style used for layout. Ensure to call [markStyleAffectedDirty] when mutating. */
private var style: TextStyle,
private var fontFamilyResolver: FontFamily.Resolver,
private var overflow: TextOverflow = TextOverflow.Clip,
@@ -163,7 +164,7 @@
// paragraphIntrinsics now does not match with style and needs to be set to null
// otherwise the correct font size will not be used in layout
style = style.copy(fontSize = optimalFontSize)
- paragraphIntrinsics = null
+ markStyleAffectedDirty()
}
val multiParagraph = layoutText(finalConstraints, layoutDirection)
@@ -376,6 +377,13 @@
_textAutoSizeLayoutScope = null
}
+ private fun markStyleAffectedDirty() {
+ paragraphIntrinsics = null
+ layoutCache = null
+ cachedIntrinsicHeight = -1
+ cachedIntrinsicHeightInputWidth = -1
+ }
+
/** The width at which increasing the width of the text no longer decreases the height. */
fun maxIntrinsicWidth(layoutDirection: LayoutDirection): Int {
return setLayoutDirection(layoutDirection).maxIntrinsicWidth.ceilToIntPx()
@@ -408,6 +416,7 @@
val scaledFontSize = fontSize.scaledToInitialFontSize()
if (scaledFontSize != style.fontSize) {
style = style.copy(fontSize = scaledFontSize)
+ markStyleAffectedDirty()
}
val layoutConstraints =
@@ -428,6 +437,8 @@
layoutConstraints,
multiParagraph,
)
+ // We're restoring the original style but purposefully not marking the style-affected
+ // caches dirty as this will be done in layoutWithConstraints after auto size if needed.
style = styleBeforeLayout
return layoutCache!!
}
diff --git a/compose/integration-tests/demos/OWNERS b/compose/integration-tests/demos/OWNERS
index ffc7965..78dca0e 100644
--- a/compose/integration-tests/demos/OWNERS
+++ b/compose/integration-tests/demos/OWNERS
@@ -3,9 +3,7 @@
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
diff --git a/compose/material/OWNERS b/compose/material/OWNERS
index 8c2dd0a..2d21ff4 100644
--- a/compose/material/OWNERS
+++ b/compose/material/OWNERS
@@ -3,7 +3,6 @@
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
diff --git a/compose/material3/OWNERS b/compose/material3/OWNERS
index 7ab3b6d..3778dd9 100644
--- a/compose/material3/OWNERS
+++ b/compose/material3/OWNERS
@@ -1,7 +1,6 @@
# Bug component: 489623
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-as/strings.xml b/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-as/strings.xml
new file mode 100644
index 0000000..d550f15
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-as/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2025 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="m3_adaptive_default_pane_expansion_drag_handle_content_description" msgid="9058489142432490820">"পে’ন সম্প্ৰসাৰণ কৰিবলৈ টনা হেণ্ডেল"</string>
+ <string name="m3_adaptive_default_pane_expansion_drag_handle_action_description" msgid="9031621431415014327">"পে’নৰ বিভাজন %sলৈ সলনি কৰক"</string>
+ <string name="m3_adaptive_default_pane_expansion_proportion_anchor_description" msgid="1205294531112795522">"%d শতাংশ"</string>
+ <string name="m3_adaptive_default_pane_expansion_offset_anchor_description" msgid="8189074525698747223">"%d DPs"</string>
+</resources>
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-en-rCA/strings.xml b/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..33e0970
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-en-rCA/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2025 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="m3_adaptive_default_pane_expansion_drag_handle_content_description" msgid="9058489142432490820">"Pane expansion drag handle"</string>
+ <string name="m3_adaptive_default_pane_expansion_drag_handle_action_description" msgid="9031621431415014327">"Change pane split to %s"</string>
+ <string name="m3_adaptive_default_pane_expansion_proportion_anchor_description" msgid="1205294531112795522">"%d percent"</string>
+ <string name="m3_adaptive_default_pane_expansion_offset_anchor_description" msgid="8189074525698747223">"%d DPs"</string>
+</resources>
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-hi/strings.xml b/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-hi/strings.xml
new file mode 100644
index 0000000..d25df6b
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-hi/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2025 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="m3_adaptive_default_pane_expansion_drag_handle_content_description" msgid="9058489142432490820">"पैनल को बड़ा करने के लिए, खींचकर छोड़ने वाला हैंडल"</string>
+ <string name="m3_adaptive_default_pane_expansion_drag_handle_action_description" msgid="9031621431415014327">"पैनल स्प्लिट को %s में बदलें"</string>
+ <string name="m3_adaptive_default_pane_expansion_proportion_anchor_description" msgid="1205294531112795522">"%d प्रतिशत"</string>
+ <string name="m3_adaptive_default_pane_expansion_offset_anchor_description" msgid="8189074525698747223">"%d डीपी"</string>
+</resources>
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-ja/strings.xml b/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-ja/strings.xml
new file mode 100644
index 0000000..c287ba9
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-ja/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2025 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="m3_adaptive_default_pane_expansion_drag_handle_content_description" msgid="9058489142432490820">"ペインの展開のドラッグ ハンドル"</string>
+ <string name="m3_adaptive_default_pane_expansion_drag_handle_action_description" msgid="9031621431415014327">"ペインの分割を %s に変更"</string>
+ <string name="m3_adaptive_default_pane_expansion_proportion_anchor_description" msgid="1205294531112795522">"%d パーセント"</string>
+ <string name="m3_adaptive_default_pane_expansion_offset_anchor_description" msgid="8189074525698747223">"%d DP"</string>
+</resources>
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-ka/strings.xml b/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-ka/strings.xml
new file mode 100644
index 0000000..d5cbb1b
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-ka/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2025 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="m3_adaptive_default_pane_expansion_drag_handle_content_description" msgid="9058489142432490820">"არეს გაფართოების სახელური ჩავლებისთვის"</string>
+ <string name="m3_adaptive_default_pane_expansion_drag_handle_action_description" msgid="9031621431415014327">"არეს გაყოფის შეცვლა %s-ით"</string>
+ <string name="m3_adaptive_default_pane_expansion_proportion_anchor_description" msgid="1205294531112795522">"%d პროცენტი"</string>
+ <string name="m3_adaptive_default_pane_expansion_offset_anchor_description" msgid="8189074525698747223">"%d DPs"</string>
+</resources>
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-ml/strings.xml b/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-ml/strings.xml
new file mode 100644
index 0000000..0747fcb
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-ml/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2025 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="m3_adaptive_default_pane_expansion_drag_handle_content_description" msgid="9058489142432490820">"പെയിൻ വികസിപ്പിക്കാനായി വലിച്ചിടുന്നതിനുള്ള ഹാൻഡിൽ"</string>
+ <string name="m3_adaptive_default_pane_expansion_drag_handle_action_description" msgid="9031621431415014327">"പെയിൻ വിഭജനം %s ആയി മാറ്റുക"</string>
+ <string name="m3_adaptive_default_pane_expansion_proportion_anchor_description" msgid="1205294531112795522">"%d ശതമാനം"</string>
+ <string name="m3_adaptive_default_pane_expansion_offset_anchor_description" msgid="8189074525698747223">"%d DP-കൾ"</string>
+</resources>
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-ms/strings.xml b/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-ms/strings.xml
new file mode 100644
index 0000000..81f4fb8
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-ms/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2025 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="m3_adaptive_default_pane_expansion_drag_handle_content_description" msgid="9058489142432490820">"Pemegang seret pengembangan anak tetingkap"</string>
+ <string name="m3_adaptive_default_pane_expansion_drag_handle_action_description" msgid="9031621431415014327">"Tukar anak tetingkap terpisah kepada %s"</string>
+ <string name="m3_adaptive_default_pane_expansion_proportion_anchor_description" msgid="1205294531112795522">"%d peratus"</string>
+ <string name="m3_adaptive_default_pane_expansion_offset_anchor_description" msgid="8189074525698747223">"%d DP"</string>
+</resources>
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-pl/strings.xml b/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-pl/strings.xml
new file mode 100644
index 0000000..19a0cd0
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-pl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2025 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="m3_adaptive_default_pane_expansion_drag_handle_content_description" msgid="9058489142432490820">"Uchwyt do przeciągania panelu"</string>
+ <string name="m3_adaptive_default_pane_expansion_drag_handle_action_description" msgid="9031621431415014327">"Zmień podział panelu na %s"</string>
+ <string name="m3_adaptive_default_pane_expansion_proportion_anchor_description" msgid="1205294531112795522">"%d procent"</string>
+ <string name="m3_adaptive_default_pane_expansion_offset_anchor_description" msgid="8189074525698747223">"%d DP"</string>
+</resources>
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-pt-rPT/strings.xml b/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..2dae797
--- /dev/null
+++ b/compose/material3/adaptive/adaptive-layout/src/androidMain/res/values-pt-rPT/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2025 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="m3_adaptive_default_pane_expansion_drag_handle_content_description" msgid="9058489142432490820">"Indicador para arrastar de expansão do painel"</string>
+ <string name="m3_adaptive_default_pane_expansion_drag_handle_action_description" msgid="9031621431415014327">"Altere a divisão do painel para %s"</string>
+ <string name="m3_adaptive_default_pane_expansion_proportion_anchor_description" msgid="1205294531112795522">"%d por cento"</string>
+ <string name="m3_adaptive_default_pane_expansion_offset_anchor_description" msgid="8189074525698747223">"%d DPs"</string>
+</resources>
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt
index cc05a0f..df9e946 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt
@@ -199,7 +199,7 @@
private class PreferredWidthNode(var width: Dp) : ParentDataModifierNode, Modifier.Node() {
override fun Density.modifyParentData(parentData: Any?) =
((parentData as? PaneScaffoldParentDataImpl) ?: PaneScaffoldParentDataImpl()).also {
- it.preferredWidth = with(this) { width.toPx() }
+ it.preferredWidth = width
}
}
@@ -263,7 +263,7 @@
* The preferred width of the child, which is supposed to be set via
* [PaneScaffoldScope.preferredWidth] on a pane composable, like [AnimatedPane].
*/
- val preferredWidth: Float
+ val preferredWidth: Dp
/** `true` to indicate that the child is an [AnimatedPane]; otherwise `false`. */
val isAnimatedPane: Boolean
@@ -277,7 +277,7 @@
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
internal data class PaneScaffoldParentDataImpl(
- override var preferredWidth: Float = Float.NaN,
+ override var preferredWidth: Dp = Dp.Unspecified,
var paneMargins: PaneMargins = PaneMargins.Unspecified,
override var isAnimatedPane: Boolean = false,
override var minTouchTargetSize: Dp = Dp.Unspecified
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
index f5a3832..e3477c7 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
@@ -44,9 +44,11 @@
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.isUnspecified
import androidx.compose.ui.unit.roundToIntRect
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachIndexed
@@ -581,7 +583,8 @@
primaryMeasurables,
ThreePaneScaffoldDefaults.PrimaryPanePriority,
role,
- scaffoldDirective.defaultPanePreferredWidth.roundToPx()
+ scaffoldDirective.defaultPanePreferredWidth.roundToPx(),
+ this@getPanesMeasurables
)
}
ThreePaneScaffoldRole.Secondary -> {
@@ -589,7 +592,8 @@
secondaryMeasurables,
ThreePaneScaffoldDefaults.SecondaryPanePriority,
role,
- scaffoldDirective.defaultPanePreferredWidth.roundToPx()
+ scaffoldDirective.defaultPanePreferredWidth.roundToPx(),
+ this@getPanesMeasurables
)
}
ThreePaneScaffoldRole.Tertiary -> {
@@ -597,7 +601,8 @@
tertiaryMeasurables,
ThreePaneScaffoldDefaults.TertiaryPanePriority,
role,
- scaffoldDirective.defaultPanePreferredWidth.roundToPx()
+ scaffoldDirective.defaultPanePreferredWidth.roundToPx(),
+ this@getPanesMeasurables
)
}
}
@@ -610,10 +615,11 @@
measurables: List<Measurable>,
priority: Int,
role: ThreePaneScaffoldRole,
- defaultPreferredWidth: Int
+ defaultPreferredWidth: Int,
+ density: Density
) {
if (measurables.isNotEmpty()) {
- add(PaneMeasurable(measurables[0], priority, role, defaultPreferredWidth))
+ add(PaneMeasurable(measurables[0], priority, role, defaultPreferredWidth, density))
}
}
@@ -799,16 +805,17 @@
val measurable: Measurable,
val priority: Int,
val role: ThreePaneScaffoldRole,
- defaultPreferredWidth: Int
+ defaultPreferredWidth: Int,
+ density: Density
) {
private val data =
((measurable.parentData as? PaneScaffoldParentData) ?: PaneScaffoldParentDataImpl())
var measuringWidth =
- if (data.preferredWidth.isNaN()) {
+ if (data.preferredWidth.isUnspecified) {
defaultPreferredWidth
} else {
- data.preferredWidth.toInt()
+ with(density) { data.preferredWidth.roundToPx() }
}
// TODO(conradchen): uncomment it when we can expose PaneMargins
diff --git a/compose/material3/material3-adaptive-navigation-suite/api/current.txt b/compose/material3/material3-adaptive-navigation-suite/api/current.txt
index 83d23c0..cab14b1 100644
--- a/compose/material3/material3-adaptive-navigation-suite/api/current.txt
+++ b/compose/material3/material3-adaptive-navigation-suite/api/current.txt
@@ -46,8 +46,29 @@
public final class NavigationSuiteScaffoldKt {
method @androidx.compose.runtime.Composable public static void NavigationSuite(optional androidx.compose.ui.Modifier modifier, optional String layoutType, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteColors colors, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope,kotlin.Unit> content);
- method @androidx.compose.runtime.Composable public static void NavigationSuiteScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope,kotlin.Unit> navigationSuiteItems, optional androidx.compose.ui.Modifier modifier, optional String layoutType, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteColors navigationSuiteColors, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @androidx.compose.runtime.Composable public static void NavigationSuiteScaffoldLayout(kotlin.jvm.functions.Function0<kotlin.Unit> navigationSuite, optional String layoutType, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void NavigationSuiteScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope,kotlin.Unit> navigationSuiteItems, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldState state, optional String layoutType, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteColors navigationSuiteColors, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @Deprecated @androidx.compose.runtime.Composable public static void NavigationSuiteScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope,kotlin.Unit> navigationSuiteItems, optional androidx.compose.ui.Modifier modifier, optional String layoutType, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteColors navigationSuiteColors, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void NavigationSuiteScaffoldLayout(kotlin.jvm.functions.Function0<kotlin.Unit> navigationSuite, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldState state, optional String layoutType, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @Deprecated @androidx.compose.runtime.Composable public static void NavigationSuiteScaffoldLayout(kotlin.jvm.functions.Function0<kotlin.Unit> navigationSuite, optional String layoutType, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldState rememberNavigationSuiteScaffoldState(optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldValue initialValue);
+ }
+
+ public interface NavigationSuiteScaffoldState {
+ method public androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldValue getCurrentValue();
+ method public androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldValue getTargetValue();
+ method public suspend Object? hide(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method public boolean isAnimating();
+ method public suspend Object? show(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method public suspend Object? snapTo(androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldValue targetValue, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method public suspend Object? toggle(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ property public abstract androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldValue currentValue;
+ property public abstract boolean isAnimating;
+ property public abstract androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldValue targetValue;
+ }
+
+ public enum NavigationSuiteScaffoldValue {
+ enum_constant public static final androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldValue Hidden;
+ enum_constant public static final androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldValue Visible;
}
public sealed interface NavigationSuiteScope {
diff --git a/compose/material3/material3-adaptive-navigation-suite/api/restricted_current.txt b/compose/material3/material3-adaptive-navigation-suite/api/restricted_current.txt
index 83d23c0..cab14b1 100644
--- a/compose/material3/material3-adaptive-navigation-suite/api/restricted_current.txt
+++ b/compose/material3/material3-adaptive-navigation-suite/api/restricted_current.txt
@@ -46,8 +46,29 @@
public final class NavigationSuiteScaffoldKt {
method @androidx.compose.runtime.Composable public static void NavigationSuite(optional androidx.compose.ui.Modifier modifier, optional String layoutType, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteColors colors, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope,kotlin.Unit> content);
- method @androidx.compose.runtime.Composable public static void NavigationSuiteScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope,kotlin.Unit> navigationSuiteItems, optional androidx.compose.ui.Modifier modifier, optional String layoutType, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteColors navigationSuiteColors, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @androidx.compose.runtime.Composable public static void NavigationSuiteScaffoldLayout(kotlin.jvm.functions.Function0<kotlin.Unit> navigationSuite, optional String layoutType, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void NavigationSuiteScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope,kotlin.Unit> navigationSuiteItems, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldState state, optional String layoutType, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteColors navigationSuiteColors, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @Deprecated @androidx.compose.runtime.Composable public static void NavigationSuiteScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope,kotlin.Unit> navigationSuiteItems, optional androidx.compose.ui.Modifier modifier, optional String layoutType, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteColors navigationSuiteColors, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void NavigationSuiteScaffoldLayout(kotlin.jvm.functions.Function0<kotlin.Unit> navigationSuite, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldState state, optional String layoutType, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @Deprecated @androidx.compose.runtime.Composable public static void NavigationSuiteScaffoldLayout(kotlin.jvm.functions.Function0<kotlin.Unit> navigationSuite, optional String layoutType, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldState rememberNavigationSuiteScaffoldState(optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldValue initialValue);
+ }
+
+ public interface NavigationSuiteScaffoldState {
+ method public androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldValue getCurrentValue();
+ method public androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldValue getTargetValue();
+ method public suspend Object? hide(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method public boolean isAnimating();
+ method public suspend Object? show(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method public suspend Object? snapTo(androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldValue targetValue, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method public suspend Object? toggle(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ property public abstract androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldValue currentValue;
+ property public abstract boolean isAnimating;
+ property public abstract androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldValue targetValue;
+ }
+
+ public enum NavigationSuiteScaffoldValue {
+ enum_constant public static final androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldValue Hidden;
+ enum_constant public static final androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldValue Visible;
}
public sealed interface NavigationSuiteScope {
diff --git a/compose/material3/material3-adaptive-navigation-suite/samples/src/main/java/androidx/compose/material3/adaptive/navigationsuite/samples/NavigationSuiteScaffoldSamples.kt b/compose/material3/material3-adaptive-navigation-suite/samples/src/main/java/androidx/compose/material3/adaptive/navigationsuite/samples/NavigationSuiteScaffoldSamples.kt
index 7d112c9..3379088 100644
--- a/compose/material3/material3-adaptive-navigation-suite/samples/src/main/java/androidx/compose/material3/adaptive/navigationsuite/samples/NavigationSuiteScaffoldSamples.kt
+++ b/compose/material3/material3-adaptive-navigation-suite/samples/src/main/java/androidx/compose/material3/adaptive/navigationsuite/samples/NavigationSuiteScaffoldSamples.kt
@@ -19,32 +19,40 @@
package androidx.compose.material3.adaptive.navigationsuite.samples
import androidx.annotation.Sampled
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationRail
import androidx.compose.material3.NavigationRailItem
+import androidx.compose.material3.Surface
import androidx.compose.material3.Text
-import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuite
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldDefaults
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldLayout
+import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldValue
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteType
+import androidx.compose.material3.adaptive.navigationsuite.rememberNavigationSuiteScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.window.core.layout.WindowWidthSizeClass
+import kotlinx.coroutines.launch
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Preview
@Sampled
@Composable
@@ -53,8 +61,12 @@
val navItems = listOf("Songs", "Artists", "Playlists")
val navSuiteType =
NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(currentWindowAdaptiveInfo())
+ val state = rememberNavigationSuiteScaffoldState()
+ val scope = rememberCoroutineScope()
NavigationSuiteScaffold(
+ layoutType = navSuiteType,
+ state = state,
navigationSuiteItems = {
navItems.forEachIndexed { index, navItem ->
item(
@@ -67,14 +79,28 @@
}
) {
// Screen content.
- Text(
- modifier = Modifier.padding(16.dp),
- text = "Current NavigationSuiteType: $navSuiteType"
- )
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ val text =
+ if (state.currentValue == NavigationSuiteScaffoldValue.Visible) {
+ "displayed"
+ } else {
+ "hidden"
+ }
+ Text(
+ modifier = Modifier.padding(16.dp),
+ text = "Current NavigationSuiteType: $navSuiteType,\nit is $text",
+ textAlign = TextAlign.Center
+ )
+ Button(onClick = { scope.launch { state.toggle() } }) {
+ Text("Hide/show navigation component")
+ }
+ }
}
}
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Preview
@Sampled
@Composable
@@ -83,6 +109,8 @@
var selectedItem by remember { mutableIntStateOf(0) }
val navItems = listOf("Songs", "Artists", "Playlists")
val adaptiveInfo = currentWindowAdaptiveInfo()
+ val state = rememberNavigationSuiteScaffoldState()
+ val scope = rememberCoroutineScope()
// Custom configuration that shows a navigation drawer in large screens.
val customNavSuiteType =
with(adaptiveInfo) {
@@ -95,6 +123,7 @@
NavigationSuiteScaffold(
layoutType = customNavSuiteType,
+ state = state,
navigationSuiteItems = {
navItems.forEachIndexed { index, navItem ->
item(
@@ -107,14 +136,26 @@
}
) {
// Screen content.
- Text(
- modifier = Modifier.padding(16.dp),
- text = "Current custom NavigationSuiteType: $customNavSuiteType"
- )
+ // Screen content.
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ val text =
+ if (state.currentValue == NavigationSuiteScaffoldValue.Visible) "displayed"
+ else "hidden"
+ Text(
+ modifier = Modifier.padding(16.dp),
+ text = "Current custom NavigationSuiteType: $customNavSuiteType, is $text",
+ textAlign = TextAlign.Center
+ )
+ Button(onClick = { scope.launch { state.toggle() } }) {
+ Text("Hide/show navigation component")
+ }
+ }
}
}
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Preview
@Sampled
@Composable
@@ -123,43 +164,60 @@
val navItems = listOf("Songs", "Artists", "Playlists")
val navSuiteType =
NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(currentWindowAdaptiveInfo())
+ val state = rememberNavigationSuiteScaffoldState()
+ val scope = rememberCoroutineScope()
- NavigationSuiteScaffoldLayout(
- navigationSuite = {
- // Custom Navigation Rail with centered items.
- if (navSuiteType == NavigationSuiteType.NavigationRail) {
- NavigationRail {
- // Adding Spacers before and after the item so they are pushed towards the
- // center of the NavigationRail.
- Spacer(Modifier.weight(1f))
- navItems.forEachIndexed { index, item ->
- NavigationRailItem(
- icon = { Icon(Icons.Filled.Favorite, contentDescription = item) },
- label = { Text(item) },
- selected = selectedItem == index,
- onClick = { selectedItem = index }
- )
+ Surface {
+ NavigationSuiteScaffoldLayout(
+ state = state,
+ navigationSuite = {
+ // Custom Navigation Rail with centered items.
+ if (navSuiteType == NavigationSuiteType.NavigationRail) {
+ NavigationRail {
+ // Adding Spacers before and after the item so they are pushed towards the
+ // center of the NavigationRail.
+ Spacer(Modifier.weight(1f))
+ navItems.forEachIndexed { index, item ->
+ NavigationRailItem(
+ icon = { Icon(Icons.Filled.Favorite, contentDescription = item) },
+ label = { Text(item) },
+ selected = selectedItem == index,
+ onClick = { selectedItem = index }
+ )
+ }
+ Spacer(Modifier.weight(1f))
}
- Spacer(Modifier.weight(1f))
- }
- } else {
- NavigationSuite {
- navItems.forEachIndexed { index, item ->
- item(
- icon = { Icon(Icons.Filled.Favorite, contentDescription = item) },
- label = { Text(item) },
- selected = selectedItem == index,
- onClick = { selectedItem = index }
- )
+ } else {
+ NavigationSuite {
+ navItems.forEachIndexed { index, item ->
+ item(
+ icon = { Icon(Icons.Filled.Favorite, contentDescription = item) },
+ label = { Text(item) },
+ selected = selectedItem == index,
+ onClick = { selectedItem = index }
+ )
+ }
}
}
}
+ ) {
+ // Screen content.
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ val text =
+ if (state.currentValue == NavigationSuiteScaffoldValue.Visible) "displayed"
+ else "hidden"
+ Text(
+ modifier = Modifier.padding(16.dp),
+ text = "Current NavigationSuiteType: $navSuiteType, is $text",
+ textAlign = TextAlign.Center
+ )
+ Button(onClick = { scope.launch { state.toggle() } }) {
+ Text("Hide/show navigation component")
+ }
+ }
}
- ) {
- // Screen content.
- Text(
- modifier = Modifier.padding(16.dp),
- text = "Current NavigationSuiteType: $navSuiteType"
- )
}
}
diff --git a/compose/material3/material3-adaptive-navigation-suite/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffoldTest.kt b/compose/material3/material3-adaptive-navigation-suite/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffoldTest.kt
index b34dd9f..a3eb013 100644
--- a/compose/material3/material3-adaptive-navigation-suite/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffoldTest.kt
+++ b/compose/material3/material3-adaptive-navigation-suite/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffoldTest.kt
@@ -19,16 +19,23 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.testutils.assertIsEqualTo
import androidx.compose.testutils.assertIsNotEqualTo
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.junit4.StateRestorationTester
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.unit.height
import androidx.compose.ui.unit.width
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,6 +44,53 @@
@RunWith(JUnit4::class)
class NavigationSuiteScaffoldTest {
@get:Rule val rule = createComposeRule()
+ private val restorationTester = StateRestorationTester(rule)
+
+ @Test
+ fun navigationSuiteScaffoldState_savesAndRestores() {
+ lateinit var state: NavigationSuiteScaffoldState
+
+ restorationTester.setContent { state = rememberNavigationSuiteScaffoldState() }
+
+ assertThat(state.targetValue.isVisible).isTrue()
+ restorationTester.emulateSavedInstanceStateRestore()
+ assertThat(state.targetValue.isVisible).isTrue()
+ }
+
+ @Test
+ fun navigationSuiteScaffold_hideNavigationSuite() {
+ lateinit var state: NavigationSuiteScaffoldState
+ lateinit var scope: CoroutineScope
+
+ rule.setContent {
+ state = rememberNavigationSuiteScaffoldState()
+ scope = rememberCoroutineScope()
+ SampleNavigationSuiteScaffoldLayout(NavigationSuiteType.NavigationBar, state)
+ }
+
+ scope.launch { state.snapTo(NavigationSuiteScaffoldValue.Hidden) }
+
+ rule.onNodeWithTag(NavigationSuiteTag).assertIsNotDisplayed()
+ }
+
+ @Test
+ fun navigationSuiteScaffold_showNavigationSuite() {
+ lateinit var state: NavigationSuiteScaffoldState
+ lateinit var scope: CoroutineScope
+
+ rule.setContent {
+ state =
+ rememberNavigationSuiteScaffoldState(
+ initialValue = NavigationSuiteScaffoldValue.Hidden
+ )
+ scope = rememberCoroutineScope()
+ SampleNavigationSuiteScaffoldLayout(NavigationSuiteType.NavigationBar, state)
+ }
+
+ scope.launch { state.snapTo(NavigationSuiteScaffoldValue.Visible) }
+
+ rule.onNodeWithTag(NavigationSuiteTag).assertIsDisplayed()
+ }
@Test
fun navigationSuiteScaffoldTest_fillMaxSize_withNavBar_succeeds() {
@@ -109,8 +163,12 @@
}
@Composable
-private fun SampleNavigationSuiteScaffoldLayout(layoutType: NavigationSuiteType) {
+private fun SampleNavigationSuiteScaffoldLayout(
+ layoutType: NavigationSuiteType,
+ state: NavigationSuiteScaffoldState = rememberNavigationSuiteScaffoldState()
+) {
NavigationSuiteScaffoldLayout(
+ state = state,
navigationSuite = {
NavigationSuite(
modifier = Modifier.testTag(NavigationSuiteTag),
diff --git a/compose/material3/material3-adaptive-navigation-suite/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.kt b/compose/material3/material3-adaptive-navigation-suite/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.kt
index cc8b5de..f82c288 100644
--- a/compose/material3/material3-adaptive-navigation-suite/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.kt
+++ b/compose/material3/material3-adaptive-navigation-suite/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.kt
@@ -18,6 +18,12 @@
package androidx.compose.material3.adaptive.navigationsuite
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.SpringSpec
+import androidx.compose.animation.core.VectorConverter
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.spring
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
@@ -57,6 +63,8 @@
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.Layout
@@ -65,17 +73,78 @@
import androidx.window.core.layout.WindowHeightSizeClass
import androidx.window.core.layout.WindowWidthSizeClass
+/** Possible values of [NavigationSuiteScaffoldState]. */
+enum class NavigationSuiteScaffoldValue {
+ /** The state of the navigation component of the scaffold when it's visible. */
+ Visible,
+
+ /** The state of the navigation component of the scaffold when it's hidden. */
+ Hidden
+}
+
+/**
+ * A state object that can be hoisted to observe the navigation suite scaffold state. It allows for
+ * setting its navigation component to be hidden or displayed.
+ *
+ * @see rememberNavigationSuiteScaffoldState to construct the default implementation.
+ */
+interface NavigationSuiteScaffoldState {
+ /** Whether the state is currently animating. */
+ val isAnimating: Boolean
+
+ /** Whether the navigation component is going to be shown or hidden. */
+ val targetValue: NavigationSuiteScaffoldValue
+
+ /** Whether the navigation component is currently shown or hidden. */
+ val currentValue: NavigationSuiteScaffoldValue
+
+ /** Hide the navigation component with animation and suspend until it fully expands. */
+ suspend fun hide()
+
+ /** Show the navigation component with animation and suspend until it fully expands. */
+ suspend fun show()
+
+ /**
+ * Hide the navigation component with animation if it's shown, or collapse it otherwise, and
+ * suspend until it fully expands.
+ */
+ suspend fun toggle()
+
+ /**
+ * Set the state without any animation and suspend until it's set.
+ *
+ * @param targetValue the value to set to
+ */
+ suspend fun snapTo(targetValue: NavigationSuiteScaffoldValue)
+}
+
+/** Create and [remember] a [NavigationSuiteScaffoldState] */
+@Composable
+fun rememberNavigationSuiteScaffoldState(
+ initialValue: NavigationSuiteScaffoldValue = NavigationSuiteScaffoldValue.Visible
+): NavigationSuiteScaffoldState {
+ return rememberSaveable(saver = NavigationSuiteScaffoldStateImpl.Saver()) {
+ NavigationSuiteScaffoldStateImpl(initialValue = initialValue)
+ }
+}
+
/**
* The Navigation Suite Scaffold wraps the provided content and places the adequate provided
* navigation component on the screen according to the current [NavigationSuiteType].
*
+ * The navigation component can be animated to be hidden or shown via a
+ * [NavigationSuiteScaffoldState].
+ *
* Example default usage:
*
* @sample androidx.compose.material3.adaptive.navigationsuite.samples.NavigationSuiteScaffoldSample
- * Example custom configuration usage:
+ *
+ * Example custom configuration usage:
+ *
* @sample androidx.compose.material3.adaptive.navigationsuite.samples.NavigationSuiteScaffoldCustomConfigSample
* @param navigationSuiteItems the navigation items to be displayed
* @param modifier the [Modifier] to be applied to the navigation suite scaffold
+ * @param state the [NavigationSuiteScaffoldState] of this navigation suite scaffold
* @param layoutType the current [NavigationSuiteType]. Defaults to
* [NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo]
* @param navigationSuiteColors [NavigationSuiteColors] that will be used to determine the container
@@ -91,6 +160,7 @@
fun NavigationSuiteScaffold(
navigationSuiteItems: NavigationSuiteScope.() -> Unit,
modifier: Modifier = Modifier,
+ state: NavigationSuiteScaffoldState = rememberNavigationSuiteScaffoldState(),
layoutType: NavigationSuiteType =
NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(WindowAdaptiveInfoDefault),
navigationSuiteColors: NavigationSuiteColors = NavigationSuiteDefaults.colors(),
@@ -107,18 +177,30 @@
content = navigationSuiteItems
)
},
+ state = state,
layoutType = layoutType,
content = {
Box(
Modifier.consumeWindowInsets(
- when (layoutType) {
- NavigationSuiteType.NavigationBar ->
- NavigationBarDefaults.windowInsets.only(WindowInsetsSides.Bottom)
- NavigationSuiteType.NavigationRail ->
- NavigationRailDefaults.windowInsets.only(WindowInsetsSides.Start)
- NavigationSuiteType.NavigationDrawer ->
- DrawerDefaults.windowInsets.only(WindowInsetsSides.Start)
- else -> NoWindowInsets
+ if (
+ state.currentValue == NavigationSuiteScaffoldValue.Hidden &&
+ !state.isAnimating
+ ) {
+ NoWindowInsets
+ } else {
+ when (layoutType) {
+ NavigationSuiteType.NavigationBar ->
+ NavigationBarDefaults.windowInsets.only(
+ WindowInsetsSides.Bottom
+ )
+ NavigationSuiteType.NavigationRail ->
+ NavigationRailDefaults.windowInsets.only(
+ WindowInsetsSides.Start
+ )
+ NavigationSuiteType.NavigationDrawer ->
+ DrawerDefaults.windowInsets.only(WindowInsetsSides.Start)
+ else -> NoWindowInsets
+ }
}
)
) {
@@ -130,6 +212,56 @@
}
/**
+ * The Navigation Suite Scaffold wraps the provided content and places the adequate provided
+ * navigation component on the screen according to the current [NavigationSuiteType].
+ *
+ * Example default usage:
+ *
+ * @sample androidx.compose.material3.adaptive.navigationsuite.samples.NavigationSuiteScaffoldSample
+ *
+ * Example custom configuration usage:
+ *
+ * @sample androidx.compose.material3.adaptive.navigationsuite.samples.NavigationSuiteScaffoldCustomConfigSample
+ * @param navigationSuiteItems the navigation items to be displayed
+ * @param modifier the [Modifier] to be applied to the navigation suite scaffold
+ * @param layoutType the current [NavigationSuiteType]. Defaults to
+ * [NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo]
+ * @param navigationSuiteColors [NavigationSuiteColors] that will be used to determine the container
+ * (background) color of the navigation component and the preferred color for content inside the
+ * navigation component
+ * @param containerColor the color used for the background of the navigation suite scaffold,
+ * including the passed [content] composable. Use [Color.Transparent] to have no color
+ * @param contentColor the preferred color to be used for typography and iconography within the
+ * passed in [content] lambda inside the navigation suite scaffold.
+ * @param content the content of your screen
+ */
+@Deprecated(
+ message = "Deprecated in favor of NavigationSuiteScaffold with state parameter",
+ level = DeprecationLevel.HIDDEN
+)
+@Composable
+fun NavigationSuiteScaffold(
+ navigationSuiteItems: NavigationSuiteScope.() -> Unit,
+ modifier: Modifier = Modifier,
+ layoutType: NavigationSuiteType =
+ NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(WindowAdaptiveInfoDefault),
+ navigationSuiteColors: NavigationSuiteColors = NavigationSuiteDefaults.colors(),
+ containerColor: Color = NavigationSuiteScaffoldDefaults.containerColor,
+ contentColor: Color = NavigationSuiteScaffoldDefaults.contentColor,
+ content: @Composable () -> Unit = {},
+) =
+ NavigationSuiteScaffold(
+ navigationSuiteItems = navigationSuiteItems,
+ modifier = modifier,
+ state = rememberNavigationSuiteScaffoldState(),
+ layoutType = layoutType,
+ navigationSuiteColors = navigationSuiteColors,
+ containerColor = containerColor,
+ contentColor = contentColor,
+ content = content
+ )
+
+/**
* Layout for a [NavigationSuiteScaffold]'s content. This function wraps the [content] and places
* the [navigationSuite] component according to the given [layoutType].
*
@@ -138,6 +270,7 @@
*
* @sample androidx.compose.material3.adaptive.navigationsuite.samples.NavigationSuiteScaffoldCustomNavigationRail
* @param navigationSuite the navigation component to be displayed, typically [NavigationSuite]
+ * @param state the [NavigationSuiteScaffoldState] of this navigation suite scaffold layout
* @param layoutType the current [NavigationSuiteType]. Defaults to
* [NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo]
* @param content the content of your screen
@@ -145,10 +278,17 @@
@Composable
fun NavigationSuiteScaffoldLayout(
navigationSuite: @Composable () -> Unit,
+ state: NavigationSuiteScaffoldState = rememberNavigationSuiteScaffoldState(),
layoutType: NavigationSuiteType =
NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(WindowAdaptiveInfoDefault),
content: @Composable () -> Unit = {}
) {
+ val animationProgress by
+ animateFloatAsState(
+ targetValue = if (state.currentValue == NavigationSuiteScaffoldValue.Hidden) 0f else 1f,
+ animationSpec = AnimationSpec
+ )
+
Layout({
// Wrap the navigation suite and content composables each in a Box to not propagate the
// parent's (Surface) min constraints to its children (see b/312664933).
@@ -171,13 +311,21 @@
.measure(
if (isNavigationBar) {
constraints.copy(
- minHeight = layoutHeight - navigationPlaceable.height,
- maxHeight = layoutHeight - navigationPlaceable.height
+ minHeight =
+ layoutHeight -
+ (navigationPlaceable.height * animationProgress).toInt(),
+ maxHeight =
+ layoutHeight -
+ (navigationPlaceable.height * animationProgress).toInt()
)
} else {
constraints.copy(
- minWidth = layoutWidth - navigationPlaceable.width,
- maxWidth = layoutWidth - navigationPlaceable.width
+ minWidth =
+ layoutWidth -
+ (navigationPlaceable.width * animationProgress).toInt(),
+ maxWidth =
+ layoutWidth -
+ (navigationPlaceable.width * animationProgress).toInt()
)
}
)
@@ -187,18 +335,58 @@
// Place content above the navigation component.
contentPlaceable.placeRelative(0, 0)
// Place the navigation component at the bottom of the screen.
- navigationPlaceable.placeRelative(0, layoutHeight - (navigationPlaceable.height))
+ navigationPlaceable.placeRelative(
+ 0,
+ layoutHeight - (navigationPlaceable.height * animationProgress).toInt()
+ )
} else {
// Place the navigation component at the start of the screen.
- navigationPlaceable.placeRelative(0, 0)
+ navigationPlaceable.placeRelative(
+ (0 - (navigationPlaceable.width * (1f - animationProgress))).toInt(),
+ 0
+ )
// Place content to the side of the navigation component.
- contentPlaceable.placeRelative((navigationPlaceable.width), 0)
+ contentPlaceable.placeRelative(
+ (navigationPlaceable.width * animationProgress).toInt(),
+ 0
+ )
}
}
}
}
/**
+ * Layout for a [NavigationSuiteScaffold]'s content. This function wraps the [content] and places
+ * the [navigationSuite] component according to the given [layoutType].
+ *
+ * The usage of this function is recommended when you need some customization that is not viable via
+ * the use of [NavigationSuiteScaffold]. Example usage:
+ *
+ * @sample androidx.compose.material3.adaptive.navigationsuite.samples.NavigationSuiteScaffoldCustomNavigationRail
+ * @param navigationSuite the navigation component to be displayed, typically [NavigationSuite]
+ * @param layoutType the current [NavigationSuiteType]. Defaults to
+ * [NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo]
+ * @param content the content of your screen
+ */
+@Deprecated(
+ message = "Deprecated in favor of NavigationSuiteScaffoldLayout with state parameter",
+ level = DeprecationLevel.HIDDEN
+)
+@Composable
+fun NavigationSuiteScaffoldLayout(
+ navigationSuite: @Composable () -> Unit,
+ layoutType: NavigationSuiteType =
+ NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(WindowAdaptiveInfoDefault),
+ content: @Composable () -> Unit = {}
+) =
+ NavigationSuiteScaffoldLayout(
+ navigationSuite = navigationSuite,
+ state = rememberNavigationSuiteScaffoldState(),
+ layoutType = layoutType,
+ content = content
+ )
+
+/**
* The default Material navigation component according to the current [NavigationSuiteType] to be
* used with the [NavigationSuiteScaffold].
*
@@ -544,6 +732,68 @@
internal val WindowAdaptiveInfoDefault
@Composable get() = currentWindowAdaptiveInfo()
+internal val NavigationSuiteScaffoldValue.isVisible
+ get() = this == NavigationSuiteScaffoldValue.Visible
+
+internal class NavigationSuiteScaffoldStateImpl(var initialValue: NavigationSuiteScaffoldValue) :
+ NavigationSuiteScaffoldState {
+ private val internalValue: Float = if (initialValue.isVisible) Visible else Hidden
+ private val internalState = Animatable(internalValue, Float.VectorConverter)
+ private val _currentVal = derivedStateOf {
+ if (internalState.value == Visible) {
+ NavigationSuiteScaffoldValue.Visible
+ } else {
+ NavigationSuiteScaffoldValue.Hidden
+ }
+ }
+
+ override val isAnimating: Boolean
+ get() = internalState.isRunning
+
+ override val targetValue: NavigationSuiteScaffoldValue
+ get() =
+ if (internalState.targetValue == Visible) {
+ NavigationSuiteScaffoldValue.Visible
+ } else {
+ NavigationSuiteScaffoldValue.Hidden
+ }
+
+ override val currentValue: NavigationSuiteScaffoldValue
+ get() = _currentVal.value
+
+ override suspend fun hide() {
+ internalState.animateTo(targetValue = Hidden, animationSpec = AnimationSpec)
+ }
+
+ override suspend fun show() {
+ internalState.animateTo(targetValue = Visible, animationSpec = AnimationSpec)
+ }
+
+ override suspend fun toggle() {
+ internalState.animateTo(
+ targetValue = if (targetValue.isVisible) Hidden else Visible,
+ animationSpec = AnimationSpec
+ )
+ }
+
+ override suspend fun snapTo(targetValue: NavigationSuiteScaffoldValue) {
+ val target = if (targetValue.isVisible) Visible else Hidden
+ internalState.snapTo(target)
+ }
+
+ companion object {
+ private const val Hidden = 0f
+ private const val Visible = 1f
+
+ /** The default [Saver] implementation for [NavigationSuiteScaffoldState]. */
+ fun Saver() =
+ Saver<NavigationSuiteScaffoldState, NavigationSuiteScaffoldValue>(
+ save = { it.targetValue },
+ restore = { NavigationSuiteScaffoldStateImpl(it) }
+ )
+ }
+}
+
private interface NavigationSuiteItemProvider {
val itemsCount: Int
val itemList: MutableVector<NavigationSuiteItem>
@@ -618,7 +868,11 @@
}
}
-private val NoWindowInsets = WindowInsets(0, 0, 0, 0)
-
+private const val SpringDefaultSpatialDamping = 0.9f
+private const val SpringDefaultSpatialStiffness = 700.0f
private const val NavigationSuiteLayoutIdTag = "navigationSuite"
private const val ContentLayoutIdTag = "content"
+
+private val NoWindowInsets = WindowInsets(0, 0, 0, 0)
+private val AnimationSpec: SpringSpec<Float> =
+ spring(dampingRatio = SpringDefaultSpatialDamping, stiffness = SpringDefaultSpatialStiffness)
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 9c465b2e..a3501ba 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -51,6 +51,7 @@
method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional float expandedHeight, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.ui.Alignment.Horizontal titleHorizontalAlignment, optional float expandedHeight, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void TwoRowsTopAppBar(kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit?> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.ui.Alignment.Horizontal titleHorizontalAlignment, optional kotlin.jvm.functions.Function1<? super java.lang.Boolean,androidx.compose.ui.unit.Dp> height, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.BottomAppBarState rememberBottomAppBarState(optional float initialHeightOffsetLimit, optional float initialHeightOffset, optional float initialContentOffset);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TopAppBarState rememberTopAppBarState(optional float initialHeightOffsetLimit, optional float initialHeightOffset, optional float initialContentOffset);
}
@@ -1035,10 +1036,10 @@
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public static androidx.compose.material3.FloatingToolbarState FloatingToolbarState(float initialOffsetLimit, float initialOffset, float initialContentOffset);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void HorizontalFloatingToolbar(boolean expanded, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.FloatingToolbarColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.material3.FloatingToolbarScrollBehavior? scrollBehavior, optional androidx.compose.ui.graphics.Shape shape, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? leadingContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? trailingContent, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void HorizontalFloatingToolbar(boolean expanded, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.material3.FloatingToolbarScrollBehavior? scrollBehavior, optional androidx.compose.ui.graphics.Shape shape, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? leadingContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? trailingContent, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void HorizontalFloatingToolbar(boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.FloatingToolbarColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape, optional int floatingActionButtonPosition, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void HorizontalFloatingToolbar(boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.FloatingToolbarColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.material3.FloatingToolbarScrollBehavior? scrollBehavior, optional androidx.compose.ui.graphics.Shape shape, optional int floatingActionButtonPosition, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void VerticalFloatingToolbar(boolean expanded, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.FloatingToolbarColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.material3.FloatingToolbarScrollBehavior? scrollBehavior, optional androidx.compose.ui.graphics.Shape shape, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? leadingContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? trailingContent, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void VerticalFloatingToolbar(boolean expanded, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.material3.FloatingToolbarScrollBehavior? scrollBehavior, optional androidx.compose.ui.graphics.Shape shape, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? leadingContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? trailingContent, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void VerticalFloatingToolbar(boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.FloatingToolbarColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape, optional int floatingActionButtonPosition, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void VerticalFloatingToolbar(boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.FloatingToolbarColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.material3.FloatingToolbarScrollBehavior? scrollBehavior, optional androidx.compose.ui.graphics.Shape shape, optional int floatingActionButtonPosition, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.FloatingToolbarState rememberFloatingToolbarState(optional float initialOffsetLimit, optional float initialOffset, optional float initialContentOffset);
}
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 9c465b2e..a3501ba 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -51,6 +51,7 @@
method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional float expandedHeight, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> subtitle, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.ui.Alignment.Horizontal titleHorizontalAlignment, optional float expandedHeight, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void TwoRowsTopAppBar(kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit?> subtitle, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.ui.Alignment.Horizontal titleHorizontalAlignment, optional kotlin.jvm.functions.Function1<? super java.lang.Boolean,androidx.compose.ui.unit.Dp> height, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.BottomAppBarState rememberBottomAppBarState(optional float initialHeightOffsetLimit, optional float initialHeightOffset, optional float initialContentOffset);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TopAppBarState rememberTopAppBarState(optional float initialHeightOffsetLimit, optional float initialHeightOffset, optional float initialContentOffset);
}
@@ -1035,10 +1036,10 @@
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public static androidx.compose.material3.FloatingToolbarState FloatingToolbarState(float initialOffsetLimit, float initialOffset, float initialContentOffset);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void HorizontalFloatingToolbar(boolean expanded, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.FloatingToolbarColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.material3.FloatingToolbarScrollBehavior? scrollBehavior, optional androidx.compose.ui.graphics.Shape shape, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? leadingContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? trailingContent, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void HorizontalFloatingToolbar(boolean expanded, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.material3.FloatingToolbarScrollBehavior? scrollBehavior, optional androidx.compose.ui.graphics.Shape shape, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? leadingContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? trailingContent, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void HorizontalFloatingToolbar(boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.FloatingToolbarColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape, optional int floatingActionButtonPosition, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void HorizontalFloatingToolbar(boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.FloatingToolbarColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.material3.FloatingToolbarScrollBehavior? scrollBehavior, optional androidx.compose.ui.graphics.Shape shape, optional int floatingActionButtonPosition, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void VerticalFloatingToolbar(boolean expanded, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.FloatingToolbarColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.material3.FloatingToolbarScrollBehavior? scrollBehavior, optional androidx.compose.ui.graphics.Shape shape, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? leadingContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? trailingContent, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void VerticalFloatingToolbar(boolean expanded, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.material3.FloatingToolbarScrollBehavior? scrollBehavior, optional androidx.compose.ui.graphics.Shape shape, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? leadingContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? trailingContent, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void VerticalFloatingToolbar(boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.FloatingToolbarColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape, optional int floatingActionButtonPosition, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void VerticalFloatingToolbar(boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.FloatingToolbarColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.material3.FloatingToolbarScrollBehavior? scrollBehavior, optional androidx.compose.ui.graphics.Shape shape, optional int floatingActionButtonPosition, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.FloatingToolbarState rememberFloatingToolbarState(optional float initialOffsetLimit, optional float initialOffset, optional float initialContentOffset);
}
diff --git a/compose/material3/material3/build.gradle b/compose/material3/material3/build.gradle
index c025935..dce4bf4 100644
--- a/compose/material3/material3/build.gradle
+++ b/compose/material3/material3/build.gradle
@@ -111,6 +111,7 @@
implementation("androidx.compose.material:material-icons-core:1.7.5")
implementation(project(":test:screenshot:screenshot"))
implementation(project(":core:core"))
+ implementation("androidx.compose.ui:ui-tooling:1.4.1")
implementation(libs.espressoCore)
implementation(libs.testRules)
implementation(libs.testRunner)
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
index afd28629..6d3b4e5 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
@@ -40,6 +40,8 @@
import androidx.compose.material3.samples.ButtonSample
import androidx.compose.material3.samples.ButtonWithIconSample
import androidx.compose.material3.samples.CardSample
+import androidx.compose.material3.samples.CenteredHorizontalFloatingToolbarWithFabSample
+import androidx.compose.material3.samples.CenteredVerticalFloatingToolbarWithFabSample
import androidx.compose.material3.samples.CheckboxSample
import androidx.compose.material3.samples.CheckboxWithTextSample
import androidx.compose.material3.samples.ChipGroupReflowSample
@@ -50,6 +52,7 @@
import androidx.compose.material3.samples.ClickableElevatedCardSample
import androidx.compose.material3.samples.ClickableOutlinedCardSample
import androidx.compose.material3.samples.ContainedLoadingIndicatorSample
+import androidx.compose.material3.samples.CustomTwoRowsTopAppBar
import androidx.compose.material3.samples.DateInputSample
import androidx.compose.material3.samples.DatePickerDialogSample
import androidx.compose.material3.samples.DatePickerSample
@@ -837,6 +840,13 @@
) {
ExitUntilCollapsedCenterAlignedLargeFlexibleTopAppBar()
},
+ Example(
+ name = "CustomTwoRowsTopAppBar",
+ description = TopAppBarExampleDescription,
+ sourceUrl = TopAppBarExampleSourceUrl,
+ ) {
+ CustomTwoRowsTopAppBar()
+ },
)
private const val FloatingToolbarsExampleDescription = "Floating toolbar examples"
@@ -880,11 +890,11 @@
HorizontalFloatingToolbarWithFabSample()
},
Example(
- name = "VerticalFloatingToolbarWithFabSample",
+ name = "CenteredHorizontalFloatingToolbarWithFabSample",
description = FloatingToolbarsExampleDescription,
sourceUrl = FloatingToolbarsExampleSourceUrl,
) {
- VerticalFloatingToolbarWithFabSample()
+ CenteredHorizontalFloatingToolbarWithFabSample()
},
Example(
name = "HorizontalFloatingToolbarAsScaffoldFabSample",
@@ -893,6 +903,20 @@
) {
HorizontalFloatingToolbarAsScaffoldFabSample()
},
+ Example(
+ name = "VerticalFloatingToolbarWithFabSample",
+ description = FloatingToolbarsExampleDescription,
+ sourceUrl = FloatingToolbarsExampleSourceUrl,
+ ) {
+ VerticalFloatingToolbarWithFabSample()
+ },
+ Example(
+ name = "CenteredVerticalFloatingToolbarWithFabSample",
+ description = FloatingToolbarsExampleDescription,
+ sourceUrl = FloatingToolbarsExampleSourceUrl,
+ ) {
+ CenteredVerticalFloatingToolbarWithFabSample()
+ },
)
private const val ExtendedFABExampleDescription = "Extended FAB examples"
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/AppBarSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/AppBarSamples.kt
index 7d459ef..3c08716 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/AppBarSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/AppBarSamples.kt
@@ -20,12 +20,15 @@
import android.view.accessibility.AccessibilityManager
import androidx.annotation.Sampled
import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.ArrowForward
@@ -47,6 +50,7 @@
import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeFlexibleTopAppBar
import androidx.compose.material3.LargeTopAppBar
+import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MediumFlexibleTopAppBar
import androidx.compose.material3.MediumTopAppBar
@@ -54,7 +58,9 @@
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.TwoRowsTopAppBar
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -62,6 +68,7 @@
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
import androidx.compose.ui.unit.dp
/**
@@ -611,6 +618,71 @@
)
}
+/**
+ * A sample for a [TwoRowsTopAppBar] that collapses when the content is scrolled up, and appears
+ * when the content is completely scrolled back down.
+ */
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
+@Preview
+@Sampled
+@Composable
+fun CustomTwoRowsTopAppBar() {
+ val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
+ Scaffold(
+ modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+ topBar = {
+ TwoRowsTopAppBar(
+ title = { expanded ->
+ if (expanded) {
+ Text("Expanded TopAppBar", maxLines = 1, overflow = TextOverflow.Ellipsis)
+ } else {
+ Text("Collapsed TopAppBar", maxLines = 1, overflow = TextOverflow.Ellipsis)
+ }
+ },
+ subtitle = { expanded ->
+ if (expanded) {
+ Text(
+ "Expanded Subtitle",
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ modifier = Modifier.padding(bottom = 24.dp)
+ )
+ } else {
+ Text("Collapsed Subtitle", maxLines = 1, overflow = TextOverflow.Ellipsis)
+ }
+ },
+ height = { expanded ->
+ if (expanded) {
+ 156.dp
+ } else {
+ 64.dp
+ }
+ },
+ navigationIcon = {
+ IconButton(onClick = { /* doSomething() */ }) {
+ Icon(
+ imageVector = Icons.Filled.Menu,
+ contentDescription = "Localized description"
+ )
+ }
+ },
+ scrollBehavior = scrollBehavior
+ )
+ },
+ content = { innerPadding ->
+ Column(
+ Modifier.fillMaxWidth().padding(innerPadding).verticalScroll(rememberScrollState())
+ ) {
+ CompositionLocalProvider(
+ LocalTextStyle provides MaterialTheme.typography.bodyLarge
+ ) {
+ Text(text = remember { LoremIpsum().values.first() })
+ }
+ }
+ }
+ )
+}
+
@Preview
@Sampled
@Composable
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/FloatingToolbarSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/FloatingToolbarSamples.kt
index ed11f6b..ccaa941 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/FloatingToolbarSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/FloatingToolbarSamples.kt
@@ -61,8 +61,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment.Companion.BottomCenter
-import androidx.compose.ui.Alignment.Companion.CenterEnd
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
@@ -107,7 +106,7 @@
}
}
HorizontalFloatingToolbar(
- modifier = Modifier.align(BottomCenter).offset(y = -ScreenOffset),
+ modifier = Modifier.align(Alignment.BottomCenter).offset(y = -ScreenOffset),
expanded = expanded || isTouchExplorationEnabled,
leadingContent = { leadingContent() },
trailingContent = { trailingContent() },
@@ -157,7 +156,7 @@
}
}
HorizontalFloatingToolbar(
- modifier = Modifier.align(BottomCenter).offset(y = -ScreenOffset),
+ modifier = Modifier.align(Alignment.BottomCenter).offset(y = -ScreenOffset),
expanded = true,
leadingContent = { leadingContent() },
trailingContent = { trailingContent() },
@@ -214,7 +213,7 @@
}
}
VerticalFloatingToolbar(
- modifier = Modifier.align(CenterEnd).offset(x = -ScreenOffset),
+ modifier = Modifier.align(Alignment.CenterEnd).offset(x = -ScreenOffset),
expanded = expanded || isTouchExplorationEnabled,
leadingContent = { leadingContent() },
trailingContent = { trailingContent() },
@@ -264,7 +263,7 @@
}
}
VerticalFloatingToolbar(
- modifier = Modifier.align(CenterEnd).offset(x = -ScreenOffset),
+ modifier = Modifier.align(Alignment.CenterEnd).offset(x = -ScreenOffset),
expanded = true,
leadingContent = { leadingContent() },
trailingContent = { trailingContent() },
@@ -330,7 +329,9 @@
Icon(Icons.Filled.Add, "Localized description")
}
},
- modifier = Modifier.align(BottomCenter).offset(y = -ScreenOffset),
+ modifier =
+ Modifier.align(Alignment.BottomEnd)
+ .offset(x = -ScreenOffset, y = -ScreenOffset),
colors = vibrantColors,
content = {
IconButton(onClick = { /* doSomething() */ }) {
@@ -355,6 +356,62 @@
@Preview
@Sampled
@Composable
+fun CenteredHorizontalFloatingToolbarWithFabSample() {
+ val context = LocalContext.current
+ val isTouchExplorationEnabled = remember {
+ val am = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
+ am.isEnabled && am.isTouchExplorationEnabled
+ }
+ val exitAlwaysScrollBehavior =
+ FloatingToolbarDefaults.exitAlwaysScrollBehavior(exitDirection = Bottom)
+ val vibrantColors = FloatingToolbarDefaults.vibrantFloatingToolbarColors()
+ Scaffold(modifier = Modifier.nestedScroll(exitAlwaysScrollBehavior)) { innerPadding ->
+ Box(Modifier.padding(innerPadding)) {
+ Column(
+ Modifier.fillMaxWidth()
+ .padding(horizontal = 16.dp)
+ .verticalScroll(rememberScrollState())
+ ) {
+ Text(text = remember { LoremIpsum().values.first() })
+ }
+ HorizontalFloatingToolbar(
+ // Always expanded as the toolbar is bottom-centered. We will use a
+ // FloatingToolbarScrollBehavior to hide both the toolbar and its FAB on scroll.
+ expanded = true,
+ floatingActionButton = {
+ // Match the FAB to the vibrantColors. See also StandardFloatingActionButton.
+ FloatingToolbarDefaults.VibrantFloatingActionButton(
+ onClick = { /* doSomething() */ },
+ ) {
+ Icon(Icons.Filled.Add, "Localized description")
+ }
+ },
+ modifier = Modifier.align(Alignment.BottomCenter).offset(y = -ScreenOffset),
+ colors = vibrantColors,
+ scrollBehavior = if (!isTouchExplorationEnabled) exitAlwaysScrollBehavior else null,
+ content = {
+ IconButton(onClick = { /* doSomething() */ }) {
+ Icon(Icons.Filled.Person, contentDescription = "Localized description")
+ }
+ IconButton(onClick = { /* doSomething() */ }) {
+ Icon(Icons.Filled.Edit, contentDescription = "Localized description")
+ }
+ IconButton(onClick = { /* doSomething() */ }) {
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+ }
+ IconButton(onClick = { /* doSomething() */ }) {
+ Icon(Icons.Filled.MoreVert, contentDescription = "Localized description")
+ }
+ },
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Preview
+@Sampled
+@Composable
fun VerticalFloatingToolbarWithFabSample() {
val context = LocalContext.current
val isTouchExplorationEnabled = remember {
@@ -396,7 +453,9 @@
Icon(Icons.Filled.Add, "Localized description")
}
},
- modifier = Modifier.align(CenterEnd).offset(x = -ScreenOffset),
+ modifier =
+ Modifier.align(Alignment.BottomEnd)
+ .offset(x = -ScreenOffset, y = -ScreenOffset),
colors = vibrantColors,
content = {
IconButton(onClick = { /* doSomething() */ }) {
@@ -421,6 +480,62 @@
@Preview
@Sampled
@Composable
+fun CenteredVerticalFloatingToolbarWithFabSample() {
+ val context = LocalContext.current
+ val isTouchExplorationEnabled = remember {
+ val am = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
+ am.isEnabled && am.isTouchExplorationEnabled
+ }
+ val exitAlwaysScrollBehavior =
+ FloatingToolbarDefaults.exitAlwaysScrollBehavior(exitDirection = End)
+ val vibrantColors = FloatingToolbarDefaults.vibrantFloatingToolbarColors()
+ Scaffold(modifier = Modifier.nestedScroll(exitAlwaysScrollBehavior)) { innerPadding ->
+ Box(Modifier.padding(innerPadding)) {
+ Column(
+ Modifier.fillMaxWidth()
+ .padding(horizontal = 16.dp)
+ .verticalScroll(rememberScrollState())
+ ) {
+ Text(text = remember { LoremIpsum().values.first() })
+ }
+ VerticalFloatingToolbar(
+ // Always expanded as the toolbar is right-centered. We will use a
+ // FloatingToolbarScrollBehavior to hide both the toolbar and its FAB on scroll.
+ expanded = true,
+ floatingActionButton = {
+ // Match the FAB to the vibrantColors. See also StandardFloatingActionButton.
+ FloatingToolbarDefaults.VibrantFloatingActionButton(
+ onClick = { /* doSomething() */ },
+ ) {
+ Icon(Icons.Filled.Add, "Localized description")
+ }
+ },
+ modifier = Modifier.align(Alignment.CenterEnd).offset(x = -ScreenOffset),
+ colors = vibrantColors,
+ scrollBehavior = if (!isTouchExplorationEnabled) exitAlwaysScrollBehavior else null,
+ content = {
+ IconButton(onClick = { /* doSomething() */ }) {
+ Icon(Icons.Filled.Person, contentDescription = "Localized description")
+ }
+ IconButton(onClick = { /* doSomething() */ }) {
+ Icon(Icons.Filled.Edit, contentDescription = "Localized description")
+ }
+ IconButton(onClick = { /* doSomething() */ }) {
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+ }
+ IconButton(onClick = { /* doSomething() */ }) {
+ Icon(Icons.Filled.MoreVert, contentDescription = "Localized description")
+ }
+ },
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Preview
+@Sampled
+@Composable
fun HorizontalFloatingToolbarAsScaffoldFabSample() {
val context = LocalContext.current
val isTouchExplorationEnabled = remember {
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/AppBarTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/AppBarTest.kt
index ba662e3..71612d1 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/AppBarTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/AppBarTest.kt
@@ -47,6 +47,7 @@
import androidx.compose.material3.tokens.TypographyKeyTokens
import androidx.compose.runtime.Composable
import androidx.compose.testutils.assertContainsColor
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
@@ -68,6 +69,7 @@
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.getBoundsInRoot
import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.isDisplayed
import androidx.compose.ui.test.junit4.StateRestorationTester
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onAllNodesWithTag
@@ -83,9 +85,11 @@
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpRect
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.height
+import androidx.compose.ui.unit.isSpecified
import androidx.compose.ui.unit.width
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
@@ -179,6 +183,44 @@
}
@Test
+ fun smallTopAppBar_centeredWithSubtitle_positioning() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(Modifier.testTag(TopAppBarTestTag)) {
+ TopAppBar(
+ navigationIcon = { FakeIcon(Modifier.testTag(NavigationIconTestTag)) },
+ title = { Text("Title", Modifier.testTag(TitleTestTag)) },
+ subtitle = { Text("Subtitle", Modifier.testTag(SubtitleTestTag)) },
+ titleHorizontalAlignment = Alignment.CenterHorizontally,
+ actions = { FakeIcon(Modifier.testTag(ActionsTestTag)) }
+ )
+ }
+ }
+ assertSmallDefaultPositioning(isCenteredTitle = true)
+ }
+
+ // Ensure that the titles are still centered even when there are more actions.
+ @Test
+ fun smallTopAppBar_centeredWithSubtitle_multiActions_positioning() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(Modifier.testTag(TopAppBarTestTag)) {
+ TopAppBar(
+ navigationIcon = { FakeIcon(Modifier.testTag(NavigationIconTestTag)) },
+ title = { Text("Title", Modifier.testTag(TitleTestTag)) },
+ subtitle = { Text("Subtitle", Modifier.testTag(SubtitleTestTag)) },
+ titleHorizontalAlignment = Alignment.CenterHorizontally,
+ actions = {
+ FakeIcon(Modifier)
+ // Apply the test tag just to the action at the end. We will test its
+ // position at the assertSmallDefaultPositioning.
+ FakeIcon(Modifier.testTag(ActionsTestTag))
+ }
+ )
+ }
+ }
+ assertSmallDefaultPositioning(isCenteredTitle = true)
+ }
+
+ @Test
fun smallTopAppBar_titleDefaultStyle() {
var textStyle: TextStyle? = null
var expectedTextStyle: TextStyle? = null
@@ -1715,6 +1757,77 @@
}
@Test
+ fun topAppBar_customTwoRows_expanded() {
+ val collapsedHeightDp = 36.dp
+ val expandedHeightDp = 112.dp
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(Modifier.testTag(TopAppBarTestTag)) {
+ TwoRowsTopAppBar(
+ title = { expanded ->
+ if (expanded) {
+ Text("Expanded Title", Modifier.testTag(TitleTestTag))
+ } else {
+ Text("Collapsed Title", Modifier.testTag(TitleTestTag))
+ }
+ },
+ navigationIcon = { FakeIcon(Modifier.testTag(NavigationIconTestTag)) },
+ actions = { FakeIcon(Modifier.testTag(ActionsTestTag)) },
+ height = { expanded ->
+ if (expanded) {
+ expandedHeightDp
+ } else {
+ collapsedHeightDp
+ }
+ }
+ )
+ }
+ }
+
+ assertMediumOrLargeDefaultPositioning(
+ appBarCollapsedHeight = collapsedHeightDp,
+ appBarExpandedHeight = expandedHeightDp
+ )
+ }
+
+ @Test
+ fun topAppBar_customTwoRows_scrolled_positioning() {
+ val collapsedHeightDp = 40.dp
+ val expandedHeightDp = 120.dp
+ val windowInsets = WindowInsets(13.dp, 13.dp, 13.dp, 13.dp)
+ val content =
+ @Composable { scrollBehavior: TopAppBarScrollBehavior? ->
+ Box(Modifier.testTag(TopAppBarTestTag)) {
+ TwoRowsTopAppBar(
+ title = { expanded ->
+ if (expanded) {
+ Text("Expanded Title", Modifier.testTag(TitleTestTag))
+ } else {
+ Text("Collapsed Title", Modifier.testTag(TitleTestTag))
+ }
+ },
+ navigationIcon = { FakeIcon(Modifier.testTag(NavigationIconTestTag)) },
+ actions = { FakeIcon(Modifier.testTag(ActionsTestTag)) },
+ height = { expanded ->
+ if (expanded) {
+ expandedHeightDp
+ } else {
+ collapsedHeightDp
+ }
+ },
+ windowInsets = windowInsets,
+ scrollBehavior = scrollBehavior,
+ )
+ }
+ }
+ assertMediumOrLargeScrolledHeight(
+ appBarMaxHeight = expandedHeightDp,
+ appBarMinHeight = collapsedHeightDp,
+ windowInsets,
+ content
+ )
+ }
+
+ @Test
fun bottomAppBarWithFAB_heightIsFromSpec() {
rule
.setMaterialContentForSizeAssertions {
@@ -2077,13 +2190,30 @@
)
val titleNode = rule.onNodeWithTag(TitleTestTag)
+ val subtitleNode = rule.onNodeWithTag(SubtitleTestTag)
+ val subtitleBounds =
+ with(subtitleNode) {
+ if (isDisplayed()) {
+ getUnclippedBoundsInRoot()
+ } else {
+ DpRect(0.dp, 0.dp, 0.dp, 0.dp)
+ }
+ }
// Title should be vertically centered
- titleNode.assertTopPositionInRootIsEqualTo((appBarBounds.height - titleBounds.height) / 2)
+ titleNode.assertTopPositionInRootIsEqualTo(
+ (appBarBounds.height - titleBounds.height - subtitleBounds.height) / 2
+ )
if (isCenteredTitle) {
// Title should be horizontally centered
titleNode.assertLeftPositionInRootIsEqualTo(
(appBarBounds.width - titleBounds.width) / 2
)
+ if (subtitleNode.isDisplayed()) {
+ // Subtitle should be horizontally centered
+ subtitleNode.assertLeftPositionInRootIsEqualTo(
+ (appBarBounds.width - subtitleBounds.width) / 2
+ )
+ }
} else {
// Title should be 56.dp from the start
// 4.dp padding for the whole app bar + 48.dp icon size + 4.dp title padding.
@@ -2101,13 +2231,13 @@
}
/**
- * Checks the app bar's components positioning when it's a [MediumTopAppBar] or a
- * [LargeTopAppBar].
+ * Checks the app bar's components positioning when it's a [MediumTopAppBar], [LargeTopAppBar],
+ * or a [TwoRowsTopAppBar].
*/
private fun assertMediumOrLargeDefaultPositioning(
appBarCollapsedHeight: Dp,
appBarExpandedHeight: Dp,
- bottomTextPadding: Dp
+ bottomTextPadding: Dp = Dp.Unspecified
) {
val appBarBounds = rule.onNodeWithTag(TopAppBarTestTag).getUnclippedBoundsInRoot()
appBarBounds.height.assertIsEqualTo(appBarExpandedHeight, "top app bar height")
@@ -2155,18 +2285,21 @@
// Bottom title should be 16.dp from the start.
.assertLeftPositionInRootIsEqualTo(16.dp)
- // Check if the bottom text baseline is at the expected distance from the bottom of the
- // app bar.
- val bottomTextBaselineY = bottomTitleBounds.top + bottomTitleNode.getLastBaselinePosition()
- (bottomAppBarBottomEdgeY - bottomTextBaselineY).assertIsEqualTo(
- bottomTextPadding,
- "text baseline distance from the bottom"
- )
+ if (bottomTextPadding.isSpecified) {
+ // Check if the bottom text baseline is at the expected distance from the bottom of the
+ // app bar.
+ val bottomTextBaselineY =
+ bottomTitleBounds.top + bottomTitleNode.getLastBaselinePosition()
+ (bottomAppBarBottomEdgeY - bottomTextBaselineY).assertIsEqualTo(
+ bottomTextPadding,
+ "text baseline distance from the bottom"
+ )
+ }
}
/**
- * Checks that changing values at a [MediumTopAppBar] or a [LargeTopAppBar] scroll behavior
- * affects the height of the app bar.
+ * Checks that changing values at a [MediumTopAppBar], [LargeTopAppBar], or [TwoRowsTopAppBar]
+ * scroll behavior affects the height of the app bar.
*
* This check partially and fully collapses the app bar to test its height.
*
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/FloatingToolbarTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/FloatingToolbarTest.kt
index b9b0ac3..de628ad 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/FloatingToolbarTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/FloatingToolbarTest.kt
@@ -26,25 +26,32 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Person
+import androidx.compose.material3.FloatingToolbarDefaults.ScreenOffset
import androidx.compose.material3.FloatingToolbarDefaults.floatingToolbarVerticalNestedScroll
import androidx.compose.material3.FloatingToolbarExitDirection.Companion.Bottom
import androidx.compose.material3.FloatingToolbarExitDirection.Companion.End
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.testutils.assertContainsColor
import androidx.compose.testutils.assertPixels
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.assertCountEquals
@@ -64,6 +71,7 @@
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeDown
import androidx.compose.ui.test.swipeUp
+import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
@@ -92,7 +100,7 @@
val scrollHeightOffsetDp = 20.dp
var scrollHeightOffsetPx = 0f
var containerSizePx = 0f
- val screenOffsetDp = FloatingToolbarDefaults.ScreenOffset
+ val screenOffsetDp = ScreenOffset
var screenOffsetPx = 0f
rule.setMaterialContent(lightColorScheme()) {
@@ -143,7 +151,7 @@
val scrollHeightOffsetDp = 20.dp
var scrollHeightOffsetPx = 0f
var containerSizePx = 0f
- val screenOffsetDp = FloatingToolbarDefaults.ScreenOffset
+ val screenOffsetDp = ScreenOffset
var screenOffsetPx = 0f
rule.setMaterialContent(lightColorScheme()) {
@@ -918,6 +926,76 @@
rule.runOnIdle { assertThat(expanded).isEqualTo(false) }
}
+ @Test
+ fun verticalFloatingToolbar_scrollBehavior() {
+ rule.setMaterialContent(lightColorScheme()) {
+ val scrollBehavior =
+ FloatingToolbarDefaults.exitAlwaysScrollBehavior(exitDirection = End)
+ Scaffold(modifier = Modifier.nestedScroll(scrollBehavior).testTag(mainLayoutTag)) {
+ innerPadding ->
+ Box(Modifier.padding(innerPadding)) {
+ Column(Modifier.fillMaxWidth().verticalScroll(rememberScrollState())) {
+ Text(text = remember { LoremIpsum().values.first() })
+ }
+ VerticalFloatingToolbar(
+ expanded = true,
+ floatingActionButton = { ToolbarFab() },
+ modifier = Modifier.align(Alignment.CenterEnd).offset(x = -ScreenOffset),
+ scrollBehavior = scrollBehavior,
+ ) {
+ ToolbarContent()
+ }
+ }
+ }
+ }
+
+ // Check that the FAB and a sample from the toolbar content are displayed.
+ rule.onNodeWithTag(FloatingActionButtonTestTag).assertIsDisplayed()
+ rule.onNodeWithTag(FloatingToolbarContentLastItemTestTag).assertIsDisplayed()
+
+ // Swipe the content up to collapse the FloatingToolbar.
+ rule.onNodeWithTag(mainLayoutTag).performTouchInput { swipeUp(bottom, bottom - 1000) }
+ rule.waitForIdle()
+ // Check that the FAB and a sample from the toolbar content are not displayed.
+ rule.onNodeWithTag(FloatingActionButtonTestTag).assertIsNotDisplayed()
+ rule.onNodeWithTag(FloatingToolbarContentLastItemTestTag).assertIsNotDisplayed()
+ }
+
+ @Test
+ fun horizontalFloatingToolbar_scrollBehavior() {
+ rule.setMaterialContent(lightColorScheme()) {
+ val scrollBehavior =
+ FloatingToolbarDefaults.exitAlwaysScrollBehavior(exitDirection = End)
+ Scaffold(modifier = Modifier.nestedScroll(scrollBehavior).testTag(mainLayoutTag)) {
+ innerPadding ->
+ Box(Modifier.padding(innerPadding)) {
+ Column(Modifier.fillMaxWidth().verticalScroll(rememberScrollState())) {
+ Text(text = remember { LoremIpsum().values.first() })
+ }
+ HorizontalFloatingToolbar(
+ expanded = true,
+ floatingActionButton = { ToolbarFab() },
+ modifier = Modifier.align(Alignment.BottomCenter).offset(y = -ScreenOffset),
+ scrollBehavior = scrollBehavior,
+ ) {
+ ToolbarContent()
+ }
+ }
+ }
+ }
+
+ // Check that the FAB and a sample from the toolbar content are displayed.
+ rule.onNodeWithTag(FloatingActionButtonTestTag).assertIsDisplayed()
+ rule.onNodeWithTag(FloatingToolbarContentLastItemTestTag).assertIsDisplayed()
+
+ // Swipe the content up to collapse the FloatingToolbar.
+ rule.onNodeWithTag(mainLayoutTag).performTouchInput { swipeUp(bottom, bottom - 1000) }
+ rule.waitForIdle()
+ // Check that the FAB and a sample from the toolbar content are not displayed.
+ rule.onNodeWithTag(FloatingActionButtonTestTag).assertIsNotDisplayed()
+ rule.onNodeWithTag(FloatingToolbarContentLastItemTestTag).assertIsNotDisplayed()
+ }
+
@Composable
private fun VerticalNestedScrollTestContent(
onExpanded: () -> Unit,
diff --git a/compose/material3/material3/src/androidMain/res/values-iw/strings.xml b/compose/material3/material3/src/androidMain/res/values-iw/strings.xml
index a5a414f..ed49d3f 100644
--- a/compose/material3/material3/src/androidMain/res/values-iw/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-iw/strings.xml
@@ -28,7 +28,7 @@
<string name="m3c_date_picker_title" msgid="7430790972741451689">"בחירת תאריך"</string>
<string name="m3c_date_picker_headline" msgid="7605002211875882969">"התאריך הנבחר"</string>
<string name="m3c_date_picker_switch_to_year_selection" msgid="791651718641787594">"החלפה לבחירה של שנה"</string>
- <string name="m3c_date_picker_switch_to_day_selection" msgid="395627960681594326">"יש להחליק כדי לבחור שנה, או להקיש כדי לחזור לבחירת היום"</string>
+ <string name="m3c_date_picker_switch_to_day_selection" msgid="395627960681594326">"יש להחליק כדי לבחור שנה, או ללחוץ כדי לחזור לבחירת היום"</string>
<string name="m3c_date_picker_switch_to_next_month" msgid="7142101321095356500">"מעבר לחודש הבא"</string>
<string name="m3c_date_picker_switch_to_previous_month" msgid="228438865139394590">"מעבר לחודש הקודם"</string>
<string name="m3c_date_picker_navigate_to_year_description" msgid="8436650776581492840">"ניווט לשנת %1$s"</string>
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
index 05287c9..610a237 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
@@ -99,6 +99,7 @@
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.isFinite
@@ -618,7 +619,8 @@
* will be allowed to expand. The expanded height is expected to be greater or equal to the
* [collapsedHeight], and the function will throw an [IllegalArgumentException] otherwise. Also,
* this value must be specified and finite, otherwise it will be ignored and replaced with
- * [TopAppBarDefaults.MediumAppBarExpandedHeight].
+ * [TopAppBarDefaults.MediumFlexibleAppBarWithSubtitleExpandedHeight] or
+ * [TopAppBarDefaults.MediumFlexibleAppBarWithoutSubtitleExpandedHeight].
* @param windowInsets a window insets that app bar will respect.
* @param colors [TopAppBarColors] that will be used to resolve the colors used for this top app bar
* in different states. See [TopAppBarDefaults.topAppBarColors].
@@ -870,7 +872,8 @@
* will be allowed to expand. The expanded height is expected to be greater or equal to the
* [collapsedHeight], and the function will throw an [IllegalArgumentException] otherwise. Also,
* this value must be specified and finite, otherwise it will be ignored and replaced with
- * [TopAppBarDefaults.LargeAppBarExpandedHeight].
+ * [TopAppBarDefaults.LargeFlexibleAppBarWithSubtitleExpandedHeight] or
+ * [TopAppBarDefaults.LargeFlexibleAppBarWithoutSubtitleExpandedHeight].
* @param windowInsets a window insets that app bar will respect.
* @param colors [TopAppBarColors] that will be used to resolve the colors used for this top app bar
* in different states. See [TopAppBarDefaults.topAppBarColors].
@@ -938,6 +941,112 @@
)
/**
+ * A basic two-rows Material Design top app bar.
+ *
+ * Top app bars display information and actions at the top of a screen.
+ *
+ * ![Two rows top app bar
+ * image](https://developer.android.com/images/reference/androidx/compose/material3/medium-top-app-bar.png)
+ *
+ * This two-rows top app bar has slots for titles and subtitles, navigation icon, and actions. In
+ * its default expanded state, the expanded title and subtitle are displayed in a second row under
+ * the navigation and actions.
+ *
+ * By default, the two-rows top app bar will apply the [MediumFlexibleTopAppBar] text styles to the
+ * expanded and collapsed titles. You may override that by applying your own style to the
+ * Composition passed into those collapsed and expanded title slots.
+ *
+ * A two-rows top app bar that uses a [scrollBehavior] to customize its nested scrolling behavior
+ * when working in conjunction with scrolling content looks like:
+ *
+ * @sample androidx.compose.material3.samples.CustomTwoRowsTopAppBar
+ * @param title a labmda for providing a title to be displayed in the top app bar in collapsed and
+ * expanded states. By default a small-app-bar [TextStyle] is applied to the Composition, and you
+ * may override it by wrapping your provided component with a composition local. Note that unlike
+ * the large or medium top app bars, the `TwoRowsTopAppBar` does not append bottom padding to the
+ * expanded title Composable by default. Padding should be applied directly to the provided
+ * expanded title, or to the [subtitle] that appears below it.
+ * @param modifier the [Modifier] to be applied to this top app bar
+ * @param subtitle a labmda for providing an optional subtitle to be displayed in the top app bar in
+ * collapsed and expanded states.
+ * @param navigationIcon the navigation icon displayed at the start of the top app bar. This should
+ * typically be an [IconButton] or [IconToggleButton].
+ * @param actions the actions displayed at the end of the top app bar. This should typically be
+ * [IconButton]s. The default layout here is a [Row], so icons inside will be placed horizontally.
+ * @param titleHorizontalAlignment the horizontal alignment of the title and subtitle
+ * @param height a lambda that provides the app bar's height in collapsed or expanded states. When a
+ * [scrollBehavior] causes the app bar to collapse or expand, the expanded `height` represents the
+ * _total_ height the app-bar will expand to. In case the provided height values are
+ * [Dp.Unspecified] or [Dp.Infinity], the height will fallback to the [MediumFlexibleTopAppBar]
+ * defaults. Note that the height values may be adjusted to support displaying larger fonts and
+ * that the expanded height is expected to be greater or equal to the collapsed height, and the
+ * function will throw an [IllegalArgumentException] otherwise.
+ * @param windowInsets a window insets that app bar will respect.
+ * @param colors [TopAppBarColors] that will be used to resolve the colors used for this top app bar
+ * in different states. See [TopAppBarDefaults.topAppBarColors].
+ * @param scrollBehavior a [TopAppBarScrollBehavior] which holds various offset values that will be
+ * applied by this top app bar to set up its height and colors. A scroll behavior is designed to
+ * work in conjunction with a scrolled content to change the top app bar appearance as the content
+ * scrolls. See [TopAppBarScrollBehavior.nestedScrollConnection].
+ * @throws IllegalArgumentException if the provided [expandedHeight] is smaller to the
+ * [collapsedHeight]
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@ExperimentalMaterial3ExpressiveApi
+@Composable
+fun TwoRowsTopAppBar(
+ title: @Composable (expanded: Boolean) -> Unit,
+ modifier: Modifier = Modifier,
+ subtitle: (@Composable (expanded: Boolean) -> Unit?) = { null },
+ navigationIcon: @Composable () -> Unit = {},
+ actions: @Composable RowScope.() -> Unit = {},
+ titleHorizontalAlignment: Alignment.Horizontal = Alignment.Start,
+ height: (expanded: Boolean) -> Dp = { Dp.Unspecified },
+ windowInsets: WindowInsets = TopAppBarDefaults.windowInsets,
+ colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(),
+ scrollBehavior: TopAppBarScrollBehavior? = null
+) {
+ var expandedHeight = height(true)
+ var collapsedHeight = height(false)
+
+ if (expandedHeight == Dp.Unspecified || expandedHeight == Dp.Infinity) {
+ // Default to the medium app-bar expanded height
+ expandedHeight =
+ if (subtitle(true) != null) {
+ TopAppBarDefaults.MediumFlexibleAppBarWithSubtitleExpandedHeight
+ } else {
+ TopAppBarDefaults.MediumFlexibleAppBarWithoutSubtitleExpandedHeight
+ }
+ }
+
+ if (collapsedHeight == Dp.Unspecified || collapsedHeight == Dp.Infinity) {
+ // Default to the medium app-bar collapsed height
+ collapsedHeight = TopAppBarDefaults.MediumAppBarCollapsedHeight
+ }
+
+ TwoRowsTopAppBar(
+ title = { title(true) },
+ titleTextStyle = AppBarMediumFlexibleTokens.TitleFont.value,
+ smallTitleTextStyle = AppBarSmallTokens.TitleFont.value,
+ titleBottomPadding = 0.dp,
+ smallTitle = { title(false) },
+ modifier = modifier,
+ subtitle = { subtitle.invoke(true) },
+ subtitleTextStyle = AppBarMediumFlexibleTokens.SubtitleFont.value,
+ smallSubtitle = { subtitle.invoke(false) },
+ smallSubtitleTextStyle = AppBarSmallTokens.SubtitleFont.value,
+ titleHorizontalAlignment = titleHorizontalAlignment,
+ navigationIcon = navigationIcon,
+ actions = actions,
+ collapsedHeight = collapsedHeight,
+ expandedHeight = expandedHeight,
+ windowInsets = windowInsets,
+ colors = colors,
+ scrollBehavior = scrollBehavior
+ )
+}
+
+/**
* <a href="https://m3.material.io/components/bottom-app-bar/overview" class="external"
* target="_blank">Material Design bottom app bar</a>.
*
@@ -2486,7 +2595,7 @@
hideTitleSemantics = false,
navigationIcon = navigationIcon,
actions = actionsRow,
- expandedHeight = expandedHeight
+ height = expandedHeight
)
}
}
@@ -2526,8 +2635,7 @@
require(expandedHeight >= collapsedHeight) {
"The expandedHeight is expected to be greater or equal to the collapsedHeight"
}
- val titleBottomPaddingPx: Int
- LocalDensity.current.run { titleBottomPaddingPx = titleBottomPadding.roundToPx() }
+ val titleBottomPaddingPx = with(LocalDensity.current) { titleBottomPadding.roundToPx() }
// Obtain the container Color from the TopAppBarColors using the `collapsedFraction`, as the
// bottom part of this TwoRowsTopAppBar changes color at the same rate the app bar expands or
@@ -2604,7 +2712,7 @@
hideTitleSemantics = hideTopRowSemantics,
navigationIcon = navigationIcon,
actions = actionsRow,
- expandedHeight = collapsedHeight
+ height = collapsedHeight
)
TopAppBarLayout(
modifier =
@@ -2631,7 +2739,7 @@
hideTitleSemantics = hideBottomRowSemantics,
navigationIcon = {},
actions = {},
- expandedHeight = expandedHeight - collapsedHeight
+ height = expandedHeight - collapsedHeight
)
}
}
@@ -2674,7 +2782,7 @@
* the same time, when animating between collapsed / expanded states.
* @param navigationIcon a navigation icon [Composable]
* @param actions actions [Composable]
- * @param expandedHeight this app bar's maximum height
+ * @param height this app bar's requested height
*/
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@@ -2696,7 +2804,7 @@
hideTitleSemantics: Boolean,
navigationIcon: @Composable () -> Unit,
actions: @Composable () -> Unit,
- expandedHeight: Dp,
+ height: Dp,
) {
Layout(
{
@@ -2761,14 +2869,14 @@
titleVerticalArrangement,
titleHorizontalAlignment,
titleBottomPadding,
- expandedHeight
+ height
) {
TopAppBarMeasurePolicy(
scrolledOffset,
titleVerticalArrangement,
titleHorizontalAlignment,
titleBottomPadding,
- expandedHeight
+ height
)
}
)
@@ -2780,7 +2888,7 @@
val titleVerticalArrangement: Arrangement.Vertical,
val titleHorizontalAlignment: Alignment.Horizontal,
val titleBottomPadding: Int,
- val expandedHeight: Dp
+ val height: Dp
) : MeasurePolicy {
override fun MeasureScope.measure(
measurables: List<Measurable>,
@@ -2820,7 +2928,7 @@
val scrolledOffsetValue = scrolledOffset()
val heightOffset = if (scrolledOffsetValue.isNaN()) 0 else scrolledOffsetValue.roundToInt()
- val maxLayoutHeight = max(expandedHeight.roundToPx(), titlePlaceable.height)
+ val maxLayoutHeight = max(height.roundToPx(), titlePlaceable.height)
val layoutHeight =
if (constraints.maxHeight == Constraints.Infinity) {
maxLayoutHeight
@@ -2849,7 +2957,7 @@
width: Int
): Int {
return max(
- expandedHeight.roundToPx(),
+ height.roundToPx(),
measurables.fastMaxOfOrNull { it.minIntrinsicHeight(width) } ?: 0
)
}
@@ -2864,7 +2972,7 @@
width: Int
): Int {
return max(
- expandedHeight.roundToPx(),
+ height.roundToPx(),
measurables.fastMaxOfOrNull { it.maxIntrinsicHeight(width) } ?: 0
)
}
@@ -2885,37 +2993,32 @@
y = (layoutHeight - navigationIconPlaceable.height) / 2
)
- // Title
- titlePlaceable.placeRelative(
- x =
- when (titleHorizontalAlignment) {
- Alignment.CenterHorizontally -> {
- var baseX = (constraints.maxWidth - titlePlaceable.width) / 2
- if (baseX < navigationIconPlaceable.width) {
- // May happen if the navigation is wider than the actions and the
- // title is long. In this case, prioritize showing more of the title
- // by offsetting it to the right.
- baseX += (navigationIconPlaceable.width - baseX)
- } else if (
- baseX + titlePlaceable.width >
- constraints.maxWidth - actionIconsPlaceable.width
- ) {
- // May happen if the actions are wider than the navigation and the
- // title is long. In this case, offset to the left.
- baseX +=
- ((constraints.maxWidth - actionIconsPlaceable.width) -
- (baseX + titlePlaceable.width))
- }
- baseX
- }
- Alignment.End ->
- constraints.maxWidth - titlePlaceable.width - actionIconsPlaceable.width
- // Fallback to Alignment.Start.
- // A TopAppBarTitleInset will make sure the title is offset in case the
- // navigation icon is missing.
- else -> max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width)
- },
- y =
+ titlePlaceable.let {
+ val start = max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width)
+ val end = actionIconsPlaceable.width
+
+ // Align using the maxWidth. We will adjust the position later according to the
+ // start and end. This is done to ensure that a center alignment is still maintained
+ // when the start and end have different widths. Note that the title is centered
+ // relative to the entire app bar width, and not just centered between the
+ // navigation icon and the actions.
+ var titleX =
+ titleHorizontalAlignment.align(
+ size = titlePlaceable.width,
+ space = constraints.maxWidth,
+ // Using Ltr as we call placeRelative later on.
+ layoutDirection = LayoutDirection.Ltr
+ )
+ // Reposition the title based on the start and the end (i.e. the navigation and
+ // action widths).
+ if (titleX < start) {
+ titleX += (start - titleX)
+ } else if (titleX + titlePlaceable.width > constraints.maxWidth - end) {
+ titleX += ((constraints.maxWidth - end) - (titleX + titlePlaceable.width))
+ }
+
+ // The titleVerticalArrangement is always one of Center or Bottom.
+ val titleY =
when (titleVerticalArrangement) {
Arrangement.Center -> (layoutHeight - titlePlaceable.height) / 2
// Apply bottom padding from the title's baseline only when the Arrangement
@@ -2943,7 +3046,9 @@
// Arrangement.Top
else -> 0
}
- )
+
+ it.placeRelative(titleX, titleY)
+ }
// Action icons
actionIconsPlaceable.placeRelative(
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingToolbar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingToolbar.kt
index e0e61a2..6ac3dec 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingToolbar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingToolbar.kt
@@ -51,12 +51,15 @@
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.FloatingToolbarDefaults.horizontalEnterTransition
import androidx.compose.material3.FloatingToolbarDefaults.horizontalExitTransition
+import androidx.compose.material3.FloatingToolbarDefaults.standardFloatingToolbarColors
import androidx.compose.material3.FloatingToolbarDefaults.verticalEnterTransition
import androidx.compose.material3.FloatingToolbarDefaults.verticalExitTransition
+import androidx.compose.material3.FloatingToolbarDefaults.vibrantFloatingToolbarColors
import androidx.compose.material3.FloatingToolbarExitDirection.Companion.Bottom
import androidx.compose.material3.FloatingToolbarExitDirection.Companion.End
import androidx.compose.material3.FloatingToolbarExitDirection.Companion.Start
import androidx.compose.material3.FloatingToolbarExitDirection.Companion.Top
+import androidx.compose.material3.FloatingToolbarState.Companion.Saver
import androidx.compose.material3.tokens.ColorSchemeKeyTokens
import androidx.compose.material3.tokens.ElevationTokens
import androidx.compose.material3.tokens.FabBaselineTokens
@@ -262,11 +265,22 @@
* other content, and even in a `Scaffold`'s floating action button slot. Its [expanded] flag
* controls the visibility of the actions with a slide animations.
*
- * Note that if your app uses a `Snackbar`, it's best to position the toolbar in a `Scaffold`'s FAB
- * slot. This ensures the `Snackbar` appears above the toolbar, preventing any visual overlap or
- * interference.
+ * In case the toolbar is aligned to the right or the left of the screen, you may apply a
+ * [FloatingToolbarDefaults.floatingToolbarVerticalNestedScroll] `Modifier` to update the [expanded]
+ * state when scrolling occurs, as this sample shows:
*
* @sample androidx.compose.material3.samples.HorizontalFloatingToolbarWithFabSample
+ *
+ * In case the toolbar is positioned along a center edge of the screen (like top or bottom center),
+ * it's recommended to maintain the expanded state on scroll and to attach a [scrollBehavior] in
+ * order to hide or show the entire component, as this sample shows:
+ *
+ * @sample androidx.compose.material3.samples.CenteredHorizontalFloatingToolbarWithFabSample
+ *
+ * Note that if your app uses a `Snackbar`, it's best to position the toolbar in a `Scaffold`'s FAB
+ * slot. This ensures the `Snackbar` appears above the toolbar, preventing any visual overlap or
+ * interference. See this sample:
+ *
* @sample androidx.compose.material3.samples.HorizontalFloatingToolbarAsScaffoldFabSample
* @param expanded whether the floating toolbar is expanded or not. In its expanded state, the FAB
* and the toolbar content are organized horizontally. Otherwise, only the FAB is visible.
@@ -279,6 +293,12 @@
* [FloatingToolbarDefaults.vibrantFloatingToolbarColors] which you can use or modify. See also
* [floatingActionButton] for more information on the right FAB to use for proper styling.
* @param contentPadding the padding applied to the content of this floating toolbar.
+ * @param scrollBehavior a [FloatingToolbarScrollBehavior]. If provided, this FloatingToolbar will
+ * automatically react to scrolling. If your toolbar is positioned along a center edge of the
+ * screen (like top or bottom center), it's best to use this scroll behavior to make the entire
+ * toolbar scroll off-screen as the user scrolls. This would prevent the FAB from appearing
+ * off-center, which may occur in this case when using the [expanded] flag to simply expand or
+ * collapse the toolbar.
* @param shape the shape used for this floating toolbar content.
* @param floatingActionButtonPosition the position of the floating toolbar's floating action
* button. By default, the FAB is placed at the end of the toolbar (i.e. aligned to the right in
@@ -296,6 +316,7 @@
modifier: Modifier = Modifier,
colors: FloatingToolbarColors = FloatingToolbarDefaults.standardFloatingToolbarColors(),
contentPadding: PaddingValues = FloatingToolbarDefaults.ContentPadding,
+ scrollBehavior: FloatingToolbarScrollBehavior? = null,
shape: Shape = FloatingToolbarDefaults.ContainerShape,
floatingActionButtonPosition: FloatingToolbarHorizontalFabPosition =
FloatingToolbarHorizontalFabPosition.End,
@@ -308,6 +329,7 @@
colors = colors,
toolbarToFabGap = FloatingToolbarDefaults.ToolbarToFabGap,
toolbarContentPadding = contentPadding,
+ scrollBehavior = scrollBehavior,
toolbarShape = shape,
animationSpec = animationSpec,
fab = floatingActionButton,
@@ -469,7 +491,17 @@
* other content, and its [expanded] flag controls the visibility of the actions with a slide
* animations.
*
+ * In case the toolbar is aligned to the top or the bottom of the screen, you may apply a
+ * [FloatingToolbarDefaults.floatingToolbarVerticalNestedScroll] `Modifier` to update the [expanded]
+ * state when scrolling occurs, as this sample shows:
+ *
* @sample androidx.compose.material3.samples.VerticalFloatingToolbarWithFabSample
+ *
+ * In case the toolbar is positioned along a center edge of the screen (like left or right center),
+ * it's recommended to maintain the expanded state on scroll and to attach a [scrollBehavior] in
+ * order to hide or show the entire component, as this sample shows:
+ *
+ * @sample androidx.compose.material3.samples.CenteredVerticalFloatingToolbarWithFabSample
* @param expanded whether the floating toolbar is expanded or not. In its expanded state, the FAB
* and the toolbar content are organized vertically. Otherwise, only the FAB is visible.
* @param floatingActionButton a floating action button to be displayed by the toolbar. It's
@@ -481,6 +513,12 @@
* [FloatingToolbarDefaults.vibrantFloatingToolbarColors] which you can use or modify. See also
* [floatingActionButton] for more information on the right FAB to use for proper styling.
* @param contentPadding the padding applied to the content of this floating toolbar.
+ * @param scrollBehavior a [FloatingToolbarScrollBehavior]. If provided, this FloatingToolbar will
+ * automatically react to scrolling. If your toolbar is positioned along a center edge of the
+ * screen (like left or right center), it's best to use this scroll behavior to make the entire
+ * toolbar scroll off-screen as the user scrolls. This would prevent the FAB from appearing
+ * off-center, which may occur in this case when using the [expanded] flag to simply expand or
+ * collapse the toolbar.
* @param shape the shape used for this floating toolbar content.
* @param floatingActionButtonPosition the position of the floating toolbar's floating action
* button. By default, the FAB is placed at the bottom of the toolbar (i.e. aligned to the
@@ -498,6 +536,7 @@
modifier: Modifier = Modifier,
colors: FloatingToolbarColors = FloatingToolbarDefaults.standardFloatingToolbarColors(),
contentPadding: PaddingValues = FloatingToolbarDefaults.ContentPadding,
+ scrollBehavior: FloatingToolbarScrollBehavior? = null,
shape: Shape = FloatingToolbarDefaults.ContainerShape,
floatingActionButtonPosition: FloatingToolbarVerticalFabPosition =
FloatingToolbarVerticalFabPosition.Bottom,
@@ -510,6 +549,7 @@
colors = colors,
toolbarToFabGap = FloatingToolbarDefaults.ToolbarToFabGap,
toolbarContentPadding = contentPadding,
+ scrollBehavior = scrollBehavior,
toolbarShape = shape,
animationSpec = animationSpec,
fab = floatingActionButton,
@@ -1489,6 +1529,7 @@
colors: FloatingToolbarColors,
toolbarToFabGap: Dp,
toolbarContentPadding: PaddingValues,
+ scrollBehavior: FloatingToolbarScrollBehavior?,
toolbarShape: Shape,
animationSpec: FiniteAnimationSpec<Float>,
fab: @Composable () -> Unit,
@@ -1514,7 +1555,12 @@
fab()
},
modifier =
- modifier.defaultMinSize(minHeight = FloatingToolbarDefaults.FabSizeRange.endInclusive)
+ modifier
+ .defaultMinSize(minHeight = FloatingToolbarDefaults.FabSizeRange.endInclusive)
+ .then(
+ scrollBehavior?.let { with(it) { Modifier.floatingScrollBehavior() } }
+ ?: Modifier
+ )
) { measurables, constraints ->
val toolbarMeasurable = measurables[0]
val fabMeasurable = measurables[1]
@@ -1596,6 +1642,7 @@
colors: FloatingToolbarColors,
toolbarToFabGap: Dp,
toolbarContentPadding: PaddingValues,
+ scrollBehavior: FloatingToolbarScrollBehavior?,
toolbarShape: Shape,
animationSpec: FiniteAnimationSpec<Float>,
fab: @Composable () -> Unit,
@@ -1621,7 +1668,12 @@
fab()
},
modifier =
- modifier.defaultMinSize(minWidth = FloatingToolbarDefaults.FabSizeRange.endInclusive)
+ modifier
+ .defaultMinSize(minWidth = FloatingToolbarDefaults.FabSizeRange.endInclusive)
+ .then(
+ scrollBehavior?.let { with(it) { Modifier.floatingScrollBehavior() } }
+ ?: Modifier
+ )
) { measurables, constraints ->
val toolbarMeasurable = measurables[0]
val fabMeasurable = measurables[1]
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt
index 5226452..f11dc72 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt
@@ -190,8 +190,7 @@
import kotlinx.coroutines.launch
/**
- * <a href="https://m3.material.io/components/time-pickers/overview" class="external"
- * target="_blank">Material Design time picker</a>.
+ * [Material Design time picker](https://m3.material.io/components/time-pickers/overview)
*
* Time pickers help users select and set a specific time.
*
diff --git a/compose/ui/ui-geometry/OWNERS b/compose/ui/ui-geometry/OWNERS
index c3ab1d5..f766d4a 100644
--- a/compose/ui/ui-geometry/OWNERS
+++ b/compose/ui/ui-geometry/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 1070768
[email protected]
[email protected]
diff --git a/compose/ui/ui-graphics/OWNERS b/compose/ui/ui-graphics/OWNERS
index c3ab1d5..7c7c0e4 100644
--- a/compose/ui/ui-graphics/OWNERS
+++ b/compose/ui/ui-graphics/OWNERS
@@ -1,3 +1,3 @@
# Bug component: 1070768
[email protected]
[email protected]
[email protected]
diff --git a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayerTest.kt b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayerTest.kt
index 658156b..2b840b3 100644
--- a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayerTest.kt
+++ b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayerTest.kt
@@ -1700,6 +1700,31 @@
}
@Test
+ fun testChildLayerHasReferenceToParentLayer() {
+ lateinit var layer1: GraphicsLayer
+ lateinit var layer2: GraphicsLayer
+ graphicsLayerTest(
+ block = { context ->
+ // creating new layers will also schedule a persistence pass in Handler
+ layer1 = context.createGraphicsLayer()
+ layer2 = context.createGraphicsLayer()
+ layer2.record(Density(1f), Ltr, IntSize(10, 10)) {
+ assertEquals(layer2, drawContext.graphicsLayer)
+ drawRect(Color.Red)
+ }
+ layer1.record(Density(1f), Ltr, IntSize(10, 10)) {
+ assertEquals(layer1, drawContext.graphicsLayer)
+ drawLayer(layer2)
+ }
+ drawLayer(layer1)
+ },
+ verify = {
+ // just verifying there is no crash
+ }
+ )
+ }
+
+ @Test
fun testCanvasTransformStateRestore() {
val bg = Color.White
val layerColor1 = Color.Red
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayer.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayer.android.kt
index b9b0424..857a07a 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayer.android.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayer.android.kt
@@ -41,6 +41,7 @@
import androidx.compose.ui.graphics.drawscope.DefaultDensity
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.clipPath
+import androidx.compose.ui.graphics.drawscope.draw
import androidx.compose.ui.graphics.layer.LayerManager.Companion.isRobolectric
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.unit.Density
@@ -67,9 +68,9 @@
private val clipDrawBlock: DrawScope.() -> Unit = {
val path = outlinePath
if (usePathForClip && clip && path != null) {
- clipPath(path, block = drawBlock)
+ clipPath(path) { drawWithChildTracking() }
} else {
- drawBlock()
+ drawWithChildTracking()
}
}
@@ -432,10 +433,14 @@
}
private fun recordInternal() {
+ impl.record(density, layoutDirection, this, clipDrawBlock)
+ }
+
+ private fun DrawScope.drawWithChildTracking() {
childDependenciesTracker.withTracking(
onDependencyRemoved = { it.onRemovedFromParentLayer() }
) {
- impl.record(density, layoutDirection, this, clipDrawBlock)
+ drawBlock()
}
}
@@ -550,7 +555,9 @@
impl.draw(canvas)
} else {
val drawScope = softwareDrawScope ?: CanvasDrawScope().also { softwareDrawScope = it }
- drawScope.draw(density, layoutDirection, canvas, size.toSize(), drawBlock)
+ drawScope.draw(density, layoutDirection, canvas, size.toSize(), this) {
+ drawWithChildTracking()
+ }
}
if (willClipPath) {
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsViewLayer.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsViewLayer.android.kt
index 9ed246d..024dff6 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsViewLayer.android.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsViewLayer.android.kt
@@ -437,7 +437,14 @@
val pictureCanvas = p.beginRecording(size.width, size.height)
try {
pictureCanvasHolder?.drawInto(pictureCanvas) {
- pictureDrawScope?.draw(density, layoutDirection, this, size.toSize(), block)
+ pictureDrawScope?.draw(
+ density,
+ layoutDirection,
+ this,
+ size.toSize(),
+ layer,
+ block
+ )
}
} finally {
p.endRecording()
diff --git a/compose/ui/ui-test-junit4/OWNERS b/compose/ui/ui-test-junit4/OWNERS
index 0b4fcdd..90ad461 100644
--- a/compose/ui/ui-test-junit4/OWNERS
+++ b/compose/ui/ui-test-junit4/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 741702
[email protected]
[email protected]
diff --git a/compose/ui/ui-test/OWNERS b/compose/ui/ui-test/OWNERS
index 0b4fcdd..90ad461 100644
--- a/compose/ui/ui-test/OWNERS
+++ b/compose/ui/ui-test/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 741702
[email protected]
[email protected]
diff --git a/compose/ui/ui-text/OWNERS b/compose/ui/ui-text/OWNERS
index 115c29d..bb35b51 100644
--- a/compose/ui/ui-text/OWNERS
+++ b/compose/ui/ui-text/OWNERS
@@ -5,4 +5,3 @@
[email protected]
[email protected]
[email protected]
[email protected]
diff --git a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationIndentationFixTest.kt b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationIndentationFixTest.kt
index a851fb5..7c54156 100644
--- a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationIndentationFixTest.kt
+++ b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationIndentationFixTest.kt
@@ -17,7 +17,12 @@
package androidx.compose.ui.text
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.text.font.toFontFamily
+import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextIndent
import androidx.compose.ui.text.style.TextOverflow
@@ -30,6 +35,7 @@
import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -76,6 +82,49 @@
}
@Test
+ fun drawParagraphIndentsCorrectly_whenPaintedRepeatedly() {
+ val width1 = charWidth * 3
+ val subject =
+ Paragraph(
+ text = ltrChar.repeat(repeatCount),
+ style =
+ TextStyle(
+ textAlign = TextAlign.Center,
+ letterSpacing = 1.sp,
+ lineHeight = 24.sp,
+ lineHeightStyle =
+ LineHeightStyle(
+ alignment = LineHeightStyle.Alignment.Center,
+ trim = LineHeightStyle.Trim.None,
+ mode = LineHeightStyle.Mode.Fixed
+ ),
+ ),
+ maxLines = lastLine + 1,
+ overflow = TextOverflow.Ellipsis,
+ constraints = Constraints(maxWidth = width1),
+ density = Density(density = 1f),
+ fontFamilyResolver = UncachedFontFamilyResolver(getInstrumentation().context)
+ )
+
+ val width = subject.width.ceilToInt()
+ val height = subject.height.ceilToInt()
+ val bitmap = ImageBitmap(width, height)
+ val canvas = Canvas(bitmap)
+ val paint = Paint()
+ paint.color = Color.Black
+ canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
+
+ subject.paint(canvas, Color.Blue)
+ val initialPixels = bitmap.dumpFirstCharPixels()
+ canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
+
+ subject.paint(canvas, Color.Blue)
+ val finalPixels = bitmap.dumpFirstCharPixels()
+
+ assertThat(initialPixels).isEqualTo(finalPixels)
+ }
+
+ @Test
fun getLineLeftAndGetLineRight_Rtl() {
val paragraph = paragraph(rtlChar.repeat(repeatCount))
for (line in 0 until paragraph.lineCount) {
@@ -353,4 +402,10 @@
UncachedFontFamilyResolver(InstrumentationRegistry.getInstrumentation().context)
)
}
+
+ private fun ImageBitmap.dumpFirstCharPixels(): IntArray {
+ val out = IntArray(charWidth * charWidth)
+ readPixels(out, width = charWidth, height = charWidth)
+ return out
+ }
}
diff --git a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/ParagraphIntrinsicsAsyncTypefaceTest.kt b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/ParagraphIntrinsicsAsyncTypefaceTest.kt
index ba2d38b..13a0595 100644
--- a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/ParagraphIntrinsicsAsyncTypefaceTest.kt
+++ b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/ParagraphIntrinsicsAsyncTypefaceTest.kt
@@ -113,7 +113,7 @@
spanStyles,
listOf(),
fontFamilyResolver,
- Density(1f)
+ Density(1f),
)
}
}
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidParagraph.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidParagraph.android.kt
index 49d12ab..f1880be 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidParagraph.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidParagraph.android.kt
@@ -67,6 +67,7 @@
import androidx.compose.ui.text.android.LayoutCompat.TEXT_GRANULARITY_CHARACTER
import androidx.compose.ui.text.android.LayoutCompat.TEXT_GRANULARITY_WORD
import androidx.compose.ui.text.android.TextLayout
+import androidx.compose.ui.text.android.hasSpan
import androidx.compose.ui.text.android.selection.getWordEnd
import androidx.compose.ui.text.android.selection.getWordStart
import androidx.compose.ui.text.android.style.IndentationFixSpan
@@ -689,11 +690,13 @@
textAlign != TextAlign.Justify)
}
-@OptIn(InternalPlatformTextApi::class)
+// this _will_ be called multiple times on the same ParagraphIntrinsics
private fun CharSequence.attachIndentationFixSpan(): CharSequence {
if (isEmpty()) return this
- val spannable = if (this is Spannable) this else SpannableString(this)
- spannable.setSpan(IndentationFixSpan(), spannable.length - 1, spannable.length - 1)
+ val spannable = this as? Spannable ?: SpannableString(this)
+ if (!spannable.hasSpan(IndentationFixSpan::class.java)) {
+ spannable.setSpan(IndentationFixSpan(), spannable.length - 1, spannable.length - 1)
+ }
return spannable
}
diff --git a/compose/ui/ui-unit/OWNERS b/compose/ui/ui-unit/OWNERS
index 0587d0c..24172be 100644
--- a/compose/ui/ui-unit/OWNERS
+++ b/compose/ui/ui-unit/OWNERS
@@ -1,7 +1,6 @@
# Bug component: 1070768
[email protected]
[email protected]
[email protected]
# For text related files
include /TEXT_OWNERS
diff --git a/compose/ui/ui-util/OWNERS b/compose/ui/ui-util/OWNERS
index 0587d0c..24172be 100644
--- a/compose/ui/ui-util/OWNERS
+++ b/compose/ui/ui-util/OWNERS
@@ -1,7 +1,6 @@
# Bug component: 1070768
[email protected]
[email protected]
[email protected]
# For text related files
include /TEXT_OWNERS
diff --git a/compose/ui/ui/OWNERS b/compose/ui/ui/OWNERS
index 44beee2..9f1c725 100644
--- a/compose/ui/ui/OWNERS
+++ b/compose/ui/ui/OWNERS
@@ -2,7 +2,6 @@
# UI Team
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
@@ -12,7 +11,6 @@
# For Material related files
[email protected]
[email protected]
[email protected]
# For text related files
include /TEXT_OWNERS
diff --git a/compose/ui/ui/api/current.ignore b/compose/ui/ui/api/current.ignore
index ce4a76f..9731bc5 100644
--- a/compose/ui/ui/api/current.ignore
+++ b/compose/ui/ui/api/current.ignore
@@ -13,10 +13,6 @@
Removed property AbstractComposeView.showLayoutBounds from compatibility checked API surface
-ChangedType: androidx.compose.ui.layout.MeasureResult#getAlignmentLines():
- Method androidx.compose.ui.layout.MeasureResult.getAlignmentLines has changed return type from java.util.Map<androidx.compose.ui.layout.AlignmentLine,java.lang.Integer> to java.util.Map<? extends androidx.compose.ui.layout.AlignmentLine,java.lang.Integer>
-
-
RemovedMethod: androidx.compose.ui.graphics.TransformOrigin#getPivotFractionX():
Removed method androidx.compose.ui.graphics.TransformOrigin.getPivotFractionX()
RemovedMethod: androidx.compose.ui.graphics.TransformOrigin#getPivotFractionY():
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 7caf96e..5a884ad 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -2531,20 +2531,20 @@
}
public interface MeasureResult {
- method public java.util.Map<? extends androidx.compose.ui.layout.AlignmentLine,java.lang.Integer> getAlignmentLines();
+ method public java.util.Map<androidx.compose.ui.layout.AlignmentLine,java.lang.Integer> getAlignmentLines();
method public int getHeight();
method public default kotlin.jvm.functions.Function1<androidx.compose.ui.layout.RulerScope,kotlin.Unit>? getRulers();
method public int getWidth();
method public void placeChildren();
- property public abstract java.util.Map<? extends androidx.compose.ui.layout.AlignmentLine,java.lang.Integer> alignmentLines;
+ property public abstract java.util.Map<androidx.compose.ui.layout.AlignmentLine,java.lang.Integer> alignmentLines;
property public abstract int height;
property public default kotlin.jvm.functions.Function1<androidx.compose.ui.layout.RulerScope,kotlin.Unit>? rulers;
property public abstract int width;
}
@androidx.compose.ui.layout.MeasureScopeMarker @kotlin.jvm.JvmDefaultWithCompatibility public interface MeasureScope extends androidx.compose.ui.layout.IntrinsicMeasureScope {
- method public default androidx.compose.ui.layout.MeasureResult layout(int width, int height, optional java.util.Map<? extends androidx.compose.ui.layout.AlignmentLine,java.lang.Integer> alignmentLines, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Placeable.PlacementScope,kotlin.Unit> placementBlock);
- method public default androidx.compose.ui.layout.MeasureResult layout(int width, int height, optional java.util.Map<? extends androidx.compose.ui.layout.AlignmentLine,java.lang.Integer> alignmentLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.RulerScope,kotlin.Unit>? rulers, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Placeable.PlacementScope,kotlin.Unit> placementBlock);
+ method public default androidx.compose.ui.layout.MeasureResult layout(int width, int height, optional java.util.Map<androidx.compose.ui.layout.AlignmentLine,java.lang.Integer> alignmentLines, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Placeable.PlacementScope,kotlin.Unit> placementBlock);
+ method public default androidx.compose.ui.layout.MeasureResult layout(int width, int height, optional java.util.Map<androidx.compose.ui.layout.AlignmentLine,java.lang.Integer> alignmentLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.RulerScope,kotlin.Unit>? rulers, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Placeable.PlacementScope,kotlin.Unit> placementBlock);
}
@kotlin.DslMarker public @interface MeasureScopeMarker {
diff --git a/compose/ui/ui/api/restricted_current.ignore b/compose/ui/ui/api/restricted_current.ignore
index ce4a76f..9731bc5 100644
--- a/compose/ui/ui/api/restricted_current.ignore
+++ b/compose/ui/ui/api/restricted_current.ignore
@@ -13,10 +13,6 @@
Removed property AbstractComposeView.showLayoutBounds from compatibility checked API surface
-ChangedType: androidx.compose.ui.layout.MeasureResult#getAlignmentLines():
- Method androidx.compose.ui.layout.MeasureResult.getAlignmentLines has changed return type from java.util.Map<androidx.compose.ui.layout.AlignmentLine,java.lang.Integer> to java.util.Map<? extends androidx.compose.ui.layout.AlignmentLine,java.lang.Integer>
-
-
RemovedMethod: androidx.compose.ui.graphics.TransformOrigin#getPivotFractionX():
Removed method androidx.compose.ui.graphics.TransformOrigin.getPivotFractionX()
RemovedMethod: androidx.compose.ui.graphics.TransformOrigin#getPivotFractionY():
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index a9d48f4..2c0d712 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -2535,20 +2535,20 @@
}
public interface MeasureResult {
- method public java.util.Map<? extends androidx.compose.ui.layout.AlignmentLine,java.lang.Integer> getAlignmentLines();
+ method public java.util.Map<androidx.compose.ui.layout.AlignmentLine,java.lang.Integer> getAlignmentLines();
method public int getHeight();
method public default kotlin.jvm.functions.Function1<androidx.compose.ui.layout.RulerScope,kotlin.Unit>? getRulers();
method public int getWidth();
method public void placeChildren();
- property public abstract java.util.Map<? extends androidx.compose.ui.layout.AlignmentLine,java.lang.Integer> alignmentLines;
+ property public abstract java.util.Map<androidx.compose.ui.layout.AlignmentLine,java.lang.Integer> alignmentLines;
property public abstract int height;
property public default kotlin.jvm.functions.Function1<androidx.compose.ui.layout.RulerScope,kotlin.Unit>? rulers;
property public abstract int width;
}
@androidx.compose.ui.layout.MeasureScopeMarker @kotlin.jvm.JvmDefaultWithCompatibility public interface MeasureScope extends androidx.compose.ui.layout.IntrinsicMeasureScope {
- method public default androidx.compose.ui.layout.MeasureResult layout(int width, int height, optional java.util.Map<? extends androidx.compose.ui.layout.AlignmentLine,java.lang.Integer> alignmentLines, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Placeable.PlacementScope,kotlin.Unit> placementBlock);
- method public default androidx.compose.ui.layout.MeasureResult layout(int width, int height, optional java.util.Map<? extends androidx.compose.ui.layout.AlignmentLine,java.lang.Integer> alignmentLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.RulerScope,kotlin.Unit>? rulers, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Placeable.PlacementScope,kotlin.Unit> placementBlock);
+ method public default androidx.compose.ui.layout.MeasureResult layout(int width, int height, optional java.util.Map<androidx.compose.ui.layout.AlignmentLine,java.lang.Integer> alignmentLines, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Placeable.PlacementScope,kotlin.Unit> placementBlock);
+ method public default androidx.compose.ui.layout.MeasureResult layout(int width, int height, optional java.util.Map<androidx.compose.ui.layout.AlignmentLine,java.lang.Integer> alignmentLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.RulerScope,kotlin.Unit>? rulers, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Placeable.PlacementScope,kotlin.Unit> placementBlock);
}
@kotlin.DslMarker public @interface MeasureScopeMarker {
diff --git a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/autofill/FakeViewStructure.kt b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/autofill/FakeViewStructure.kt
new file mode 100644
index 0000000..6dc2a1c
--- /dev/null
+++ b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/autofill/FakeViewStructure.kt
@@ -0,0 +1,369 @@
+/*
+ * Copyright 2024 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.benchmark.autofill
+
+import android.graphics.Matrix
+import android.graphics.Rect
+import android.os.Build
+import android.os.Bundle
+import android.os.LocaleList
+import android.os.Parcel
+import android.view.View
+import android.view.ViewStructure
+import android.view.autofill.AutofillId
+import android.view.autofill.AutofillValue
+import androidx.annotation.GuardedBy
+import androidx.annotation.RequiresApi
+
+/**
+ * A fake implementation of [ViewStructure] to use in tests.
+ *
+ * @param virtualId An ID that is unique for each viewStructure node in the viewStructure tree.
+ * @param packageName The package name of the app (Used as an autofill heuristic).
+ * @param typeName The type name of the view's identifier, or null if there is none.
+ * @param entryName The entry name of the view's identifier, or null if there is none.
+ * @param children A list of [ViewStructure]s that are children of the current [ViewStructure].
+ * @param bounds The bounds (Dimensions) of the component represented by this [ViewStructure].
+ * @param autofillId The [autofillId] for the parent component. The same autofillId is used for
+ * other child components.
+ * @param autofillType The data type. Can be one of the following: [View.AUTOFILL_TYPE_DATE],
+ * [View.AUTOFILL_TYPE_LIST], [View.AUTOFILL_TYPE_TEXT], [View.AUTOFILL_TYPE_TOGGLE] or
+ * [View.AUTOFILL_TYPE_NONE].
+ * @param autofillHints The autofill hint. If this value not specified, we use heuristics to
+ * determine what data to use while performing autofill.
+ */
+@RequiresApi(Build.VERSION_CODES.M)
+internal data class FakeViewStructure(
+ var virtualId: Int = 0,
+ var packageName: String? = null,
+ var typeName: String? = null,
+ var entryName: String? = null,
+ var children: MutableList<FakeViewStructure> = mutableListOf(),
+ var bounds: Rect? = null,
+ private val autofillId: AutofillId? =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) generateAutofillId() else null,
+ internal var autofillType: Int = View.AUTOFILL_TYPE_NONE,
+ internal var autofillHints: Array<out String> = arrayOf()
+) : ViewStructure() {
+
+ private var activated: Boolean = false
+ private var alpha: Float = 1f
+ private var autofillOptions: Array<CharSequence>? = null
+ private var autofillValue: AutofillValue? = null
+ private var className: String? = null
+ private var contentDescription: CharSequence? = null
+ private var dataIsSensitive: Boolean = false
+ private var elevation: Float = 0f
+ private var extras: Bundle = Bundle()
+ private var hint: CharSequence? = null
+ private var htmlInfo: HtmlInfo? = null
+ private var inputType: Int = 0
+ private var isEnabled: Boolean = true
+ private var isAccessibilityFocused: Boolean = false
+ private var isCheckable: Boolean = false
+ private var isChecked: Boolean = false
+ private var isClickable: Boolean = true
+ private var isContextClickable: Boolean = false
+ private var isFocused: Boolean = false
+ private var isFocusable: Boolean = false
+ private var isLongClickable: Boolean = false
+ private var isOpaque: Boolean = false
+ private var selected: Boolean = false
+ private var text: CharSequence = ""
+ private var textLines: IntArray? = null
+ private var transformation: Matrix? = null
+ private var visibility: Int = View.VISIBLE
+ private var webDomain: String? = null
+
+ internal companion object {
+ @GuardedBy("this") private var previousId = 0
+ private val NO_SESSION = 0
+
+ // Android API level 26 introduced Autofill. Prior to API level 26, no autofill ID will be
+ // provided.
+ @RequiresApi(Build.VERSION_CODES.O)
+ @Synchronized
+ private fun generateAutofillId(): AutofillId {
+ var autofillId: AutofillId? = null
+ useParcel { parcel ->
+ parcel.writeInt(++previousId) // View Id.
+ parcel.writeInt(NO_SESSION) // Flag.
+ parcel.setDataPosition(0)
+ autofillId = AutofillId.CREATOR.createFromParcel(parcel)
+ }
+ return autofillId ?: error("Could not generate autofill id")
+ }
+ }
+
+ override fun getChildCount() = children.count()
+
+ override fun addChildCount(childCount: Int): Int {
+ repeat(childCount) { children.add(FakeViewStructure(autofillId = autofillId)) }
+ return children.count() - childCount
+ }
+
+ override fun newChild(index: Int): FakeViewStructure {
+ if (index >= children.count()) error("Call addChildCount() before calling newChild()")
+ return children[index]
+ }
+
+ override fun getAutofillId() = autofillId
+
+ override fun setAutofillId(rootId: AutofillId, virtualId: Int) {
+ this.virtualId = virtualId
+ }
+
+ override fun setId(
+ virtualId: Int,
+ packageName: String?,
+ typeName: String?,
+ entryName: String?
+ ) {
+ this.virtualId = virtualId
+ this.packageName = packageName
+ this.typeName = typeName
+ this.entryName = entryName
+ }
+
+ override fun setAutofillType(autofillType: Int) {
+ this.autofillType = autofillType
+ }
+
+ override fun setAutofillHints(autofillHints: Array<out String>?) {
+ autofillHints?.let { this.autofillHints = it }
+ }
+
+ override fun setDimens(left: Int, top: Int, x: Int, y: Int, width: Int, height: Int) {
+ this.bounds = Rect(left, top, width - left, height - top)
+ }
+
+ override fun equals(other: Any?) =
+ other is FakeViewStructure &&
+ other.virtualId == virtualId &&
+ other.packageName == packageName &&
+ other.typeName == typeName &&
+ other.entryName == entryName &&
+ other.autofillType == autofillType &&
+ other.autofillHints.contentEquals(autofillHints) &&
+ other.bounds.contentEquals(bounds) &&
+ other.activated == activated &&
+ other.alpha == alpha &&
+ other.autofillOptions.contentEquals(autofillOptions) &&
+ other.autofillValue == autofillValue &&
+ other.className == className &&
+ other.children.count() == children.count() &&
+ other.contentDescription == contentDescription &&
+ other.dataIsSensitive == dataIsSensitive &&
+ other.elevation == elevation &&
+ other.hint == hint &&
+ other.htmlInfo == htmlInfo &&
+ other.inputType == inputType &&
+ other.isEnabled == isEnabled &&
+ other.isCheckable == isCheckable &&
+ other.isChecked == isChecked &&
+ other.isClickable == isClickable &&
+ other.isContextClickable == isContextClickable &&
+ other.isAccessibilityFocused == isAccessibilityFocused &&
+ other.isFocused == isFocused &&
+ other.isLongClickable == isLongClickable &&
+ other.isOpaque == isOpaque &&
+ other.isFocusable == isFocusable &&
+ other.selected == selected &&
+ other.text == text &&
+ other.textLines.contentEquals(textLines) &&
+ other.transformation == transformation &&
+ other.visibility == visibility &&
+ other.webDomain == webDomain
+
+ override fun hashCode() = super.hashCode()
+
+ override fun getExtras() = extras
+
+ override fun getHint() = hint ?: ""
+
+ override fun getText() = text
+
+ override fun hasExtras() = !extras.isEmpty
+
+ override fun setActivated(p0: Boolean) {
+ activated = p0
+ }
+
+ override fun setAccessibilityFocused(p0: Boolean) {
+ isAccessibilityFocused = p0
+ }
+
+ override fun setAlpha(p0: Float) {
+ alpha = p0
+ }
+
+ override fun setAutofillOptions(p0: Array<CharSequence>?) {
+ autofillOptions = p0
+ }
+
+ override fun setAutofillValue(p0: AutofillValue?) {
+ autofillValue = p0
+ }
+
+ override fun setCheckable(p0: Boolean) {
+ isCheckable = p0
+ }
+
+ override fun setChecked(p0: Boolean) {
+ isChecked = p0
+ }
+
+ override fun setClassName(p0: String?) {
+ className = p0
+ }
+
+ override fun setClickable(p0: Boolean) {
+ isClickable = p0
+ }
+
+ override fun setContentDescription(p0: CharSequence?) {
+ contentDescription = p0
+ }
+
+ override fun setContextClickable(p0: Boolean) {
+ isContextClickable = p0
+ }
+
+ override fun setDataIsSensitive(p0: Boolean) {
+ dataIsSensitive = p0
+ }
+
+ override fun setElevation(p0: Float) {
+ elevation = p0
+ }
+
+ override fun setEnabled(p0: Boolean) {
+ isEnabled = p0
+ }
+
+ override fun setFocusable(p0: Boolean) {
+ isFocusable = p0
+ }
+
+ override fun setFocused(p0: Boolean) {
+ isFocused = p0
+ }
+
+ override fun setHtmlInfo(p0: HtmlInfo) {
+ htmlInfo = p0
+ }
+
+ override fun setHint(p0: CharSequence?) {
+ hint = p0
+ }
+
+ override fun setInputType(p0: Int) {
+ inputType = p0
+ }
+
+ override fun setLongClickable(p0: Boolean) {
+ isLongClickable = p0
+ }
+
+ override fun setOpaque(p0: Boolean) {
+ isOpaque = p0
+ }
+
+ override fun setSelected(p0: Boolean) {
+ selected = p0
+ }
+
+ override fun setText(p0: CharSequence?) {
+ p0?.let { text = it }
+ }
+
+ override fun setText(p0: CharSequence?, p1: Int, p2: Int) {
+ p0?.let { text = it.subSequence(p1, p2) }
+ }
+
+ override fun setTextLines(p0: IntArray?, p1: IntArray?) {
+ textLines = p0
+ }
+
+ override fun setTransformation(p0: Matrix?) {
+ transformation = p0
+ }
+
+ override fun setVisibility(p0: Int) {
+ visibility = p0
+ }
+
+ override fun setWebDomain(p0: String?) {
+ webDomain = p0
+ }
+
+ // Unimplemented methods.
+ override fun asyncCommit() {
+ TODO("not implemented")
+ }
+
+ override fun asyncNewChild(p0: Int): ViewStructure {
+ TODO("not implemented")
+ }
+
+ override fun getTextSelectionEnd(): Int {
+ TODO("not implemented")
+ }
+
+ override fun getTextSelectionStart(): Int {
+ TODO("not implemented")
+ }
+
+ override fun newHtmlInfoBuilder(p0: String): HtmlInfo.Builder {
+ TODO("not implemented")
+ }
+
+ override fun setAutofillId(p0: AutofillId) {
+ TODO("not implemented")
+ }
+
+ override fun setChildCount(p0: Int) {
+ TODO("not implemented")
+ }
+
+ override fun setLocaleList(p0: LocaleList?) {
+ TODO("not implemented")
+ }
+
+ override fun setTextStyle(p0: Float, p1: Int, p2: Int, p3: Int) {
+ TODO("not implemented")
+ }
+}
+
+private fun Rect?.contentEquals(other: Rect?) =
+ when {
+ (other == null && this == null) -> true
+ (other == null || this == null) -> false
+ else ->
+ other.left == left && other.right == right && other.bottom == bottom && other.top == top
+ }
+
+/** Obtains a parcel and then recycles it correctly whether an exception is thrown or not. */
+private fun useParcel(block: (Parcel) -> Unit) {
+ var parcel: Parcel? = null
+ try {
+ parcel = Parcel.obtain()
+ block(parcel)
+ } finally {
+ parcel?.recycle()
+ }
+}
diff --git a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/autofill/PopulateAutofillStructBenchmark.kt b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/autofill/PopulateAutofillStructBenchmark.kt
new file mode 100644
index 0000000..56e3231
--- /dev/null
+++ b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/autofill/PopulateAutofillStructBenchmark.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2024 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.benchmark.autofill
+
+import android.view.View
+import androidx.benchmark.ExperimentalBenchmarkConfigApi
+import androidx.benchmark.MicrobenchmarkConfig
+import androidx.compose.runtime.Composable
+import androidx.compose.testutils.ComposeTestCase
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.doFramesUntilNoChangesPending
+import androidx.compose.ui.ComposeUiFlags.isSemanticAutofillEnabled
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.platform.LocalView
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@LargeTest
+@SdkSuppress(minSdkVersion = 26)
+@RunWith(Parameterized::class)
+class PopulateAutofillStructBenchmark(private val isAutofillEnabled: Boolean) {
+ @OptIn(ExperimentalComposeUiApi::class)
+ private val previousFlagValue = isSemanticAutofillEnabled
+ private lateinit var ownerView: View
+
+ @OptIn(ExperimentalBenchmarkConfigApi::class)
+ @get:Rule
+ val benchmarkRule = ComposeBenchmarkRule(MicrobenchmarkConfig(traceAppTagEnabled = true))
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "Autofill enabled = {0}")
+ fun data(): Collection<Array<Any>> {
+ val testCases = mutableListOf<Array<Any>>()
+ // Add a `false` parameter here and run locally to compare Autofill off vs on
+ for (isAutofillEnabled in listOf(true)) {
+ testCases.add(arrayOf(isAutofillEnabled))
+ }
+ return testCases
+ }
+ }
+
+ @Test
+ fun populateViewStructureBenchmark_textScreen() {
+ @OptIn(ExperimentalComposeUiApi::class)
+ isSemanticAutofillEnabled = isAutofillEnabled
+ measurePopulateViewStructureRepeatedOnUiThread { AutofillTextScreen() }
+ @OptIn(ExperimentalComposeUiApi::class)
+ isSemanticAutofillEnabled = previousFlagValue
+ }
+
+ @Test
+ fun populateViewStructureBenchmark_autofillScreen() {
+ @OptIn(ExperimentalComposeUiApi::class)
+ isSemanticAutofillEnabled = isAutofillEnabled
+ measurePopulateViewStructureRepeatedOnUiThread { AutofillScreen() }
+ @OptIn(ExperimentalComposeUiApi::class)
+ isSemanticAutofillEnabled = previousFlagValue
+ }
+
+ private fun measurePopulateViewStructureRepeatedOnUiThread(content: @Composable () -> Unit) {
+ benchmarkRule.runBenchmarkFor(
+ givenTestCase = {
+ object : ComposeTestCase {
+ @Composable
+ override fun Content() {
+ ownerView = LocalView.current
+ content()
+ }
+ }
+ }
+ ) {
+ benchmarkRule.runOnUiThread { doFramesUntilNoChangesPending() }
+ benchmarkRule.measureRepeatedOnUiThread {
+ ownerView.onProvideAutofillVirtualStructure(FakeViewStructure(), 0)
+ }
+ }
+ }
+}
diff --git a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/autofill/SemanticAutofillBenchmarkUtils.kt b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/autofill/SemanticAutofillBenchmarkUtils.kt
index 898c1b2..3ec6174 100644
--- a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/autofill/SemanticAutofillBenchmarkUtils.kt
+++ b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/autofill/SemanticAutofillBenchmarkUtils.kt
@@ -18,10 +18,18 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.Button
+import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.autofill.ContentDataType
import androidx.compose.ui.autofill.ContentType
@@ -29,6 +37,7 @@
import androidx.compose.ui.semantics.contentType
import androidx.compose.ui.semantics.focused
import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.dp
@Composable
internal fun RemovableAutofillText(state: MutableState<Boolean>) {
@@ -180,6 +189,76 @@
}
}
+@Composable
+internal fun AutofillTextScreen() {
+ Column {
+ TextField(
+ value = data.firstName,
+ onValueChange = {},
+ label = { Text("Enter first name here: ") },
+ modifier =
+ Modifier.semantics {
+ contentType = ContentType.PersonFirstName
+ contentDataType = ContentDataType.Text
+ }
+ )
+ TextField(
+ value = data.lastName,
+ onValueChange = {},
+ label = { Text("Enter last name here: ") },
+ modifier =
+ Modifier.semantics {
+ contentType = ContentType.PersonLastName
+ contentDataType = ContentDataType.Text
+ }
+ )
+ TextField(
+ value = data.firstName,
+ onValueChange = {},
+ label = { Text("Enter first name here: ") },
+ modifier =
+ Modifier.semantics {
+ contentType = ContentType.PersonFirstName
+ contentDataType = ContentDataType.Text
+ }
+ )
+ TextField(
+ value = data.lastName,
+ onValueChange = {},
+ label = { Text("Enter last name here: ") },
+ modifier =
+ Modifier.semantics {
+ contentType = ContentType.PersonLastName
+ contentDataType = ContentDataType.Text
+ }
+ )
+ }
+}
+
+@Composable
+internal fun AutofillScreen() {
+ Scaffold(
+ content = { padding ->
+ Column(modifier = Modifier.fillMaxSize().padding(padding)) {
+ // Navigation Button backwards
+ Button(onClick = {}, modifier = Modifier.align(Alignment.Start)) {
+ Spacer(modifier = Modifier.width(8.dp))
+ Text("Go to route.")
+ }
+
+ // Navigation Button forwards
+ Button(onClick = {}, modifier = Modifier.align(Alignment.Start)) {
+ Spacer(modifier = Modifier.width(8.dp))
+ Text("Go to next screen")
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+ AutofillTextScreen()
+ }
+ }
+ )
+}
+
internal data class PersonData(
var title: String = "",
var firstName: String = "",
diff --git a/compose/ui/ui/lint-baseline.xml b/compose/ui/ui/lint-baseline.xml
index bbfaca0..88f4f98 100644
--- a/compose/ui/ui/lint-baseline.xml
+++ b/compose/ui/ui/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.8.0-alpha06" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha06)" variant="all" version="8.8.0-alpha06">
+<issues format="6" by="lint 8.9.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.9.0-alpha01)" variant="all" version="8.9.0-alpha01">
<issue
id="BanThreadSleep"
@@ -58,7 +58,7 @@
<issue
id="PrimitiveInCollection"
message="return type List<HapticFeedbackType> of values: replace with IntList"
- errorLine1=" fun values(): List<HapticFeedbackType> = listOf(LongPress, TextHandleMove)"
+ errorLine1=" fun values(): List<HapticFeedbackType> ="
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/commonMain/kotlin/androidx/compose/ui/hapticfeedback/HapticFeedbackType.kt"/>
@@ -75,27 +75,27 @@
<issue
id="PrimitiveInCollection"
- message="return type Map<? extends AlignmentLine, Integer> of getLastCalculation: replace with ObjectIntMap"
- errorLine1=" fun getLastCalculation(): Map<out AlignmentLine, Int> = alignmentLineMap"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="return type Map<AlignmentLine, Integer> of getLastCalculation: replace with ObjectIntMap"
+ errorLine1=" fun getLastCalculation(): Map<AlignmentLine, Int> = alignmentLineMap"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeAlignmentLines.kt"/>
</issue>
<issue
id="PrimitiveInCollection"
- message="return type Map<? extends AlignmentLine, Integer> of getAlignmentLinesMap: replace with ObjectIntMap"
- errorLine1=" protected abstract val NodeCoordinator.alignmentLinesMap: Map<out AlignmentLine, Int>"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="return type Map<AlignmentLine, Integer> of getAlignmentLinesMap: replace with ObjectIntMap"
+ errorLine1=" protected abstract val NodeCoordinator.alignmentLinesMap: Map<AlignmentLine, Int>"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeAlignmentLines.kt"/>
</issue>
<issue
id="PrimitiveInCollection"
- message="return type Map<? extends AlignmentLine, Integer> of calculateAlignmentLines: replace with ObjectIntMap"
- errorLine1=" fun calculateAlignmentLines(): Map<out AlignmentLine, Int>"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="return type Map<AlignmentLine, Integer> of calculateAlignmentLines: replace with ObjectIntMap"
+ errorLine1=" fun calculateAlignmentLines(): Map<AlignmentLine, Int>"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt"/>
</issue>
@@ -111,25 +111,25 @@
<issue
id="PrimitiveInCollection"
- message="return type Map<? extends AlignmentLine, Integer> of getAlignmentLines: replace with ObjectIntMap"
- errorLine1=" val alignmentLines: Map<out AlignmentLine, Int>"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="return type Map<AlignmentLine, Integer> of getAlignmentLines: replace with ObjectIntMap"
+ errorLine1=" val alignmentLines: Map<AlignmentLine, Int>"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/commonMain/kotlin/androidx/compose/ui/layout/MeasureResult.kt"/>
</issue>
<issue
id="PrimitiveInCollection"
- message="method layout has parameter alignmentLines with type Map<? extends AlignmentLine, Integer>: replace with ObjectIntMap"
- errorLine1=" alignmentLines: Map<out AlignmentLine, Int> = emptyMap(),"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="method layout has parameter alignmentLines with type Map<AlignmentLine, Integer>: replace with ObjectIntMap"
+ errorLine1=" alignmentLines: Map<AlignmentLine, Int> = emptyMap(),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/commonMain/kotlin/androidx/compose/ui/layout/MeasureScope.kt"/>
</issue>
<issue
id="PrimitiveInCollection"
- message="variable alignmentLines with type Map<? extends AlignmentLine, ? extends Integer>: replace with ObjectIntMap"
+ message="variable alignmentLines with type Map<AlignmentLine, ? extends Integer>: replace with ObjectIntMap"
errorLine1=" val alignmentLines = coordinator._measureResult?.alignmentLines"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/contentcapture/ContentCaptureTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/contentcapture/ContentCaptureTest.kt
index e65a15d..147a430 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/contentcapture/ContentCaptureTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/contentcapture/ContentCaptureTest.kt
@@ -335,6 +335,12 @@
rule.runOnIdle {
// At least 5 times(List itself + 4 children)
verify(contentCaptureSessionCompat, atLeast(5)).notifyViewAppeared(any())
+ with(argumentCaptor<String>()) {
+ verify(viewStructureCompat, times(5)).setClassName(capture())
+ assertThat(firstValue).isEqualTo("android.widget.ViewGroup")
+ assertThat(secondValue).isEqualTo("android.widget.TextView")
+ assertThat(thirdValue).isEqualTo("android.widget.TextView")
+ }
}
}
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusListenerTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusListenerTest.kt
new file mode 100644
index 0000000..e1be2f7
--- /dev/null
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusListenerTest.kt
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2025 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.focus
+
+import android.os.Build
+import android.os.Build.VERSION.SDK_INT
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.ui.ComposeUiFlags
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.node.requireSemanticsInfo
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FocusListenerTest {
+ @get:Rule val rule = createComposeRule()
+
+ @OptIn(ExperimentalComposeUiApi::class)
+ private val previousFlagValue = ComposeUiFlags.isSemanticAutofillEnabled
+
+ // When we clear focus on Pre P devices, request focus is called even when we are
+ // in touch mode.
+ // https://developer.android.com/about/versions/pie/android-9.0-changes-28#focus
+ private val initialFocusAfterClearFocus = SDK_INT < Build.VERSION_CODES.P
+
+ @Before
+ fun enableAutofill() {
+ @OptIn(ExperimentalComposeUiApi::class)
+ ComposeUiFlags.isSemanticAutofillEnabled = true
+ }
+
+ @After
+ fun disableAutofill() {
+ @OptIn(ExperimentalComposeUiApi::class)
+ ComposeUiFlags.isSemanticAutofillEnabled = previousFlagValue
+ }
+
+ @Test
+ fun nothingFocused() {
+ // Arrange.
+ val listener = TestFocusListener()
+ rule.setContent(listener) { Box(Modifier.size(10.dp).focusable()) }
+
+ // Assert.
+ rule.runOnIdle { assertThat(listener.events).isEmpty() }
+ }
+
+ @Test
+ fun firstItemFocused() {
+ // Arrange.
+ val listener = TestFocusListener()
+ rule.setContent(listener) { Box(Modifier.size(10.dp).testTag("item").focusable()) }
+ val itemId = rule.onNodeWithTag("item").semanticsId()
+
+ // Act.
+ rule.onNodeWithTag("item").requestFocus()
+
+ // Assert.
+ rule.runOnIdle {
+ assertThat(listener).isEqualTo(TestFocusListener(mutableListOf(Pair(null, itemId))))
+ }
+ }
+
+ @Test
+ fun firstItemUnFocused() {
+ // Arrange.
+ val listener = TestFocusListener()
+ lateinit var focusManager: FocusManager
+ rule.setContent(listener) {
+ focusManager = LocalFocusManager.current
+ Box(Modifier.size(10.dp).testTag("item").focusable())
+ }
+ val itemId = rule.onNodeWithTag("item").semanticsId()
+ rule.onNodeWithTag("item").requestFocus()
+ rule.runOnIdle { listener.reset() }
+
+ // Act.
+ rule.runOnIdle { focusManager.clearFocus() }
+
+ // Assert.
+ rule.runOnIdle {
+ assertThat(listener)
+ .isEqualTo(
+ TestFocusListener(
+ if (initialFocusAfterClearFocus) {
+ mutableListOf(Pair(itemId, null), Pair(null, itemId))
+ } else {
+ mutableListOf(Pair(itemId, null))
+ }
+ )
+ )
+ }
+ }
+
+ @Test
+ fun secondItemFocused() {
+ // Arrange.
+ val listener = TestFocusListener()
+ rule.setContent(listener) {
+ Column {
+ Box(Modifier.size(10.dp).testTag("item1").focusable())
+ Box(Modifier.size(10.dp).testTag("item2").focusable())
+ }
+ }
+ val item1Id = rule.onNodeWithTag("item1").semanticsId()
+ val item2Id = rule.onNodeWithTag("item2").semanticsId()
+ rule.onNodeWithTag("item1").requestFocus()
+ rule.runOnIdle { listener.reset() }
+
+ // Act.
+ rule.onNodeWithTag("item2").requestFocus()
+
+ // Assert.
+ rule.runOnIdle {
+ assertThat(listener)
+ .isEqualTo(
+ TestFocusListener(mutableListOf(Pair(item1Id, null), Pair(null, item2Id)))
+ )
+ }
+ }
+
+ @Test
+ fun secondItemUnFocused() {
+ // Arrange.
+ val listener = TestFocusListener()
+ lateinit var focusManager: FocusManager
+ rule.setContent(listener) {
+ focusManager = LocalFocusManager.current
+ Column {
+ Box(Modifier.size(10.dp).testTag("item1").focusable())
+ Box(Modifier.size(10.dp).testTag("item2").focusable())
+ }
+ }
+ val item1Id = rule.onNodeWithTag("item1").semanticsId()
+ val item2Id = rule.onNodeWithTag("item2").semanticsId()
+ rule.onNodeWithTag("item1").requestFocus()
+ rule.onNodeWithTag("item2").requestFocus()
+ rule.runOnIdle { listener.reset() }
+
+ // Act.
+ rule.runOnIdle { focusManager.clearFocus() }
+
+ // Assert.
+ rule.runOnIdle {
+ assertThat(listener)
+ .isEqualTo(
+ TestFocusListener(
+ if (initialFocusAfterClearFocus) {
+ mutableListOf(Pair(item2Id, null), Pair(null, item1Id))
+ } else {
+ mutableListOf(Pair(item2Id, null))
+ }
+ )
+ )
+ }
+ }
+
+ private data class TestFocusListener(
+ val events: MutableList<Pair<Int?, Int?>> = mutableListOf<Pair<Int?, Int?>>()
+ ) : FocusListener {
+ override fun onFocusChanged(
+ previous: FocusTargetModifierNode?,
+ current: FocusTargetModifierNode?
+ ) {
+ events +=
+ Pair(
+ previous?.requireSemanticsInfo()?.semanticsId,
+ current?.requireSemanticsInfo()?.semanticsId
+ )
+ }
+
+ fun reset() {
+ events.clear()
+ }
+ }
+
+ private fun ComposeContentTestRule.setContent(
+ focusListener: FocusListener,
+ content: @Composable (() -> Unit)
+ ) {
+ setContent {
+ val focusOwner = LocalFocusManager.current as FocusOwner
+ DisposableEffect(focusOwner, focusListener) {
+ focusOwner.listeners += focusListener
+ onDispose { focusOwner.listeners -= focusListener }
+ }
+ content()
+ }
+ }
+}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillManager.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillManager.android.kt
index f6a524e..69f31cc 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillManager.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillManager.android.kt
@@ -27,7 +27,12 @@
import androidx.annotation.RequiresApi
import androidx.collection.MutableIntSet
import androidx.collection.mutableObjectListOf
+import androidx.compose.ui.ComposeUiFlags
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.focus.FocusListener
+import androidx.compose.ui.focus.FocusTargetModifierNode
import androidx.compose.ui.internal.checkPreconditionNotNull
+import androidx.compose.ui.node.requireSemanticsInfo
import androidx.compose.ui.platform.coreshims.ViewCompatShims
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.semantics.SemanticsConfiguration
@@ -54,7 +59,7 @@
private val view: View,
private val rectManager: RectManager,
private val packageName: String,
-) : AutofillManager(), SemanticsListener {
+) : AutofillManager(), SemanticsListener, FocusListener {
private var reusableRect = Rect()
private var rootAutofillId: AutofillId
@@ -85,6 +90,26 @@
}
}
+ override fun onFocusChanged(
+ previous: FocusTargetModifierNode?,
+ current: FocusTargetModifierNode?
+ ) {
+ previous?.requireSemanticsInfo()?.let {
+ if (it.semanticsConfiguration?.isAutofillable() == true) {
+ platformAutofillManager.notifyViewExited(view, it.semanticsId)
+ }
+ }
+ current?.requireSemanticsInfo()?.let {
+ if (it.semanticsConfiguration?.isAutofillable() == true) {
+ val semanticsId = it.semanticsId
+ rectManager.rects.withRect(semanticsId) { l, t, r, b ->
+ platformAutofillManager.notifyViewEntered(view, semanticsId, Rect(l, t, r, b))
+ }
+ previouslyFocusedId = semanticsId
+ }
+ }
+ }
+
/** Send events to the autofill service in response to semantics changes. */
override fun onSemanticsChanged(
semanticsInfo: SemanticsInfo,
@@ -109,22 +134,18 @@
}
// Check Focus.
- // TODO: Instead of saving the focused item here, add some internal API to focusManager
- // so that this could be more efficient.
- val previousFocus = prevConfig?.getOrNull(SemanticsProperties.Focused)
- val currFocus = config?.getOrNull(SemanticsProperties.Focused)
- if (previousFocus != true && currFocus == true && config.isAutofillable()) {
- previouslyFocusedId = semanticsId
- rectManager.rects.withRect(semanticsId) { left, top, right, bottom ->
- platformAutofillManager.notifyViewEntered(
- view,
- semanticsId,
- Rect(left, top, right, bottom)
- )
+ if (@OptIn(ExperimentalComposeUiApi::class) !ComposeUiFlags.isTrackFocusEnabled) {
+ val previousFocus = prevConfig?.getOrNull(SemanticsProperties.Focused)
+ val currFocus = config?.getOrNull(SemanticsProperties.Focused)
+ if (previousFocus != true && currFocus == true && config.isAutofillable()) {
+ previouslyFocusedId = semanticsId
+ rectManager.rects.withRect(semanticsId) { l, t, r, b ->
+ platformAutofillManager.notifyViewEntered(view, semanticsId, Rect(l, t, r, b))
+ }
}
- }
- if (previousFocus == true && currFocus != true && prevConfig.isAutofillable()) {
- platformAutofillManager.notifyViewExited(view, semanticsId)
+ if (previousFocus == true && currFocus != true && prevConfig.isAutofillable()) {
+ platformAutofillManager.notifyViewExited(view, semanticsId)
+ }
}
// Update currentlyDisplayedIDs if relevance to Autocommit has changed.
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/contentcapture/AndroidContentCaptureManager.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/contentcapture/AndroidContentCaptureManager.android.kt
index f6267e88..27b2ae0 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/contentcapture/AndroidContentCaptureManager.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/contentcapture/AndroidContentCaptureManager.android.kt
@@ -351,6 +351,9 @@
// Treat test tag as resourceId
structure.setId(id, null, null, it)
}
+ configuration.getOrNull(SemanticsProperties.IsTraversalGroup)?.let {
+ structure.setClassName("android.widget.ViewGroup")
+ }
configuration.getOrNull(SemanticsProperties.Text)?.let {
structure.setClassName("android.widget.TextView")
structure.setText(it.fastJoinToString("\n"))
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 1ba5721..c059f5d 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
@@ -26,7 +26,6 @@
import android.os.Build.VERSION_CODES.M
import android.os.Build.VERSION_CODES.N
import android.os.Build.VERSION_CODES.O
-import android.os.Build.VERSION_CODES.P
import android.os.Build.VERSION_CODES.Q
import android.os.Build.VERSION_CODES.S
import android.os.Looper
@@ -1744,8 +1743,10 @@
return layer
}
- // enable new layers on versions supporting render nodes
- if (isHardwareAccelerated && SDK_INT >= M && SDK_INT != P) {
+ // Prior to M ViewLayer implementation might be doing extra drawing in order
+ // to support the software rendering. This extra drawing is breaking some of tests
+ // and we can't fully migrate to it until we figure out how to solve it.
+ if (SDK_INT >= M) {
return GraphicsLayerOwnerLayer(
graphicsLayer = graphicsContext.createGraphicsLayer(),
context = graphicsContext,
@@ -2036,7 +2037,10 @@
viewTreeObserver.addOnTouchModeChangeListener(touchModeChangeListener)
if (SDK_INT >= S) AndroidComposeViewTranslationCallbackS.setViewTranslationCallback(this)
- if (autofillSupported()) _autofillManager?.let { semanticsOwner.listeners += it }
+ _autofillManager?.let {
+ focusOwner.listeners += it
+ semanticsOwner.listeners += it
+ }
}
override fun onDetachedFromWindow() {
@@ -2061,7 +2065,10 @@
viewTreeObserver.removeOnTouchModeChangeListener(touchModeChangeListener)
if (SDK_INT >= S) AndroidComposeViewTranslationCallbackS.clearViewTranslationCallback(this)
- if (autofillSupported()) _autofillManager?.let { semanticsOwner.listeners -= it }
+ _autofillManager?.let {
+ semanticsOwner.listeners -= it
+ focusOwner.listeners -= it
+ }
}
override fun onProvideAutofillVirtualStructure(structure: ViewStructure?, flags: Int) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusListener.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusListener.kt
new file mode 100644
index 0000000..139f0db
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusListener.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2025 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.focus
+
+/** Listener that can be used to observe focus changes. */
+internal interface FocusListener {
+ /**
+ * Called when the focus changes.
+ *
+ * @param previous The previous focus target node.
+ * @param current The new focus target node.
+ */
+ fun onFocusChanged(previous: FocusTargetModifierNode?, current: FocusTargetModifierNode?)
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwner.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwner.kt
index ae474794..ae030e0 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwner.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwner.kt
@@ -16,6 +16,7 @@
package androidx.compose.ui.focus
+import androidx.collection.MutableObjectList
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.input.key.KeyEvent
@@ -147,6 +148,9 @@
/** Schedule the owner to be invalidated after onApplyChanges. */
fun scheduleInvalidationForOwner()
+ /** Listeners that will be notified when the active item changes. */
+ val listeners: MutableObjectList<FocusListener>
+
/** The focus state of the root focus node. */
val rootState: FocusState
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwnerImpl.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwnerImpl.kt
index 07a8cdf..01e0582 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwnerImpl.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwnerImpl.kt
@@ -17,6 +17,7 @@
package androidx.compose.ui.focus
import androidx.collection.MutableLongSet
+import androidx.collection.MutableObjectList
import androidx.compose.ui.ComposeUiFlags
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
@@ -465,15 +466,19 @@
override val rootState: FocusState
get() = rootFocusNode.focusState
+ override val listeners: MutableObjectList<FocusListener> = MutableObjectList(1)
+
override var activeFocusTargetNode: FocusTargetNode? = null
set(value) {
val previousValue = field
field = value
if (value == null || previousValue !== value) isFocusCaptured = false
+ if (@OptIn(ExperimentalComposeUiApi::class) ComposeUiFlags.isSemanticAutofillEnabled) {
+ listeners.forEach { it.onFocusChanged(previousValue, value) }
+ }
}
override var isFocusCaptured: Boolean = false
- get() = field
set(value) {
requirePrecondition(!value || activeFocusTargetNode != null) {
"Cannot capture focus when the active focus target node is unset"
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ApproachMeasureScope.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ApproachMeasureScope.kt
index eff14bb..98ca07d 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ApproachMeasureScope.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ApproachMeasureScope.kt
@@ -92,7 +92,7 @@
override fun layout(
width: Int,
height: Int,
- alignmentLines: Map<out AlignmentLine, Int>,
+ alignmentLines: Map<AlignmentLine, Int>,
rulers: (RulerScope.() -> Unit)?,
placementBlock: Placeable.PlacementScope.() -> Unit
): MeasureResult {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
index 7291168..afd27de 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
@@ -352,7 +352,7 @@
override fun layout(
width: Int,
height: Int,
- alignmentLines: Map<out AlignmentLine, Int>,
+ alignmentLines: Map<AlignmentLine, Int>,
rulers: (RulerScope.() -> Unit)?,
placementBlock: Placeable.PlacementScope.() -> Unit
): MeasureResult {
@@ -366,7 +366,7 @@
override val height: Int
get() = h
- override val alignmentLines: Map<out AlignmentLine, Int>
+ override val alignmentLines: Map<AlignmentLine, Int>
get() = alignmentLines
override val rulers: (RulerScope.() -> Unit)?
@@ -386,7 +386,7 @@
override fun layout(
width: Int,
height: Int,
- alignmentLines: Map<out AlignmentLine, Int>,
+ alignmentLines: Map<AlignmentLine, Int>,
rulers: (RulerScope.() -> Unit)?,
placementBlock: Placeable.PlacementScope.() -> Unit
): MeasureResult {
@@ -400,7 +400,7 @@
override val height: Int
get() = h
- override val alignmentLines: Map<out AlignmentLine, Int>
+ override val alignmentLines: Map<AlignmentLine, Int>
get() = alignmentLines
override val rulers: (RulerScope.() -> Unit)?
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/MeasureResult.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/MeasureResult.kt
index e7a3137..a2b16cc 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/MeasureResult.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/MeasureResult.kt
@@ -19,7 +19,7 @@
* Alignment lines that can be used by parents to align this layout. This only includes the
* alignment lines of this layout and not children.
*/
- val alignmentLines: Map<out AlignmentLine, Int>
+ val alignmentLines: Map<AlignmentLine, Int>
/**
* An optional lambda function used to create [Ruler]s for child layout. This may be
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/MeasureScope.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/MeasureScope.kt
index 978165e..36e7ea8 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/MeasureScope.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/MeasureScope.kt
@@ -47,7 +47,7 @@
fun layout(
width: Int,
height: Int,
- alignmentLines: Map<out AlignmentLine, Int> = emptyMap(),
+ alignmentLines: Map<AlignmentLine, Int> = emptyMap(),
placementBlock: Placeable.PlacementScope.() -> Unit
) = layout(width, height, alignmentLines, null, placementBlock)
@@ -69,7 +69,7 @@
fun layout(
width: Int,
height: Int,
- alignmentLines: Map<out AlignmentLine, Int> = emptyMap(),
+ alignmentLines: Map<AlignmentLine, Int> = emptyMap(),
rulers: (RulerScope.() -> Unit)? = null,
placementBlock: Placeable.PlacementScope.() -> Unit
): MeasureResult {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
index e0db1cb..c0045b8 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
@@ -928,7 +928,7 @@
override fun layout(
width: Int,
height: Int,
- alignmentLines: Map<out AlignmentLine, Int>,
+ alignmentLines: Map<AlignmentLine, Int>,
rulers: (RulerScope.() -> Unit)?,
placementBlock: Placeable.PlacementScope.() -> Unit
): MeasureResult {
@@ -940,7 +940,7 @@
override val height: Int
get() = height
- override val alignmentLines: Map<out AlignmentLine, Int>
+ override val alignmentLines: Map<AlignmentLine, Int>
get() = alignmentLines
override val rulers: (RulerScope.() -> Unit)?
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatableNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatableNode.kt
index d0ada4b..08505aa 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatableNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatableNode.kt
@@ -23,6 +23,7 @@
import androidx.compose.ui.internal.checkPrecondition
import androidx.compose.ui.internal.checkPreconditionNotNull
import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.semantics.SemanticsInfo
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
@@ -325,6 +326,8 @@
}
.layoutNode
+internal fun DelegatableNode.requireSemanticsInfo(): SemanticsInfo = requireLayoutNode()
+
internal fun DelegatableNode.requireOwner(): Owner =
checkPreconditionNotNull(requireLayoutNode().owner) { "This node does not have an owner." }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeAlignmentLines.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeAlignmentLines.kt
index fe22dff..a4d667fb 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeAlignmentLines.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeAlignmentLines.kt
@@ -94,9 +94,9 @@
/** The alignment lines of this layout, inherited + intrinsic */
private val alignmentLineMap: MutableMap<AlignmentLine, Int> = hashMapOf()
- fun getLastCalculation(): Map<out AlignmentLine, Int> = alignmentLineMap
+ fun getLastCalculation(): Map<AlignmentLine, Int> = alignmentLineMap
- protected abstract val NodeCoordinator.alignmentLinesMap: Map<out AlignmentLine, Int>
+ protected abstract val NodeCoordinator.alignmentLinesMap: Map<AlignmentLine, Int>
protected abstract fun NodeCoordinator.getPositionFor(alignmentLine: AlignmentLine): Int
@@ -201,7 +201,7 @@
internal class LayoutNodeAlignmentLines(alignmentLinesOwner: AlignmentLinesOwner) :
AlignmentLines(alignmentLinesOwner) {
- override val NodeCoordinator.alignmentLinesMap: Map<out AlignmentLine, Int>
+ override val NodeCoordinator.alignmentLinesMap: Map<AlignmentLine, Int>
get() = measureResult.alignmentLines
override fun NodeCoordinator.getPositionFor(alignmentLine: AlignmentLine): Int =
@@ -215,7 +215,7 @@
internal class LookaheadAlignmentLines(alignmentLinesOwner: AlignmentLinesOwner) :
AlignmentLines(alignmentLinesOwner) {
- override val NodeCoordinator.alignmentLinesMap: Map<out AlignmentLine, Int>
+ override val NodeCoordinator.alignmentLinesMap: Map<AlignmentLine, Int>
get() = lookaheadDelegate!!.measureResult.alignmentLines
override fun NodeCoordinator.getPositionFor(alignmentLine: AlignmentLine): Int =
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt
index 53d3cdb..cb30258 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt
@@ -427,7 +427,7 @@
fun layoutChildren()
/** Recalculate the alignment lines if dirty, and layout children as needed. */
- fun calculateAlignmentLines(): Map<out AlignmentLine, Int>
+ fun calculateAlignmentLines(): Map<AlignmentLine, Int>
/**
* Parent [AlignmentLinesOwner]. This will be the AlignmentLinesOwner for the same pass but for
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LookaheadDelegate.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LookaheadDelegate.kt
index e74274b..8a90850 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LookaheadDelegate.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LookaheadDelegate.kt
@@ -223,7 +223,7 @@
override fun layout(
width: Int,
height: Int,
- alignmentLines: Map<out AlignmentLine, Int>,
+ alignmentLines: Map<AlignmentLine, Int>,
rulers: (RulerScope.() -> Unit)?,
placementBlock: PlacementScope.() -> Unit
): MeasureResult {
@@ -235,7 +235,7 @@
override val height: Int
get() = height
- override val alignmentLines: Map<out AlignmentLine, Int>
+ override val alignmentLines: Map<AlignmentLine, Int>
get() = alignmentLines
override val rulers: (RulerScope.() -> Unit)?
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LookaheadPassDelegate.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LookaheadPassDelegate.kt
index 6743401..287109e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LookaheadPassDelegate.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LookaheadPassDelegate.kt
@@ -316,7 +316,7 @@
forEachChildDelegate { it.markNodeAndSubtreeAsNotPlaced(true) }
}
- override fun calculateAlignmentLines(): Map<out AlignmentLine, Int> {
+ override fun calculateAlignmentLines(): Map<AlignmentLine, Int> {
if (!duringAlignmentLinesQuery) {
if (layoutState == LayoutState.LookaheadMeasuring) {
// Mark alignments used by modifier
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasurePassDelegate.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasurePassDelegate.kt
index 9dfdc08..ae8a8f0 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasurePassDelegate.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasurePassDelegate.kt
@@ -769,7 +769,7 @@
return true
}
- override fun calculateAlignmentLines(): Map<out AlignmentLine, Int> {
+ override fun calculateAlignmentLines(): Map<AlignmentLine, Int> {
if (!duringAlignmentLinesQuery) {
// Mark alignments used by modifier
if (layoutState == LayoutState.Measuring) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
index a59cb92..2559695 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
@@ -1540,7 +1540,7 @@
@Suppress("PrimitiveInCollection")
private fun compareEquals(
a: MutableObjectIntMap<AlignmentLine>?,
- b: Map<out AlignmentLine, Int>
+ b: Map<AlignmentLine, Int>
): Boolean {
if (a == null) return false
if (a.size != b.size) return false
diff --git a/coordinatorlayout/coordinatorlayout/api/1.3.0-beta01.txt b/coordinatorlayout/coordinatorlayout/api/1.3.0-beta01.txt
new file mode 100644
index 0000000..3026d4b
--- /dev/null
+++ b/coordinatorlayout/coordinatorlayout/api/1.3.0-beta01.txt
@@ -0,0 +1,101 @@
+// Signature format: 4.0
+package androidx.coordinatorlayout.widget {
+
+ public class CoordinatorLayout extends android.view.ViewGroup implements androidx.core.view.NestedScrollingParent2 androidx.core.view.NestedScrollingParent3 {
+ ctor public CoordinatorLayout(android.content.Context);
+ ctor public CoordinatorLayout(android.content.Context, android.util.AttributeSet?);
+ ctor public CoordinatorLayout(android.content.Context, android.util.AttributeSet?, @AttrRes int);
+ method public void dispatchDependentViewsChanged(android.view.View);
+ method public boolean doViewsOverlap(android.view.View, android.view.View);
+ method protected androidx.coordinatorlayout.widget.CoordinatorLayout.LayoutParams! generateDefaultLayoutParams();
+ method public androidx.coordinatorlayout.widget.CoordinatorLayout.LayoutParams! generateLayoutParams(android.util.AttributeSet!);
+ method protected androidx.coordinatorlayout.widget.CoordinatorLayout.LayoutParams! generateLayoutParams(android.view.ViewGroup.LayoutParams!);
+ method public java.util.List<android.view.View!> getDependencies(android.view.View);
+ method public java.util.List<android.view.View!> getDependents(android.view.View);
+ method public android.graphics.drawable.Drawable? getStatusBarBackground();
+ method public boolean isPointInChildBounds(android.view.View, int, int);
+ method public void onAttachedToWindow();
+ method public void onDetachedFromWindow();
+ method public void onDraw(android.graphics.Canvas);
+ method public void onLayoutChild(android.view.View, int);
+ method public void onMeasureChild(android.view.View, int, int, int, int);
+ method public void onNestedPreScroll(android.view.View, int, int, int[], int);
+ method public void onNestedScroll(android.view.View, int, int, int, int, int);
+ method public void onNestedScroll(android.view.View, int, int, int, int, int, int[]);
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, int, int);
+ method public boolean onStartNestedScroll(android.view.View, android.view.View, int, int);
+ method public void onStopNestedScroll(android.view.View, int);
+ method public void setStatusBarBackground(android.graphics.drawable.Drawable?);
+ method public void setStatusBarBackgroundColor(@ColorInt int);
+ method public void setStatusBarBackgroundResource(@DrawableRes int);
+ }
+
+ public static interface CoordinatorLayout.AttachedBehavior {
+ method public androidx.coordinatorlayout.widget.CoordinatorLayout.Behavior getBehavior();
+ }
+
+ public abstract static class CoordinatorLayout.Behavior<V extends android.view.View> {
+ ctor public CoordinatorLayout.Behavior();
+ ctor public CoordinatorLayout.Behavior(android.content.Context, android.util.AttributeSet?);
+ method public boolean blocksInteractionBelow(androidx.coordinatorlayout.widget.CoordinatorLayout, V);
+ method public boolean getInsetDodgeRect(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.graphics.Rect);
+ method @ColorInt public int getScrimColor(androidx.coordinatorlayout.widget.CoordinatorLayout, V);
+ method @FloatRange(from=0, to=1) public float getScrimOpacity(androidx.coordinatorlayout.widget.CoordinatorLayout, V);
+ method public static Object? getTag(android.view.View);
+ method public boolean layoutDependsOn(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View);
+ method public androidx.core.view.WindowInsetsCompat onApplyWindowInsets(androidx.coordinatorlayout.widget.CoordinatorLayout, V, androidx.core.view.WindowInsetsCompat);
+ method public void onAttachedToLayoutParams(androidx.coordinatorlayout.widget.CoordinatorLayout.LayoutParams);
+ method public boolean onDependentViewChanged(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View);
+ method public void onDependentViewRemoved(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View);
+ method public void onDetachedFromLayoutParams();
+ method public boolean onInterceptTouchEvent(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.MotionEvent);
+ method public boolean onLayoutChild(androidx.coordinatorlayout.widget.CoordinatorLayout, V, int);
+ method public boolean onMeasureChild(androidx.coordinatorlayout.widget.CoordinatorLayout, V, int, int, int, int);
+ method public boolean onNestedFling(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, float, float, boolean);
+ method public boolean onNestedPreFling(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, float, float);
+ method @Deprecated public void onNestedPreScroll(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, int, int, int[]);
+ method public void onNestedPreScroll(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, int, int, int[], int);
+ method @Deprecated public void onNestedScroll(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, int, int, int, int);
+ method @Deprecated public void onNestedScroll(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, int, int, int, int, int);
+ method public void onNestedScroll(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, int, int, int, int, int, int[]);
+ method @Deprecated public void onNestedScrollAccepted(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, android.view.View, int);
+ method public void onNestedScrollAccepted(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, android.view.View, int, int);
+ method public boolean onRequestChildRectangleOnScreen(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.graphics.Rect, boolean);
+ method public void onRestoreInstanceState(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.os.Parcelable);
+ method public android.os.Parcelable? onSaveInstanceState(androidx.coordinatorlayout.widget.CoordinatorLayout, V);
+ method @Deprecated public boolean onStartNestedScroll(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, android.view.View, int);
+ method public boolean onStartNestedScroll(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, android.view.View, int, int);
+ method @Deprecated public void onStopNestedScroll(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View);
+ method public void onStopNestedScroll(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, int);
+ method public boolean onTouchEvent(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.MotionEvent);
+ method public static void setTag(android.view.View, Object?);
+ }
+
+ @Deprecated @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) public static @interface CoordinatorLayout.DefaultBehavior {
+ method @Deprecated public abstract Class<? extends androidx.coordinatorlayout.widget.CoordinatorLayout.Behavior!> value();
+ }
+
+ public static class CoordinatorLayout.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+ ctor public CoordinatorLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
+ ctor public CoordinatorLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
+ ctor public CoordinatorLayout.LayoutParams(androidx.coordinatorlayout.widget.CoordinatorLayout.LayoutParams);
+ ctor public CoordinatorLayout.LayoutParams(int, int);
+ method @IdRes public int getAnchorId();
+ method public androidx.coordinatorlayout.widget.CoordinatorLayout.Behavior? getBehavior();
+ method public void setAnchorId(@IdRes int);
+ method public void setBehavior(androidx.coordinatorlayout.widget.CoordinatorLayout.Behavior?);
+ field public int anchorGravity;
+ field public int dodgeInsetEdges;
+ field public int gravity;
+ field public int insetEdge;
+ field public int keyline;
+ }
+
+ protected static class CoordinatorLayout.SavedState extends androidx.customview.view.AbsSavedState {
+ ctor public CoordinatorLayout.SavedState(android.os.Parcel!, ClassLoader!);
+ ctor public CoordinatorLayout.SavedState(android.os.Parcelable!);
+ field public static final android.os.Parcelable.Creator<androidx.coordinatorlayout.widget.CoordinatorLayout.SavedState!>! CREATOR;
+ }
+
+}
+
diff --git a/coordinatorlayout/coordinatorlayout/api/res-1.3.0-beta01.txt b/coordinatorlayout/coordinatorlayout/api/res-1.3.0-beta01.txt
new file mode 100644
index 0000000..6af58e16
--- /dev/null
+++ b/coordinatorlayout/coordinatorlayout/api/res-1.3.0-beta01.txt
@@ -0,0 +1,9 @@
+attr keylines
+attr layout_anchor
+attr layout_anchorGravity
+attr layout_behavior
+attr layout_dodgeInsetEdges
+attr layout_insetEdge
+attr layout_keyline
+attr statusBarBackground
+style Widget_Support_CoordinatorLayout
diff --git a/coordinatorlayout/coordinatorlayout/api/restricted_1.3.0-beta01.txt b/coordinatorlayout/coordinatorlayout/api/restricted_1.3.0-beta01.txt
new file mode 100644
index 0000000..22ecfc8
--- /dev/null
+++ b/coordinatorlayout/coordinatorlayout/api/restricted_1.3.0-beta01.txt
@@ -0,0 +1,105 @@
+// Signature format: 4.0
+package androidx.coordinatorlayout.widget {
+
+ public class CoordinatorLayout extends android.view.ViewGroup implements androidx.core.view.NestedScrollingParent2 androidx.core.view.NestedScrollingParent3 {
+ ctor public CoordinatorLayout(android.content.Context);
+ ctor public CoordinatorLayout(android.content.Context, android.util.AttributeSet?);
+ ctor public CoordinatorLayout(android.content.Context, android.util.AttributeSet?, @AttrRes int);
+ method public void dispatchDependentViewsChanged(android.view.View);
+ method public boolean doViewsOverlap(android.view.View, android.view.View);
+ method protected androidx.coordinatorlayout.widget.CoordinatorLayout.LayoutParams! generateDefaultLayoutParams();
+ method public androidx.coordinatorlayout.widget.CoordinatorLayout.LayoutParams! generateLayoutParams(android.util.AttributeSet!);
+ method protected androidx.coordinatorlayout.widget.CoordinatorLayout.LayoutParams! generateLayoutParams(android.view.ViewGroup.LayoutParams!);
+ method public java.util.List<android.view.View!> getDependencies(android.view.View);
+ method public java.util.List<android.view.View!> getDependents(android.view.View);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final androidx.core.view.WindowInsetsCompat? getLastWindowInsets();
+ method public android.graphics.drawable.Drawable? getStatusBarBackground();
+ method public boolean isPointInChildBounds(android.view.View, int, int);
+ method public void onAttachedToWindow();
+ method public void onDetachedFromWindow();
+ method public void onDraw(android.graphics.Canvas);
+ method public void onLayoutChild(android.view.View, int);
+ method public void onMeasureChild(android.view.View, int, int, int, int);
+ method public void onNestedPreScroll(android.view.View, int, int, int[], int);
+ method public void onNestedScroll(android.view.View, int, int, int, int, int);
+ method public void onNestedScroll(android.view.View, int, int, int, int, @androidx.core.view.ViewCompat.NestedScrollType int, int[]);
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, int, int);
+ method public boolean onStartNestedScroll(android.view.View, android.view.View, int, int);
+ method public void onStopNestedScroll(android.view.View, int);
+ method public void setStatusBarBackground(android.graphics.drawable.Drawable?);
+ method public void setStatusBarBackgroundColor(@ColorInt int);
+ method public void setStatusBarBackgroundResource(@DrawableRes int);
+ }
+
+ public static interface CoordinatorLayout.AttachedBehavior {
+ method public androidx.coordinatorlayout.widget.CoordinatorLayout.Behavior getBehavior();
+ }
+
+ public abstract static class CoordinatorLayout.Behavior<V extends android.view.View> {
+ ctor public CoordinatorLayout.Behavior();
+ ctor public CoordinatorLayout.Behavior(android.content.Context, android.util.AttributeSet?);
+ method public boolean blocksInteractionBelow(androidx.coordinatorlayout.widget.CoordinatorLayout, V);
+ method public boolean getInsetDodgeRect(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.graphics.Rect);
+ method @ColorInt public int getScrimColor(androidx.coordinatorlayout.widget.CoordinatorLayout, V);
+ method @FloatRange(from=0, to=1) public float getScrimOpacity(androidx.coordinatorlayout.widget.CoordinatorLayout, V);
+ method public static Object? getTag(android.view.View);
+ method public boolean layoutDependsOn(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View);
+ method public androidx.core.view.WindowInsetsCompat onApplyWindowInsets(androidx.coordinatorlayout.widget.CoordinatorLayout, V, androidx.core.view.WindowInsetsCompat);
+ method public void onAttachedToLayoutParams(androidx.coordinatorlayout.widget.CoordinatorLayout.LayoutParams);
+ method public boolean onDependentViewChanged(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View);
+ method public void onDependentViewRemoved(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View);
+ method public void onDetachedFromLayoutParams();
+ method public boolean onInterceptTouchEvent(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.MotionEvent);
+ method public boolean onLayoutChild(androidx.coordinatorlayout.widget.CoordinatorLayout, V, int);
+ method public boolean onMeasureChild(androidx.coordinatorlayout.widget.CoordinatorLayout, V, int, int, int, int);
+ method public boolean onNestedFling(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, float, float, boolean);
+ method public boolean onNestedPreFling(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, float, float);
+ method @Deprecated public void onNestedPreScroll(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, int, int, int[]);
+ method public void onNestedPreScroll(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, int, int, int[], @androidx.core.view.ViewCompat.NestedScrollType int);
+ method @Deprecated public void onNestedScroll(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, int, int, int, int);
+ method @Deprecated public void onNestedScroll(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, int, int, int, int, @androidx.core.view.ViewCompat.NestedScrollType int);
+ method public void onNestedScroll(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, int, int, int, int, @androidx.core.view.ViewCompat.NestedScrollType int, int[]);
+ method @Deprecated public void onNestedScrollAccepted(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, android.view.View, @androidx.core.view.ViewCompat.ScrollAxis int);
+ method public void onNestedScrollAccepted(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, android.view.View, @androidx.core.view.ViewCompat.ScrollAxis int, @androidx.core.view.ViewCompat.NestedScrollType int);
+ method public boolean onRequestChildRectangleOnScreen(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.graphics.Rect, boolean);
+ method public void onRestoreInstanceState(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.os.Parcelable);
+ method public android.os.Parcelable? onSaveInstanceState(androidx.coordinatorlayout.widget.CoordinatorLayout, V);
+ method @Deprecated public boolean onStartNestedScroll(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, android.view.View, @androidx.core.view.ViewCompat.ScrollAxis int);
+ method public boolean onStartNestedScroll(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, android.view.View, @androidx.core.view.ViewCompat.ScrollAxis int, @androidx.core.view.ViewCompat.NestedScrollType int);
+ method @Deprecated public void onStopNestedScroll(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View);
+ method public void onStopNestedScroll(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.View, @androidx.core.view.ViewCompat.NestedScrollType int);
+ method public boolean onTouchEvent(androidx.coordinatorlayout.widget.CoordinatorLayout, V, android.view.MotionEvent);
+ method public static void setTag(android.view.View, Object?);
+ }
+
+ @Deprecated @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) public static @interface CoordinatorLayout.DefaultBehavior {
+ method @Deprecated public abstract Class<? extends androidx.coordinatorlayout.widget.CoordinatorLayout.Behavior!> value();
+ }
+
+ @IntDef({0x0, 0x1, 0x2}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface CoordinatorLayout.DispatchChangeEvent {
+ }
+
+ public static class CoordinatorLayout.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+ ctor public CoordinatorLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
+ ctor public CoordinatorLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
+ ctor public CoordinatorLayout.LayoutParams(androidx.coordinatorlayout.widget.CoordinatorLayout.LayoutParams);
+ ctor public CoordinatorLayout.LayoutParams(int, int);
+ method @IdRes public int getAnchorId();
+ method public androidx.coordinatorlayout.widget.CoordinatorLayout.Behavior? getBehavior();
+ method public void setAnchorId(@IdRes int);
+ method public void setBehavior(androidx.coordinatorlayout.widget.CoordinatorLayout.Behavior?);
+ field public int anchorGravity;
+ field public int dodgeInsetEdges;
+ field public int gravity;
+ field public int insetEdge;
+ field public int keyline;
+ }
+
+ protected static class CoordinatorLayout.SavedState extends androidx.customview.view.AbsSavedState {
+ ctor public CoordinatorLayout.SavedState(android.os.Parcel!, ClassLoader!);
+ ctor public CoordinatorLayout.SavedState(android.os.Parcelable!);
+ field public static final android.os.Parcelable.Creator<androidx.coordinatorlayout.widget.CoordinatorLayout.SavedState!>! CREATOR;
+ }
+
+}
+
diff --git a/core/core-animation-integration-tests/OWNERS b/core/core-animation-integration-tests/OWNERS
index ef35137..df80a25 100644
--- a/core/core-animation-integration-tests/OWNERS
+++ b/core/core-animation-integration-tests/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 461355
[email protected]
[email protected]
diff --git a/core/core-animation-testing/OWNERS b/core/core-animation-testing/OWNERS
index ef35137..df80a25 100644
--- a/core/core-animation-testing/OWNERS
+++ b/core/core-animation-testing/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 461355
[email protected]
[email protected]
diff --git a/core/core-animation/OWNERS b/core/core-animation/OWNERS
index ef35137..df80a25 100644
--- a/core/core-animation/OWNERS
+++ b/core/core-animation/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 461355
[email protected]
[email protected]
diff --git a/core/core-splashscreen/OWNERS b/core/core-splashscreen/OWNERS
index 5067ff1..46e1787 100644
--- a/core/core-splashscreen/OWNERS
+++ b/core/core-splashscreen/OWNERS
@@ -2,5 +2,4 @@
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
\ No newline at end of file
diff --git a/core/core/build.gradle b/core/core/build.gradle
index 4737ed5..a033a745 100644
--- a/core/core/build.gradle
+++ b/core/core/build.gradle
@@ -26,7 +26,7 @@
api("androidx.annotation:annotation-experimental:1.4.1")
api("androidx.lifecycle:lifecycle-runtime:2.6.2")
api("androidx.versionedparcelable:versionedparcelable:1.1.1")
- api(project(":core:core-viewtree"))
+ api("androidx.core:core-viewtree:1.0.0-alpha01")
implementation("androidx.collection:collection:1.4.2")
implementation("androidx.concurrent:concurrent-futures:1.0.0")
implementation("androidx.interpolator:interpolator:1.0.0")
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/BeginCreateCredentialRequest.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/BeginCreateCredentialRequest.kt
index 48e45ce..f843732 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/BeginCreateCredentialRequest.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/BeginCreateCredentialRequest.kt
@@ -19,6 +19,7 @@
import android.os.Build
import android.os.Bundle
import androidx.annotation.RequiresApi
+import androidx.annotation.RestrictTo
import androidx.credentials.PasswordCredential
import androidx.credentials.PublicKeyCredential
import androidx.credentials.internal.FrameworkClassParsingException
@@ -88,7 +89,9 @@
}
companion object {
- internal fun createFrom(
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @JvmStatic
+ fun createFrom(
type: String,
candidateQueryData: Bundle,
callingAppInfo: CallingAppInfo?
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/BeginGetCredentialRequest.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/BeginGetCredentialRequest.kt
index de188c2..8098259 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/BeginGetCredentialRequest.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/BeginGetCredentialRequest.kt
@@ -19,6 +19,7 @@
import android.os.Build
import android.os.Bundle
import androidx.annotation.RequiresApi
+import androidx.annotation.RestrictTo
import androidx.credentials.provider.CallingAppInfo.Companion.extractCallingAppInfo
import androidx.credentials.provider.CallingAppInfo.Companion.setCallingAppInfo
import androidx.credentials.provider.utils.BeginGetCredentialUtil
@@ -130,6 +131,15 @@
}
companion object {
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @JvmStatic
+ fun createFrom(
+ id: String,
+ type: String,
+ candidateQueryData: Bundle
+ ): BeginGetCredentialOption =
+ BeginGetCredentialOption.createFrom(id, type, candidateQueryData)
+
/**
* Helper method to convert the class to a parcelable [Bundle], in case the class instance
* needs to be sent across a process. Consumers of this method should use [fromBundle] to
diff --git a/development/project-creator/create_project.py b/development/project-creator/create_project.py
index c3b1f60..3a447f6 100755
--- a/development/project-creator/create_project.py
+++ b/development/project-creator/create_project.py
@@ -23,8 +23,7 @@
from textwrap import dedent
from shutil import rmtree
from shutil import copyfile
-from distutils.dir_util import copy_tree
-from distutils.dir_util import DistutilsFileError
+from shutil import copytree
import re
try:
@@ -82,8 +81,8 @@
print_e('cp error: Source path %s does not exist.' % src_path_dir)
return None
try:
- copy_tree(src_path_dir, dst_path_dir)
- except DistutilsFileError as err:
+ copytree(src_path_dir, dst_path_dir, dirs_exist_ok=True)
+ except Error as err:
print_e('FAIL: Unable to copy %s to destination %s' % (src_path_dir, dst_path_dir))
return None
return dst_path_dir
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 2900002..10f8f5f 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -15,9 +15,9 @@
}
dependencies {
- docs("androidx.activity:activity:1.10.0-rc01")
- docs("androidx.activity:activity-compose:1.10.0-rc01")
- docs("androidx.activity:activity-ktx:1.10.0-rc01")
+ docs("androidx.activity:activity:1.10.0")
+ docs("androidx.activity:activity-compose:1.10.0")
+ docs("androidx.activity:activity-ktx:1.10.0")
// ads-identifier is deprecated
docsWithoutApiSince("androidx.ads:ads-identifier:1.0.0-alpha05")
docsWithoutApiSince("androidx.ads:ads-identifier-common:1.0.0-alpha05")
@@ -26,12 +26,12 @@
docs("androidx.annotation:annotation-experimental:1.5.0-alpha01")
docs("androidx.appcompat:appcompat:1.7.0")
docs("androidx.appcompat:appcompat-resources:1.7.0")
- docs("androidx.appsearch:appsearch:1.1.0-alpha07")
- docs("androidx.appsearch:appsearch-builtin-types:1.1.0-alpha07")
- docs("androidx.appsearch:appsearch-ktx:1.1.0-alpha07")
- docs("androidx.appsearch:appsearch-local-storage:1.1.0-alpha07")
- docs("androidx.appsearch:appsearch-platform-storage:1.1.0-alpha07")
- docs("androidx.appsearch:appsearch-play-services-storage:1.1.0-alpha07")
+ docs("androidx.appsearch:appsearch:1.1.0-beta01")
+ docs("androidx.appsearch:appsearch-builtin-types:1.1.0-beta01")
+ docs("androidx.appsearch:appsearch-ktx:1.1.0-beta01")
+ docs("androidx.appsearch:appsearch-local-storage:1.1.0-beta01")
+ docs("androidx.appsearch:appsearch-platform-storage:1.1.0-beta01")
+ docs("androidx.appsearch:appsearch-play-services-storage:1.1.0-beta01")
docs("androidx.arch.core:core-common:2.2.0")
docs("androidx.arch.core:core-runtime:2.2.0")
docs("androidx.arch.core:core-testing:2.2.0")
@@ -48,72 +48,72 @@
docs("androidx.bluetooth:bluetooth-testing:1.0.0-alpha02")
docs("androidx.browser:browser:1.8.0")
docs("androidx.camera.media3:media3-effect:1.0.0-alpha01")
- docs("androidx.camera.viewfinder:viewfinder-compose:1.4.0-alpha11")
- docs("androidx.camera.viewfinder:viewfinder-core:1.4.0-alpha11")
- docs("androidx.camera.viewfinder:viewfinder-view:1.4.0-alpha11")
- docs("androidx.camera:camera-camera2:1.5.0-alpha04")
- docs("androidx.camera:camera-compose:1.5.0-alpha04")
+ docs("androidx.camera.viewfinder:viewfinder-compose:1.4.0-alpha12")
+ docs("androidx.camera.viewfinder:viewfinder-core:1.4.0-alpha12")
+ docs("androidx.camera.viewfinder:viewfinder-view:1.4.0-alpha12")
+ docs("androidx.camera:camera-camera2:1.5.0-alpha05")
+ docs("androidx.camera:camera-compose:1.5.0-alpha05")
samples("androidx.camera:camera-compose-samples:1.5.0-alpha01")
- docs("androidx.camera:camera-core:1.5.0-alpha04")
- docs("androidx.camera:camera-effects:1.5.0-alpha04")
- docs("androidx.camera:camera-extensions:1.5.0-alpha04")
+ docs("androidx.camera:camera-core:1.5.0-alpha05")
+ docs("androidx.camera:camera-effects:1.5.0-alpha05")
+ docs("androidx.camera:camera-extensions:1.5.0-alpha05")
stubs(fileTree(dir: "../camera/camera-extensions-stub", include: ["camera-extensions-stub.jar"]))
- docs("androidx.camera:camera-feature-combination-query:1.5.0-alpha04")
- docs("androidx.camera:camera-feature-combination-query-play-services:1.5.0-alpha04")
- docs("androidx.camera:camera-lifecycle:1.5.0-alpha04")
- docs("androidx.camera:camera-mlkit-vision:1.5.0-alpha04")
- docs("androidx.camera:camera-video:1.5.0-alpha04")
- docs("androidx.camera:camera-view:1.5.0-alpha04")
- docs("androidx.car.app:app:1.7.0-beta03")
- docs("androidx.car.app:app-automotive:1.7.0-beta03")
- docs("androidx.car.app:app-projected:1.7.0-beta03")
- docs("androidx.car.app:app-testing:1.7.0-beta03")
+ docs("androidx.camera:camera-feature-combination-query:1.5.0-alpha05")
+ docs("androidx.camera:camera-feature-combination-query-play-services:1.5.0-alpha05")
+ docs("androidx.camera:camera-lifecycle:1.5.0-alpha05")
+ docs("androidx.camera:camera-mlkit-vision:1.5.0-alpha05")
+ docs("androidx.camera:camera-video:1.5.0-alpha05")
+ docs("androidx.camera:camera-view:1.5.0-alpha05")
+ docs("androidx.car.app:app:1.7.0-rc01")
+ docs("androidx.car.app:app-automotive:1.7.0-rc01")
+ docs("androidx.car.app:app-projected:1.7.0-rc01")
+ docs("androidx.car.app:app-testing:1.7.0-rc01")
docs("androidx.cardview:cardview:1.0.0")
- kmpDocs("androidx.collection:collection:1.5.0-beta01")
- docs("androidx.collection:collection-ktx:1.5.0-beta01")
- kmpDocs("androidx.compose.animation:animation:1.8.0-alpha07")
- kmpDocs("androidx.compose.animation:animation-core:1.8.0-alpha07")
- kmpDocs("androidx.compose.animation:animation-graphics:1.8.0-alpha07")
- kmpDocs("androidx.compose.foundation:foundation:1.8.0-alpha07")
- kmpDocs("androidx.compose.foundation:foundation-layout:1.8.0-alpha07")
- kmpDocs("androidx.compose.material3.adaptive:adaptive:1.1.0-alpha08")
- kmpDocs("androidx.compose.material3.adaptive:adaptive-layout:1.1.0-alpha08")
- kmpDocs("androidx.compose.material3.adaptive:adaptive-navigation:1.1.0-alpha08")
+ kmpDocs("androidx.collection:collection:1.5.0-beta02")
+ docs("androidx.collection:collection-ktx:1.5.0-beta02")
+ kmpDocs("androidx.compose.animation:animation:1.8.0-alpha08")
+ kmpDocs("androidx.compose.animation:animation-core:1.8.0-alpha08")
+ kmpDocs("androidx.compose.animation:animation-graphics:1.8.0-alpha08")
+ kmpDocs("androidx.compose.foundation:foundation:1.8.0-alpha08")
+ kmpDocs("androidx.compose.foundation:foundation-layout:1.8.0-alpha08")
+ kmpDocs("androidx.compose.material3.adaptive:adaptive:1.1.0-alpha09")
+ kmpDocs("androidx.compose.material3.adaptive:adaptive-layout:1.1.0-alpha09")
+ kmpDocs("androidx.compose.material3.adaptive:adaptive-navigation:1.1.0-alpha09")
kmpDocs("androidx.compose.material3.adaptive:adaptive-render-strategy:1.1.0-alpha05")
- kmpDocs("androidx.compose.material3:material3:1.4.0-alpha05")
- kmpDocs("androidx.compose.material3:material3-adaptive-navigation-suite:1.4.0-alpha05")
+ kmpDocs("androidx.compose.material3:material3:1.4.0-alpha06")
+ kmpDocs("androidx.compose.material3:material3-adaptive-navigation-suite:1.4.0-alpha06")
kmpDocs("androidx.compose.material3:material3-common:1.0.0-alpha01")
- kmpDocs("androidx.compose.material3:material3-window-size-class:1.4.0-alpha05")
- kmpDocs("androidx.compose.material:material:1.8.0-alpha07")
+ kmpDocs("androidx.compose.material3:material3-window-size-class:1.4.0-alpha06")
+ kmpDocs("androidx.compose.material:material:1.8.0-alpha08")
kmpDocs("androidx.compose.material:material-icons-core:1.7.6")
- docs("androidx.compose.material:material-navigation:1.8.0-alpha07")
- kmpDocs("androidx.compose.material:material-ripple:1.8.0-alpha07")
- kmpDocs("androidx.compose.runtime:runtime:1.8.0-alpha07")
- docs("androidx.compose.runtime:runtime-livedata:1.8.0-alpha07")
- docs("androidx.compose.runtime:runtime-rxjava2:1.8.0-alpha07")
- docs("androidx.compose.runtime:runtime-rxjava3:1.8.0-alpha07")
- kmpDocs("androidx.compose.runtime:runtime-saveable:1.8.0-alpha07")
- docs("androidx.compose.runtime:runtime-tracing:1.8.0-alpha07")
- kmpDocs("androidx.compose.ui:ui:1.8.0-alpha07")
- kmpDocs("androidx.compose.ui:ui-geometry:1.8.0-alpha07")
- kmpDocs("androidx.compose.ui:ui-graphics:1.8.0-alpha07")
- kmpDocs("androidx.compose.ui:ui-test:1.8.0-alpha07")
- kmpDocs("androidx.compose.ui:ui-test-junit4:1.8.0-alpha07")
- kmpDocs("androidx.compose.ui:ui-text:1.8.0-alpha07")
- docs("androidx.compose.ui:ui-text-google-fonts:1.8.0-alpha07")
- kmpDocs("androidx.compose.ui:ui-tooling:1.8.0-alpha07")
- kmpDocs("androidx.compose.ui:ui-tooling-data:1.8.0-alpha07")
- kmpDocs("androidx.compose.ui:ui-tooling-preview:1.8.0-alpha07")
- kmpDocs("androidx.compose.ui:ui-unit:1.8.0-alpha07")
- kmpDocs("androidx.compose.ui:ui-util:1.8.0-alpha07")
- docs("androidx.compose.ui:ui-viewbinding:1.8.0-alpha07")
+ docs("androidx.compose.material:material-navigation:1.8.0-alpha08")
+ kmpDocs("androidx.compose.material:material-ripple:1.8.0-alpha08")
+ kmpDocs("androidx.compose.runtime:runtime:1.8.0-alpha08")
+ docs("androidx.compose.runtime:runtime-livedata:1.8.0-alpha08")
+ docs("androidx.compose.runtime:runtime-rxjava2:1.8.0-alpha08")
+ docs("androidx.compose.runtime:runtime-rxjava3:1.8.0-alpha08")
+ kmpDocs("androidx.compose.runtime:runtime-saveable:1.8.0-alpha08")
+ docs("androidx.compose.runtime:runtime-tracing:1.8.0-alpha08")
+ kmpDocs("androidx.compose.ui:ui:1.8.0-alpha08")
+ kmpDocs("androidx.compose.ui:ui-geometry:1.8.0-alpha08")
+ kmpDocs("androidx.compose.ui:ui-graphics:1.8.0-alpha08")
+ kmpDocs("androidx.compose.ui:ui-test:1.8.0-alpha08")
+ kmpDocs("androidx.compose.ui:ui-test-junit4:1.8.0-alpha08")
+ kmpDocs("androidx.compose.ui:ui-text:1.8.0-alpha08")
+ docs("androidx.compose.ui:ui-text-google-fonts:1.8.0-alpha08")
+ kmpDocs("androidx.compose.ui:ui-tooling:1.8.0-alpha08")
+ kmpDocs("androidx.compose.ui:ui-tooling-data:1.8.0-alpha08")
+ kmpDocs("androidx.compose.ui:ui-tooling-preview:1.8.0-alpha08")
+ kmpDocs("androidx.compose.ui:ui-unit:1.8.0-alpha08")
+ kmpDocs("androidx.compose.ui:ui-util:1.8.0-alpha08")
+ docs("androidx.compose.ui:ui-viewbinding:1.8.0-alpha08")
docs("androidx.concurrent:concurrent-futures:1.3.0-alpha01")
docs("androidx.concurrent:concurrent-futures-ktx:1.3.0-alpha01")
docs("androidx.constraintlayout:constraintlayout:2.2.0")
kmpDocs("androidx.constraintlayout:constraintlayout-compose:1.1.0")
docs("androidx.constraintlayout:constraintlayout-core:1.1.0")
docs("androidx.contentpager:contentpager:1.0.0")
- docs("androidx.coordinatorlayout:coordinatorlayout:1.3.0-alpha02")
+ docs("androidx.coordinatorlayout:coordinatorlayout:1.3.0-alpha03")
docs("androidx.core:core:1.16.0-alpha01")
// TODO(b/294531403): Turn on apiSince for core-animation when it releases as alpha
docsWithoutApiSince("androidx.core:core-animation:1.0.0-rc01")
@@ -138,10 +138,10 @@
docs("androidx.credentials.registry:registry-digitalcredentials-preview:1.0.0-alpha01")
docs("androidx.credentials.registry:registry-provider:1.0.0-alpha01")
docs("androidx.credentials.registry:registry-provider-play-services:1.0.0-alpha01")
- docs("androidx.credentials:credentials:1.5.0-beta01")
+ docs("androidx.credentials:credentials:1.5.0-rc01")
docs("androidx.credentials:credentials-e2ee:1.0.0-alpha02")
docs("androidx.credentials:credentials-fido:1.0.0-alpha02")
- docs("androidx.credentials:credentials-play-services-auth:1.5.0-beta01")
+ docs("androidx.credentials:credentials-play-services-auth:1.5.0-rc01")
docs("androidx.cursoradapter:cursoradapter:1.0.0")
docs("androidx.customview:customview:1.2.0-alpha02")
// TODO(b/294531403): Turn on apiSince for customview-poolingcontainer when it releases as alpha
@@ -170,7 +170,7 @@
docs("androidx.emoji:emoji-bundled:1.2.0-alpha03")
docs("androidx.enterprise:enterprise-feedback:1.1.0")
docs("androidx.enterprise:enterprise-feedback-testing:1.1.0")
- docs("androidx.exifinterface:exifinterface:1.4.0-alpha01")
+ docs("androidx.exifinterface:exifinterface:1.4.0-beta01")
docs("androidx.fragment:fragment:1.8.5")
docs("androidx.fragment:fragment-compose:1.8.5")
docs("androidx.fragment:fragment-ktx:1.8.5")
@@ -189,11 +189,11 @@
docs("androidx.graphics:graphics-path:1.0.0")
kmpDocs("androidx.graphics:graphics-shapes:1.1.0-alpha01")
docs("androidx.gridlayout:gridlayout:1.1.0-beta01")
- docs("androidx.health.connect:connect-client:1.1.0-alpha10")
+ docs("androidx.health.connect:connect-client:1.1.0-alpha11")
samples("androidx.health.connect:connect-client-samples:1.1.0-alpha09")
docs("androidx.health.connect:connect-testing:1.0.0-alpha01")
docs("androidx.health:health-services-client:1.1.0-alpha05")
- docs("androidx.heifwriter:heifwriter:1.1.0-alpha03")
+ docs("androidx.heifwriter:heifwriter:1.1.0-alpha04")
docs("androidx.hilt:hilt-common:1.2.0")
docs("androidx.hilt:hilt-navigation:1.2.0")
docs("androidx.hilt:hilt-navigation-compose:1.2.0")
@@ -266,8 +266,8 @@
docsWithoutApiSince("androidx.media3:media3-transformer:1.6.0-alpha01")
docsWithoutApiSince("androidx.media3:media3-ui:1.6.0-alpha01")
docsWithoutApiSince("androidx.media3:media3-ui-leanback:1.6.0-alpha01")
- docs("androidx.mediarouter:mediarouter:1.8.0-alpha01")
- docs("androidx.mediarouter:mediarouter-testing:1.8.0-alpha01")
+ docs("androidx.mediarouter:mediarouter:1.8.0-alpha02")
+ docs("androidx.mediarouter:mediarouter-testing:1.8.0-alpha02")
docs("androidx.metrics:metrics-performance:1.0.0-beta01")
docs("androidx.navigation:navigation-common:2.9.0-alpha04")
docs("androidx.navigation:navigation-common-ktx:2.9.0-alpha04")
@@ -298,6 +298,8 @@
docs("androidx.pdf:pdf-viewer:1.0.0-alpha05")
docs("androidx.pdf:pdf-viewer-fragment:1.0.0-alpha05")
docs("androidx.percentlayout:percentlayout:1.0.1")
+ kmpDocs("androidx.performance:performance-annotation:1.0.0-alpha01")
+ docs("androidx.performance:performance-unsafe:1.0.0-alpha01")
docs("androidx.preference:preference:1.2.1")
docs("androidx.preference:preference-ktx:1.2.1")
docs("androidx.print:print:1.1.0-beta01")
@@ -315,7 +317,7 @@
docs("androidx.privacysandbox.ui:ui-provider:1.0.0-alpha12")
docs("androidx.profileinstaller:profileinstaller:1.4.1")
docs("androidx.recommendation:recommendation:1.0.0")
- docs("androidx.recyclerview:recyclerview:1.4.0-rc01")
+ docs("androidx.recyclerview:recyclerview:1.4.0")
docs("androidx.recyclerview:recyclerview-selection:2.0.0-alpha01")
docs("androidx.remotecallback:remotecallback:1.0.0-alpha02")
docs("androidx.resourceinspection:resourceinspection-annotation:1.0.1")
@@ -381,8 +383,8 @@
docs("androidx.tracing:tracing-perfetto-handshake:1.0.0")
docs("androidx.transition:transition:1.6.0-alpha01")
docs("androidx.transition:transition-ktx:1.6.0-alpha01")
- docs("androidx.tv:tv-foundation:1.0.0-alpha11")
- docs("androidx.tv:tv-material:1.0.0")
+ docs("androidx.tv:tv-foundation:1.0.0-alpha12")
+ docs("androidx.tv:tv-material:1.1.0-alpha01")
docs("androidx.tvprovider:tvprovider:1.1.0-alpha01")
docs("androidx.vectordrawable:vectordrawable:1.2.0")
docs("androidx.vectordrawable:vectordrawable-animated:1.2.0")
@@ -390,39 +392,39 @@
docs("androidx.versionedparcelable:versionedparcelable:1.2.0")
docs("androidx.viewpager2:viewpager2:1.1.0")
docs("androidx.viewpager:viewpager:1.1.0")
- docs("androidx.wear.compose:compose-foundation:1.5.0-alpha07")
- docs("androidx.wear.compose:compose-material:1.5.0-alpha07")
- docs("androidx.wear.compose:compose-material-core:1.5.0-alpha07")
- docs("androidx.wear.compose:compose-material3:1.0.0-alpha30")
- docs("androidx.wear.compose:compose-navigation:1.5.0-alpha07")
- docs("androidx.wear.compose:compose-ui-tooling:1.5.0-alpha07")
- docs("androidx.wear.protolayout:protolayout:1.3.0-alpha05")
- docs("androidx.wear.protolayout:protolayout-expression:1.3.0-alpha05")
- docs("androidx.wear.protolayout:protolayout-expression-pipeline:1.3.0-alpha05")
- docs("androidx.wear.protolayout:protolayout-material:1.3.0-alpha05")
- docs("androidx.wear.protolayout:protolayout-material-core:1.3.0-alpha05")
- docs("androidx.wear.protolayout:protolayout-material3:1.3.0-alpha05")
- docs("androidx.wear.protolayout:protolayout-renderer:1.3.0-alpha05")
- docs("androidx.wear.protolayout:protolayout-testing:1.3.0-alpha05")
- docs("androidx.wear.tiles:tiles:1.5.0-alpha05")
- docs("androidx.wear.tiles:tiles-material:1.5.0-alpha05")
- docs("androidx.wear.tiles:tiles-renderer:1.5.0-alpha05")
- docs("androidx.wear.tiles:tiles-testing:1.5.0-alpha05")
- docs("androidx.wear.tiles:tiles-tooling:1.5.0-alpha05")
- docs("androidx.wear.tiles:tiles-tooling-preview:1.5.0-alpha05")
- docs("androidx.wear.watchface:watchface:1.3.0-alpha04")
- docs("androidx.wear.watchface:watchface-client:1.3.0-alpha04")
- docs("androidx.wear.watchface:watchface-client-guava:1.3.0-alpha04")
- docs("androidx.wear.watchface:watchface-complications:1.3.0-alpha04")
- docs("androidx.wear.watchface:watchface-complications-data:1.3.0-alpha04")
- docs("androidx.wear.watchface:watchface-complications-data-source:1.3.0-alpha04")
- docs("androidx.wear.watchface:watchface-complications-data-source-ktx:1.3.0-alpha04")
- docs("androidx.wear.watchface:watchface-complications-rendering:1.3.0-alpha04")
- docs("androidx.wear.watchface:watchface-data:1.3.0-alpha04")
- docs("androidx.wear.watchface:watchface-editor:1.3.0-alpha04")
- docs("androidx.wear.watchface:watchface-editor-guava:1.3.0-alpha04")
- docs("androidx.wear.watchface:watchface-guava:1.3.0-alpha04")
- docs("androidx.wear.watchface:watchface-style:1.3.0-alpha04")
+ docs("androidx.wear.compose:compose-foundation:1.5.0-alpha08")
+ docs("androidx.wear.compose:compose-material:1.5.0-alpha08")
+ docs("androidx.wear.compose:compose-material-core:1.5.0-alpha08")
+ docs("androidx.wear.compose:compose-material3:1.0.0-alpha31")
+ docs("androidx.wear.compose:compose-navigation:1.5.0-alpha08")
+ docs("androidx.wear.compose:compose-ui-tooling:1.5.0-alpha08")
+ docs("androidx.wear.protolayout:protolayout:1.3.0-alpha06")
+ docs("androidx.wear.protolayout:protolayout-expression:1.3.0-alpha06")
+ docs("androidx.wear.protolayout:protolayout-expression-pipeline:1.3.0-alpha06")
+ docs("androidx.wear.protolayout:protolayout-material:1.3.0-alpha06")
+ docs("androidx.wear.protolayout:protolayout-material-core:1.3.0-alpha06")
+ docs("androidx.wear.protolayout:protolayout-material3:1.3.0-alpha06")
+ docs("androidx.wear.protolayout:protolayout-renderer:1.3.0-alpha06")
+ docs("androidx.wear.protolayout:protolayout-testing:1.3.0-alpha06")
+ docs("androidx.wear.tiles:tiles:1.5.0-alpha06")
+ docs("androidx.wear.tiles:tiles-material:1.5.0-alpha06")
+ docs("androidx.wear.tiles:tiles-renderer:1.5.0-alpha06")
+ docs("androidx.wear.tiles:tiles-testing:1.5.0-alpha06")
+ docs("androidx.wear.tiles:tiles-tooling:1.5.0-alpha06")
+ docs("androidx.wear.tiles:tiles-tooling-preview:1.5.0-alpha06")
+ docs("androidx.wear.watchface:watchface:1.3.0-alpha05")
+ docs("androidx.wear.watchface:watchface-client:1.3.0-alpha05")
+ docs("androidx.wear.watchface:watchface-client-guava:1.3.0-alpha05")
+ docs("androidx.wear.watchface:watchface-complications:1.3.0-alpha05")
+ docs("androidx.wear.watchface:watchface-complications-data:1.3.0-alpha05")
+ docs("androidx.wear.watchface:watchface-complications-data-source:1.3.0-alpha05")
+ docs("androidx.wear.watchface:watchface-complications-data-source-ktx:1.3.0-alpha05")
+ docs("androidx.wear.watchface:watchface-complications-rendering:1.3.0-alpha05")
+ docs("androidx.wear.watchface:watchface-data:1.3.0-alpha05")
+ docs("androidx.wear.watchface:watchface-editor:1.3.0-alpha05")
+ docs("androidx.wear.watchface:watchface-editor-guava:1.3.0-alpha05")
+ docs("androidx.wear.watchface:watchface-guava:1.3.0-alpha05")
+ docs("androidx.wear.watchface:watchface-style:1.3.0-alpha05")
docs("androidx.wear:wear:1.4.0-alpha01")
stubs(fileTree(dir: "../wear/wear_stubs/", include: ["com.google.android.wearable-stubs.jar"]))
docs("androidx.wear:wear-core:1.0.0-alpha01")
@@ -435,16 +437,16 @@
docs("androidx.wear:wear-remote-interactions:1.1.0")
samples("androidx.wear:wear-remote-interactions-samples:1.1.0-alpha02")
docs("androidx.wear:wear-tooling-preview:1.0.0")
- docs("androidx.webkit:webkit:1.13.0-alpha02")
+ docs("androidx.webkit:webkit:1.13.0-alpha03")
docs("androidx.window.extensions.core:core:1.0.0")
- docs("androidx.window:window:1.4.0-alpha05")
+ docs("androidx.window:window:1.4.0-beta01")
stubs(fileTree(dir: "../window/stubs/", include: ["window-sidecar-release-0.1.0-alpha01.aar"]))
- kmpDocs("androidx.window:window-core:1.4.0-alpha05")
+ kmpDocs("androidx.window:window-core:1.4.0-beta01")
stubs("androidx.window:window-extensions:1.0.0-alpha01")
- docs("androidx.window:window-java:1.4.0-alpha05")
- docs("androidx.window:window-rxjava2:1.4.0-alpha05")
- docs("androidx.window:window-rxjava3:1.4.0-alpha05")
- docs("androidx.window:window-testing:1.4.0-alpha05")
+ docs("androidx.window:window-java:1.4.0-beta01")
+ docs("androidx.window:window-rxjava2:1.4.0-beta01")
+ docs("androidx.window:window-rxjava3:1.4.0-beta01")
+ docs("androidx.window:window-testing:1.4.0-beta01")
docs("androidx.work:work-gcm:2.10.0")
docs("androidx.work:work-multiprocess:2.10.0")
docs("androidx.work:work-runtime:2.10.0")
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 61b9230..5b7b2a45 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -271,7 +271,6 @@
docs(project(":pdf:pdf-viewer-fragment"))
docs(project(":percentlayout:percentlayout"))
kmpDocs(project(":performance:performance-annotation"))
- docs(project(":performance:performance-unsafe"))
docs(project(":preference:preference"))
docs(project(":preference:preference-ktx"))
docs(project(":print:print"))
diff --git a/docs/api_guidelines/dependencies.md b/docs/api_guidelines/dependencies.md
index c6f1576..74de906 100644
--- a/docs/api_guidelines/dependencies.md
+++ b/docs/api_guidelines/dependencies.md
@@ -271,6 +271,13 @@
maintain binary compatibility during the migration. Metalava does not cover all
possible aspects of migration, so some manual work will be required.
+#### Kotlin reflect {#dependencies-kotlin-reflect}
+
+Reflection in libraries is
+[only allowed for backwards compatibility support](/docs/api_guidelines#sdk-reflection)
+and those users should use Java reflection. `kotlin-reflect` is very costly at
+runtime and should never be used.
+
#### Kotlin coroutines {#dependencies-coroutines}
The Kotlin coroutines library adds around 100kB post-shrinking. New libraries
diff --git a/glance/glance-appwidget-preview/build.gradle b/glance/glance-appwidget-preview/build.gradle
index 8619e6273..eaa5182 100644
--- a/glance/glance-appwidget-preview/build.gradle
+++ b/glance/glance-appwidget-preview/build.gradle
@@ -33,7 +33,6 @@
dependencies {
api(libs.kotlinStdlib)
- api(libs.kotlinReflect)
api(libs.kotlinCoroutinesAndroid)
implementation("androidx.core:core:1.1.0")
diff --git a/graphics/OWNERS b/graphics/OWNERS
index c79033e..cfa978c 100644
--- a/graphics/OWNERS
+++ b/graphics/OWNERS
@@ -2,5 +2,5 @@
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
\ No newline at end of file
[email protected]
[email protected]
\ No newline at end of file
diff --git a/graphics/filters/OWNERS b/graphics/filters/OWNERS
index 1bc1576..9ae376b 100644
--- a/graphics/filters/OWNERS
+++ b/graphics/filters/OWNERS
@@ -1,7 +1,5 @@
# Bug component: 1137062
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/platform/aggregate/ResultGroupByPeriodAggregatorTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/platform/aggregate/ResultGroupByPeriodAggregatorTest.kt
index cf59415..e9af1a6 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/platform/aggregate/ResultGroupByPeriodAggregatorTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/platform/aggregate/ResultGroupByPeriodAggregatorTest.kt
@@ -395,7 +395,7 @@
}
@Test
- fun getResult_recordNeededForCalculation_returnsMapWithResult() {
+ fun getResult_recordContributingToAggregation_returnsListWithResult() {
val aggregator =
ResultGroupedByPeriodAggregator(
createLocalTimeRange(
@@ -423,7 +423,34 @@
}
@Test
- fun getResult_recordOutOfBounds_returnsEmptyMap() {
+ fun getResult_recordNotContributingToAggregation_returnsEmptyList() {
+ val aggregator =
+ ResultGroupedByPeriodAggregator(
+ createLocalTimeRange(
+ TimeRangeFilter.after(
+ Instant.ofEpochMilli(100).toLocalTimeWithDefaultZoneFallback(ZoneOffset.UTC)
+ )
+ ),
+ bucketPeriod = Period.ofDays(1)
+ ) {
+ TransFatTotalAggregationProcessor(it)
+ }
+
+ aggregator.filterAndAggregate(
+ NutritionRecord(
+ startTime = Instant.ofEpochMilli(100),
+ endTime = Instant.ofEpochMilli(1000),
+ startZoneOffset = ZoneOffset.UTC,
+ endZoneOffset = ZoneOffset.UTC,
+ metadata = Metadata(dataOrigin = DataOrigin("some.package")),
+ )
+ )
+
+ assertThat(aggregator.getResult()).isEmpty()
+ }
+
+ @Test
+ fun getResult_recordOutOfBounds_returnsEmptyList() {
val aggregator =
ResultGroupedByPeriodAggregator(
LocalTimeRange(
diff --git a/libraryversions.toml b/libraryversions.toml
index 91c7048..0546928 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -4,7 +4,7 @@
ANNOTATION_EXPERIMENTAL = "1.5.0-alpha01"
APPCOMPAT = "1.8.0-alpha01"
APPFUNCTIONS = "1.0.0-alpha01"
-APPSEARCH = "1.1.0-beta01"
+APPSEARCH = "1.1.0-beta02"
ARCH_CORE = "2.3.0-alpha01"
ASYNCLAYOUTINFLATER = "1.1.0-alpha02"
AUTOFILL = "1.3.0-rc01"
@@ -19,7 +19,7 @@
CAMERA_TESTING = "1.0.0-alpha01"
CAMERA_VIEWFINDER = "1.4.0-alpha12"
CARDVIEW = "1.1.0-alpha01"
-CAR_APP = "1.7.0-rc01"
+CAR_APP = "1.8.0-alpha01"
COLLECTION = "1.5.0-beta02"
COMPOSE = "1.8.0-alpha08"
COMPOSE_MATERIAL3 = "1.4.0-alpha06"
@@ -31,8 +31,8 @@
CONSTRAINTLAYOUT_COMPOSE = "1.2.0-alpha01"
CONSTRAINTLAYOUT_CORE = "1.2.0-alpha01"
CONTENTPAGER = "1.1.0-alpha01"
-COORDINATORLAYOUT = "1.3.0-alpha03"
-CORE = "1.16.0-alpha01"
+COORDINATORLAYOUT = "1.3.0-beta01"
+CORE = "1.16.0-alpha02"
CORE_ANIMATION = "1.0.0"
CORE_ANIMATION_TESTING = "1.0.0"
CORE_APPDIGEST = "1.0.0-alpha01"
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/api/current.txt b/lifecycle/lifecycle-viewmodel-savedstate/api/current.txt
index 18274e5..74041ee 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/api/current.txt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/api/current.txt
@@ -1,10 +1,10 @@
// Signature format: 4.0
package androidx.lifecycle {
- public abstract class AbstractSavedStateViewModelFactory implements androidx.lifecycle.ViewModelProvider.Factory {
- ctor public AbstractSavedStateViewModelFactory();
- ctor public AbstractSavedStateViewModelFactory(androidx.savedstate.SavedStateRegistryOwner owner, android.os.Bundle? defaultArgs);
- method protected abstract <T extends androidx.lifecycle.ViewModel> T create(String key, Class<T> modelClass, androidx.lifecycle.SavedStateHandle handle);
+ @Deprecated public abstract class AbstractSavedStateViewModelFactory implements androidx.lifecycle.ViewModelProvider.Factory {
+ ctor @Deprecated public AbstractSavedStateViewModelFactory();
+ ctor @Deprecated public AbstractSavedStateViewModelFactory(androidx.savedstate.SavedStateRegistryOwner owner, android.os.Bundle? defaultArgs);
+ method @Deprecated protected abstract <T extends androidx.lifecycle.ViewModel> T create(String key, Class<T> modelClass, androidx.lifecycle.SavedStateHandle handle);
}
public final class SavedStateHandle {
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.txt b/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.txt
index 18274e5..74041ee 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.txt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.txt
@@ -1,10 +1,10 @@
// Signature format: 4.0
package androidx.lifecycle {
- public abstract class AbstractSavedStateViewModelFactory implements androidx.lifecycle.ViewModelProvider.Factory {
- ctor public AbstractSavedStateViewModelFactory();
- ctor public AbstractSavedStateViewModelFactory(androidx.savedstate.SavedStateRegistryOwner owner, android.os.Bundle? defaultArgs);
- method protected abstract <T extends androidx.lifecycle.ViewModel> T create(String key, Class<T> modelClass, androidx.lifecycle.SavedStateHandle handle);
+ @Deprecated public abstract class AbstractSavedStateViewModelFactory implements androidx.lifecycle.ViewModelProvider.Factory {
+ ctor @Deprecated public AbstractSavedStateViewModelFactory();
+ ctor @Deprecated public AbstractSavedStateViewModelFactory(androidx.savedstate.SavedStateRegistryOwner owner, android.os.Bundle? defaultArgs);
+ method @Deprecated protected abstract <T extends androidx.lifecycle.ViewModel> T create(String key, Class<T> modelClass, androidx.lifecycle.SavedStateHandle handle);
}
public final class SavedStateHandle {
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/savedstate/SavedStateFactoryTest.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/savedstate/SavedStateFactoryTest.kt
index 3e4dc3f..a870ad2 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/savedstate/SavedStateFactoryTest.kt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/savedstate/SavedStateFactoryTest.kt
@@ -18,7 +18,6 @@
import android.app.Application
import androidx.fragment.app.FragmentActivity
-import androidx.lifecycle.AbstractSavedStateViewModelFactory
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.SAVED_STATE_REGISTRY_OWNER_KEY
import androidx.lifecycle.SavedStateHandle
@@ -138,8 +137,9 @@
fun testLegacyCreateAndroidAbstractVM() {
val activity = activityRule.activity
val app = activity.application
+ @Suppress("DEPRECATION")
val savedStateVMFactory =
- object : AbstractSavedStateViewModelFactory(activity, null) {
+ object : androidx.lifecycle.AbstractSavedStateViewModelFactory(activity, null) {
override fun <T : ViewModel> create(
key: String,
modelClass: Class<T>,
@@ -166,8 +166,9 @@
fail()
} catch (e: UnsupportedOperationException) {}
+ @Suppress("DEPRECATION")
val absFactory =
- object : AbstractSavedStateViewModelFactory() {
+ object : androidx.lifecycle.AbstractSavedStateViewModelFactory() {
override fun <T : ViewModel> create(
key: String,
modelClass: Class<T>,
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/savedstate/ViewModelsWithStateTest.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/savedstate/ViewModelsWithStateTest.kt
index 4c6b51f..9cb150e 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/savedstate/ViewModelsWithStateTest.kt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/savedstate/ViewModelsWithStateTest.kt
@@ -20,7 +20,6 @@
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
-import androidx.lifecycle.AbstractSavedStateViewModelFactory
import androidx.lifecycle.HasDefaultViewModelProviderFactory
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.SAVED_STATE_REGISTRY_OWNER_KEY
@@ -165,19 +164,20 @@
activity
}
- val savedStateOwner = owner as SavedStateRegistryOwner
+ require(owner is SavedStateRegistryOwner)
+ @Suppress("DEPRECATION")
val factory: Factory =
when (mode.factoryMode) {
LEGACY_SAVEDSTATE_FACTORY_MODE -> {
// otherwise common type of factory is package private KeyedFactory
- SavedStateViewModelFactory(activity.application, savedStateOwner)
+ SavedStateViewModelFactory(activity.application, owner)
}
SAVEDSTATE_FACTORY_MODE -> {
SavedStateViewModelFactory()
}
LEGACY_ABSTRACT_FACTORY_MODE -> {
- object : AbstractSavedStateViewModelFactory(savedStateOwner, null) {
+ object : androidx.lifecycle.AbstractSavedStateViewModelFactory(owner, null) {
override fun <T : ViewModel> create(
key: String,
modelClass: Class<T>,
@@ -188,7 +188,7 @@
}
}
else -> {
- object : AbstractSavedStateViewModelFactory() {
+ object : androidx.lifecycle.AbstractSavedStateViewModelFactory() {
override fun <T : ViewModel> create(
key: String,
modelClass: Class<T>,
@@ -200,7 +200,7 @@
}
}
return if (mode.factoryMode in setOf(ABSTRACT_FACTORY_MODE, SAVEDSTATE_FACTORY_MODE))
- ViewModelProvider(DecorateWithCreationExtras(savedStateOwner, owner), factory)
+ ViewModelProvider(DecorateWithCreationExtras(owner, owner), factory)
else ViewModelProvider(owner, factory)
}
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/AbstractSavedStateViewModelFactory.android.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/AbstractSavedStateViewModelFactory.android.kt
index 8a228eb..6a3f96b 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/AbstractSavedStateViewModelFactory.android.kt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/AbstractSavedStateViewModelFactory.android.kt
@@ -20,6 +20,7 @@
import androidx.lifecycle.LegacySavedStateHandleController.TAG_SAVED_STATE_HANDLE_CONTROLLER
import androidx.lifecycle.LegacySavedStateHandleController.attachHandleIfNeeded
import androidx.lifecycle.viewmodel.CreationExtras
+import androidx.lifecycle.viewmodel.viewModelFactory
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryOwner
@@ -27,7 +28,19 @@
* Skeleton of androidx.lifecycle.ViewModelProvider.KeyedFactory that creates [SavedStateHandle] for
* every requested [ViewModel]. The subclasses implement [create] to actually instantiate
* `androidx.lifecycle.ViewModel`s.
+ *
+ * **Deprecated:** Use [viewModelFactory] or implement [ViewModelProvider.Factory] directly,
+ * combined with [CreationExtras.createSavedStateHandle]. This base class creates a
+ * [SavedStateHandle] for every [ViewModel], even when unnecessary, causing overhead.
+ *
+ * For example:
+ * ```
+ * viewModelFactory { initializer { MyViewModel(createSavedStateHandle()) } }
+ * ```
*/
+@Deprecated(
+ "Use `viewModelFactory` or implement `ViewModelProvider.Factory`, combined with `CreationExtras.createSavedStateHandle()`."
+)
public abstract class AbstractSavedStateViewModelFactory :
ViewModelProvider.OnRequeryFactory, ViewModelProvider.Factory {
diff --git a/metrics/OWNERS b/metrics/OWNERS
index e5530811..b9b2845 100644
--- a/metrics/OWNERS
+++ b/metrics/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 1109742
[email protected]
[email protected]
\ No newline at end of file
[email protected]
diff --git a/navigation3/navigation3/api/current.txt b/navigation3/navigation3/api/current.txt
index 0b40810..ec7e433 100644
--- a/navigation3/navigation3/api/current.txt
+++ b/navigation3/navigation3/api/current.txt
@@ -1,16 +1,6 @@
// Signature format: 4.0
package androidx.navigation3 {
- public final class AnimatedNavDisplay {
- method public java.util.Map<java.lang.String,java.lang.Object> isDialog(boolean boolean);
- method public java.util.Map<java.lang.String,java.lang.Object> transition(androidx.compose.animation.EnterTransition? enter, androidx.compose.animation.ExitTransition? exit);
- field public static final androidx.navigation3.AnimatedNavDisplay INSTANCE;
- }
-
- public final class AnimatedNavDisplay_androidKt {
- method @androidx.compose.runtime.Composable public static <T> void AnimatedNavDisplay(java.util.List<? extends T> backstack, androidx.navigation3.NavWrapperManager wrapperManager, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional androidx.compose.animation.SizeTransform? sizeTransform, optional kotlin.jvm.functions.Function0<kotlin.Unit> onBack, kotlin.jvm.functions.Function1<? super T,? extends androidx.navigation3.NavRecord<? extends T>> recordProvider);
- }
-
public interface NavContentWrapper {
method @androidx.compose.runtime.Composable public default void WrapBackStack(java.util.List<?> backStack);
method @androidx.compose.runtime.Composable public <T> void WrapContent(androidx.navigation3.NavRecord<T> record);
@@ -18,11 +8,12 @@
public final class NavDisplay {
method public java.util.Map<java.lang.String,java.lang.Object> isDialog(boolean boolean);
+ method public java.util.Map<java.lang.String,java.lang.Object> transition(androidx.compose.animation.EnterTransition? enter, androidx.compose.animation.ExitTransition? exit);
field public static final androidx.navigation3.NavDisplay INSTANCE;
}
public final class NavDisplay_androidKt {
- method @androidx.compose.runtime.Composable public static <T> void NavDisplay(java.util.List<? extends T> backstack, androidx.navigation3.NavWrapperManager wrapperManager, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> onBack, kotlin.jvm.functions.Function1<? super T,? extends androidx.navigation3.NavRecord<? extends T>> recordProvider);
+ method @androidx.compose.runtime.Composable public static <T> void NavDisplay(java.util.List<? extends T> backstack, optional androidx.compose.ui.Modifier modifier, optional androidx.navigation3.NavWrapperManager wrapperManager, optional androidx.compose.ui.Alignment contentAlignment, optional androidx.compose.animation.SizeTransform? sizeTransform, optional androidx.compose.animation.EnterTransition enterTransition, optional androidx.compose.animation.ExitTransition exitTransition, optional kotlin.jvm.functions.Function0<kotlin.Unit> onBack, kotlin.jvm.functions.Function1<? super T,? extends androidx.navigation3.NavRecord<? extends T>> recordProvider);
}
public final class NavRecord<T> {
@@ -36,7 +27,8 @@
}
public final class NavWrapperManager {
- ctor public NavWrapperManager(java.util.List<? extends androidx.navigation3.NavContentWrapper> navContentWrappers);
+ ctor public NavWrapperManager();
+ ctor public NavWrapperManager(optional java.util.List<? extends androidx.navigation3.NavContentWrapper> navContentWrappers);
method @androidx.compose.runtime.Composable public <T> void ContentForRecord(androidx.navigation3.NavRecord<T> record);
method @androidx.compose.runtime.Composable public void PrepareBackStack(java.util.List<?> backStack);
}
diff --git a/navigation3/navigation3/api/restricted_current.txt b/navigation3/navigation3/api/restricted_current.txt
index 0b40810..ec7e433 100644
--- a/navigation3/navigation3/api/restricted_current.txt
+++ b/navigation3/navigation3/api/restricted_current.txt
@@ -1,16 +1,6 @@
// Signature format: 4.0
package androidx.navigation3 {
- public final class AnimatedNavDisplay {
- method public java.util.Map<java.lang.String,java.lang.Object> isDialog(boolean boolean);
- method public java.util.Map<java.lang.String,java.lang.Object> transition(androidx.compose.animation.EnterTransition? enter, androidx.compose.animation.ExitTransition? exit);
- field public static final androidx.navigation3.AnimatedNavDisplay INSTANCE;
- }
-
- public final class AnimatedNavDisplay_androidKt {
- method @androidx.compose.runtime.Composable public static <T> void AnimatedNavDisplay(java.util.List<? extends T> backstack, androidx.navigation3.NavWrapperManager wrapperManager, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional androidx.compose.animation.SizeTransform? sizeTransform, optional kotlin.jvm.functions.Function0<kotlin.Unit> onBack, kotlin.jvm.functions.Function1<? super T,? extends androidx.navigation3.NavRecord<? extends T>> recordProvider);
- }
-
public interface NavContentWrapper {
method @androidx.compose.runtime.Composable public default void WrapBackStack(java.util.List<?> backStack);
method @androidx.compose.runtime.Composable public <T> void WrapContent(androidx.navigation3.NavRecord<T> record);
@@ -18,11 +8,12 @@
public final class NavDisplay {
method public java.util.Map<java.lang.String,java.lang.Object> isDialog(boolean boolean);
+ method public java.util.Map<java.lang.String,java.lang.Object> transition(androidx.compose.animation.EnterTransition? enter, androidx.compose.animation.ExitTransition? exit);
field public static final androidx.navigation3.NavDisplay INSTANCE;
}
public final class NavDisplay_androidKt {
- method @androidx.compose.runtime.Composable public static <T> void NavDisplay(java.util.List<? extends T> backstack, androidx.navigation3.NavWrapperManager wrapperManager, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> onBack, kotlin.jvm.functions.Function1<? super T,? extends androidx.navigation3.NavRecord<? extends T>> recordProvider);
+ method @androidx.compose.runtime.Composable public static <T> void NavDisplay(java.util.List<? extends T> backstack, optional androidx.compose.ui.Modifier modifier, optional androidx.navigation3.NavWrapperManager wrapperManager, optional androidx.compose.ui.Alignment contentAlignment, optional androidx.compose.animation.SizeTransform? sizeTransform, optional androidx.compose.animation.EnterTransition enterTransition, optional androidx.compose.animation.ExitTransition exitTransition, optional kotlin.jvm.functions.Function0<kotlin.Unit> onBack, kotlin.jvm.functions.Function1<? super T,? extends androidx.navigation3.NavRecord<? extends T>> recordProvider);
}
public final class NavRecord<T> {
@@ -36,7 +27,8 @@
}
public final class NavWrapperManager {
- ctor public NavWrapperManager(java.util.List<? extends androidx.navigation3.NavContentWrapper> navContentWrappers);
+ ctor public NavWrapperManager();
+ ctor public NavWrapperManager(optional java.util.List<? extends androidx.navigation3.NavContentWrapper> navContentWrappers);
method @androidx.compose.runtime.Composable public <T> void ContentForRecord(androidx.navigation3.NavRecord<T> record);
method @androidx.compose.runtime.Composable public void PrepareBackStack(java.util.List<?> backStack);
}
diff --git a/navigation3/navigation3/build.gradle b/navigation3/navigation3/build.gradle
index 796ed34..74aa38e 100644
--- a/navigation3/navigation3/build.gradle
+++ b/navigation3/navigation3/build.gradle
@@ -67,11 +67,10 @@
androidMain {
dependsOn(jvmMain)
dependencies {
+ api("androidx.compose.animation:animation:1.7.5")
+ api("androidx.compose.ui:ui:1.7.5")
implementation("androidx.activity:activity-compose:1.9.3")
implementation("androidx.annotation:annotation:1.8.0")
- implementation("androidx.compose.animation:animation:1.7.5")
- implementation("androidx.compose.foundation:foundation:1.7.5")
- implementation("androidx.compose.ui:ui:1.7.5")
}
}
diff --git a/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/NavDisplaySamples.kt b/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/NavDisplaySamples.kt
index bc706ddb..fa6ff47 100644
--- a/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/NavDisplaySamples.kt
+++ b/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/NavDisplaySamples.kt
@@ -24,7 +24,6 @@
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.viewmodel.navigation3.ViewModelStoreNavContentWrapper
-import androidx.navigation3.AnimatedNavDisplay
import androidx.navigation3.NavDisplay
import androidx.navigation3.NavRecord
import androidx.navigation3.SavedStateNavContentWrapper
@@ -32,9 +31,13 @@
import androidx.navigation3.recordProvider
import androidx.navigation3.rememberNavWrapperManager
+class ProfileViewModel : ViewModel() {
+ val name = "no user"
+}
+
@Sampled
@Composable
-fun BasicNav() {
+fun BaseNav() {
val backStack = rememberMutableStateListOf(Profile)
val manager =
rememberNavWrapperManager(
@@ -46,54 +49,14 @@
onBack = { backStack.removeLast() },
recordProvider =
recordProvider({ NavRecord(Unit) { Text(text = "Invalid Key") } }) {
- record<Profile> {
- val viewModel = viewModel<ProfileViewModel>()
- Profile(viewModel, { backStack.add(it) }) { backStack.removeLast() }
- }
- record<Scrollable> { Scrollable({ backStack.add(it) }) { backStack.removeLast() } }
- record<Dialog>(featureMap = NavDisplay.isDialog(true)) {
- DialogContent { backStack.removeLast() }
- }
- record<Dashboard> { dashboardArgs ->
- val userId = dashboardArgs.userId
- Dashboard(userId, onBack = { backStack.removeLast() })
- }
- }
- )
-}
-
-class ProfileViewModel : ViewModel() {
- val name = "no user"
-}
-
-@Sampled
-@Composable
-fun AnimatedNav() {
- val backStack = rememberMutableStateListOf(Profile)
- val manager =
- rememberNavWrapperManager(
- listOf(SavedStateNavContentWrapper, ViewModelStoreNavContentWrapper)
- )
- AnimatedNavDisplay(
- backstack = backStack,
- wrapperManager = manager,
- onBack = { backStack.removeLast() },
- recordProvider =
- recordProvider({ NavRecord(Unit) { Text(text = "Invalid Key") } }) {
record<Profile>(
- AnimatedNavDisplay.transition(
- slideInHorizontally { it },
- slideOutHorizontally { it }
- )
+ NavDisplay.transition(slideInHorizontally { it }, slideOutHorizontally { it })
) {
val viewModel = viewModel<ProfileViewModel>()
Profile(viewModel, { backStack.add(it) }) { backStack.removeLast() }
}
record<Scrollable>(
- AnimatedNavDisplay.transition(
- slideInHorizontally { it },
- slideOutHorizontally { it }
- )
+ NavDisplay.transition(slideInHorizontally { it }, slideOutHorizontally { it })
) {
Scrollable({ backStack.add(it) }) { backStack.removeLast() }
}
@@ -101,10 +64,7 @@
DialogContent { backStack.removeLast() }
}
record<Dashboard>(
- AnimatedNavDisplay.transition(
- slideInHorizontally { it },
- slideOutHorizontally { it }
- )
+ NavDisplay.transition(slideInHorizontally { it }, slideOutHorizontally { it })
) { dashboardArgs ->
val userId = dashboardArgs.userId
Dashboard(userId, onBack = { backStack.removeLast() })
diff --git a/navigation3/navigation3/src/androidInstrumentedTest/kotlin/androidx/navigation3/AnimatedNavDisplayTest.kt b/navigation3/navigation3/src/androidInstrumentedTest/kotlin/androidx/navigation3/AnimatedNavDisplayTest.kt
deleted file mode 100644
index bea6757..0000000
--- a/navigation3/navigation3/src/androidInstrumentedTest/kotlin/androidx/navigation3/AnimatedNavDisplayTest.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2024 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.navigation3
-
-import androidx.compose.animation.core.AnimationConstants.DefaultDurationMillis
-import androidx.compose.material3.Text
-import androidx.compose.runtime.mutableStateListOf
-import androidx.compose.runtime.remember
-import androidx.compose.ui.test.isDisplayed
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithText
-import androidx.kruth.assertThat
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.LargeTest
-import kotlin.test.Test
-import org.junit.Rule
-import org.junit.runner.RunWith
-
-@LargeTest
-@RunWith(AndroidJUnit4::class)
-class AnimatedNavDisplayTest {
- @get:Rule val composeTestRule = createComposeRule()
-
- @Test
- fun testNavHostAnimations() {
- lateinit var backstack: MutableList<Any>
-
- composeTestRule.mainClock.autoAdvance = false
-
- composeTestRule.setContent {
- backstack = remember { mutableStateListOf(first) }
- val manager = rememberNavWrapperManager(emptyList())
- AnimatedNavDisplay(backstack, wrapperManager = manager) {
- when (it) {
- first -> NavRecord(first) { Text(first) }
- second -> NavRecord(second) { Text(second) }
- else -> error("Invalid key passed")
- }
- }
- }
-
- composeTestRule.mainClock.autoAdvance = true
-
- composeTestRule.waitForIdle()
- assertThat(composeTestRule.onNodeWithText(first).isDisplayed()).isTrue()
-
- composeTestRule.mainClock.autoAdvance = false
-
- composeTestRule.runOnIdle { backstack.add(second) }
-
- // advance half way between animations
- composeTestRule.mainClock.advanceTimeBy(DefaultDurationMillis.toLong() / 2)
-
- composeTestRule.waitForIdle()
- composeTestRule.onNodeWithText(first).assertExists()
- composeTestRule.onNodeWithText(second).assertExists()
-
- composeTestRule.mainClock.autoAdvance = true
-
- composeTestRule.waitForIdle()
- composeTestRule.onNodeWithText(first).assertDoesNotExist()
- composeTestRule.onNodeWithText(second).assertExists()
- }
-}
-
-private const val first = "first"
-private const val second = "second"
diff --git a/navigation3/navigation3/src/androidInstrumentedTest/kotlin/androidx/navigation3/AnimatedTest.kt b/navigation3/navigation3/src/androidInstrumentedTest/kotlin/androidx/navigation3/AnimatedTest.kt
new file mode 100644
index 0000000..d7da926
--- /dev/null
+++ b/navigation3/navigation3/src/androidInstrumentedTest/kotlin/androidx/navigation3/AnimatedTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2024 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.navigation3
+
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.material3.Text
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.isDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.kruth.assertThat
+import androidx.navigation3.NavDisplay.DEFAULT_TRANSITION_DURATION_MILLISECOND
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import kotlin.test.Test
+import org.junit.Rule
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class AnimatedTest {
+ @get:Rule val composeTestRule = createComposeRule()
+
+ @Test
+ fun testNavHostAnimations() {
+ lateinit var backstack: MutableList<Any>
+
+ composeTestRule.mainClock.autoAdvance = false
+
+ composeTestRule.setContent {
+ backstack = remember { mutableStateListOf(first) }
+ NavDisplay(backstack) {
+ when (it) {
+ first -> NavRecord(first) { Text(first) }
+ second -> NavRecord(second) { Text(second) }
+ else -> error("Invalid key passed")
+ }
+ }
+ }
+
+ composeTestRule.mainClock.autoAdvance = true
+
+ composeTestRule.waitForIdle()
+ assertThat(composeTestRule.onNodeWithText(first).isDisplayed()).isTrue()
+
+ composeTestRule.mainClock.autoAdvance = false
+
+ composeTestRule.runOnIdle { backstack.add(second) }
+
+ // advance half way between animations
+ composeTestRule.mainClock.advanceTimeBy(
+ DEFAULT_TRANSITION_DURATION_MILLISECOND.toLong() / 2
+ )
+
+ composeTestRule.waitForIdle()
+ composeTestRule.onNodeWithText(first).assertExists()
+ composeTestRule.onNodeWithText(second).assertExists()
+
+ composeTestRule.mainClock.autoAdvance = true
+
+ composeTestRule.waitForIdle()
+ composeTestRule.onNodeWithText(first).assertDoesNotExist()
+ composeTestRule.onNodeWithText(second).assertExists()
+ }
+
+ @Test
+ fun testNavHostAnimationsCustom() {
+ lateinit var backstack: MutableList<Any>
+
+ composeTestRule.mainClock.autoAdvance = false
+ val customDuration = DEFAULT_TRANSITION_DURATION_MILLISECOND * 2
+
+ composeTestRule.setContent {
+ backstack = remember { mutableStateListOf(first) }
+ NavDisplay(backstack) {
+ when (it) {
+ first ->
+ NavRecord(
+ first,
+ ) {
+ Text(first)
+ }
+ second ->
+ NavRecord(
+ second,
+ featureMap =
+ NavDisplay.transition(
+ enter = fadeIn(tween(customDuration)),
+ exit = fadeOut(tween(customDuration))
+ )
+ ) {
+ Text(second)
+ }
+ else -> error("Invalid key passed")
+ }
+ }
+ }
+
+ composeTestRule.mainClock.autoAdvance = true
+
+ composeTestRule.waitForIdle()
+ composeTestRule.onNodeWithText(first).assertIsDisplayed()
+
+ composeTestRule.mainClock.autoAdvance = false
+
+ composeTestRule.runOnIdle { backstack.add(second) }
+
+ // advance past the default duration but not the custom duration
+ composeTestRule.mainClock.advanceTimeBy(
+ ((DEFAULT_TRANSITION_DURATION_MILLISECOND * 3 / 2)).toLong()
+ )
+
+ composeTestRule.waitForIdle()
+ composeTestRule.onNodeWithText(first).assertExists()
+ composeTestRule.onNodeWithText(second).assertExists()
+
+ composeTestRule.mainClock.autoAdvance = true
+
+ composeTestRule.waitForIdle()
+ composeTestRule.onNodeWithText(first).assertDoesNotExist()
+ composeTestRule.onNodeWithText(second).assertExists()
+ }
+}
+
+private const val first = "first"
+private const val second = "second"
diff --git a/navigation3/navigation3/src/androidInstrumentedTest/kotlin/androidx/navigation3/NavDisplayTest.kt b/navigation3/navigation3/src/androidInstrumentedTest/kotlin/androidx/navigation3/NavDisplayTest.kt
index a36372a8..fd5749a 100644
--- a/navigation3/navigation3/src/androidInstrumentedTest/kotlin/androidx/navigation3/NavDisplayTest.kt
+++ b/navigation3/navigation3/src/androidInstrumentedTest/kotlin/androidx/navigation3/NavDisplayTest.kt
@@ -45,10 +45,7 @@
@Test
fun testContentShown() {
composeTestRule.setContent {
- val manager = rememberNavWrapperManager(emptyList())
- NavDisplay(backstack = mutableStateListOf(first), wrapperManager = manager) {
- NavRecord(first) { Text(first) }
- }
+ NavDisplay(backstack = mutableStateListOf(first)) { NavRecord(first) { Text(first) } }
}
assertThat(composeTestRule.onNodeWithText(first).isDisplayed()).isTrue()
@@ -59,8 +56,7 @@
lateinit var backstack: MutableList<Any>
composeTestRule.setContent {
backstack = remember { mutableStateListOf(first) }
- val manager = rememberNavWrapperManager(emptyList())
- NavDisplay(backstack = backstack, wrapperManager = manager) {
+ NavDisplay(backstack = backstack) {
when (it) {
first -> NavRecord(first) { Text(first) }
second -> NavRecord(second) { Text(second) }
@@ -82,15 +78,7 @@
lateinit var backstack: MutableList<Any>
composeTestRule.setContent {
backstack = remember { mutableStateListOf(first) }
- val manager = rememberNavWrapperManager(emptyList())
- NavDisplay(
- backstack = backstack,
- wrapperManager = manager,
- onBack = {
- // removeLast requires API 35
- backstack.removeAt(backstack.size - 1)
- }
- ) {
+ NavDisplay(backstack = backstack) {
when (it) {
first -> NavRecord(first) { Text(first) }
second -> NavRecord(second, NavDisplay.isDialog(true)) { Text(second) }
@@ -115,15 +103,7 @@
composeTestRule.setContent {
onBackDispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher
backstack = remember { mutableStateListOf(first) }
- val manager = rememberNavWrapperManager(emptyList())
- NavDisplay(
- backstack = backstack,
- wrapperManager = manager,
- onBack = {
- // removeLast requires API 35
- backstack.removeAt(backstack.size - 1)
- }
- ) {
+ NavDisplay(backstack = backstack) {
when (it) {
first -> NavRecord(first) { Text(first) }
second -> NavRecord(second) { Text(second) }
@@ -150,8 +130,7 @@
lateinit var backstack: MutableList<Any>
composeTestRule.setContent {
backstack = remember { mutableStateListOf(first) }
- val manager = rememberNavWrapperManager(emptyList())
- NavDisplay(backstack = backstack, wrapperManager = manager) {
+ NavDisplay(backstack = backstack) {
when (it) {
first -> NavRecord(first) { numberOnScreen1 = rememberSaveable { increment++ } }
second -> NavRecord(second) {}
@@ -226,7 +205,6 @@
backStack2 = remember { mutableStateListOf(second) }
backStack3 = remember { mutableStateListOf(third) }
state = remember { mutableStateOf(1) }
- val manager = rememberNavWrapperManager(emptyList())
NavDisplay(
backstack =
when (state.value) {
@@ -234,7 +212,6 @@
2 -> backStack2
else -> backStack3
},
- wrapperManager = manager,
recordProvider =
recordProvider {
record(first) { Text(first) }
diff --git a/navigation3/navigation3/src/androidMain/kotlin/androidx/navigation3/AnimatedNavDisplay.android.kt b/navigation3/navigation3/src/androidMain/kotlin/androidx/navigation3/AnimatedNavDisplay.android.kt
deleted file mode 100644
index 3fdac92..0000000
--- a/navigation3/navigation3/src/androidMain/kotlin/androidx/navigation3/AnimatedNavDisplay.android.kt
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright 2024 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.navigation3
-
-import androidx.activity.compose.BackHandler
-import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.ContentTransform
-import androidx.compose.animation.EnterTransition
-import androidx.compose.animation.ExitTransition
-import androidx.compose.animation.SizeTransform
-import androidx.compose.animation.core.tween
-import androidx.compose.animation.core.updateTransition
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.window.Dialog
-
-/** Object that indicates the features that can be handled by the [AnimatedNavDisplay] */
-public object AnimatedNavDisplay {
- /**
- * Function to be called on the [NavRecord.featureMap] to notify the [AnimatedNavDisplay] that
- * the content should be animated using the provided transitions.
- */
- public fun transition(enter: EnterTransition?, exit: ExitTransition?): Map<String, Any> =
- if (enter == null || exit == null) emptyMap()
- else mapOf(ENTER_TRANSITION_KEY to enter, EXIT_TRANSITION_KEY to exit)
-
- /**
- * Function to be called on the [NavRecord.featureMap] to notify the [NavDisplay] that the
- * content should be displayed inside of a [Dialog]
- */
- public fun isDialog(boolean: Boolean): Map<String, Any> =
- if (!boolean) emptyMap() else mapOf(DIALOG_KEY to true)
-
- internal const val ENTER_TRANSITION_KEY = "enterTransition"
- internal const val EXIT_TRANSITION_KEY = "exitTransition"
- internal const val DIALOG_KEY = "dialog"
-}
-
-/**
- * Display for Composable content that displays a single pane of content at a time, but can move
- * that content in and out with customized transitions.
- *
- * The AnimatedNavDisplay displays the content associated with the last key on the back stack in
- * most circumstances. If that content wants to be displayed as a dialog, as communicated by adding
- * [NavDisplay.isDialog] to a [NavRecord.featureMap], then the last key's content is a dialog and
- * the second to last key is a displayed in the background.
- *
- * @param backstack the collection of keys that represents the state that needs to be handled
- * @param wrapperManager the manager that combines all of the [NavContentWrapper]s
- * @param modifier the modifier to be applied to the layout.
- * @param contentAlignment The [Alignment] of the [AnimatedContent]
- * @param onBack a callback for handling system back presses
- * @param recordProvider lambda used to construct each possible [NavRecord]
- * @sample androidx.navigation3.samples.AnimatedNav
- */
-@Composable
-public fun <T : Any> AnimatedNavDisplay(
- backstack: List<T>,
- wrapperManager: NavWrapperManager,
- modifier: Modifier = Modifier,
- contentAlignment: Alignment = Alignment.TopStart,
- sizeTransform: SizeTransform? = null,
- onBack: () -> Unit = {},
- recordProvider: (key: T) -> NavRecord<out T>
-) {
- BackHandler(backstack.size > 1, onBack)
- wrapperManager.PrepareBackStack(backStack = backstack)
- val key = backstack.last()
- val record = recordProvider.invoke(key)
-
- // Incoming record defines transitions, otherwise it defaults to a fade
- val enterTransition =
- record.featureMap[AnimatedNavDisplay.ENTER_TRANSITION_KEY] as? EnterTransition
- ?: fadeIn(animationSpec = tween(700))
- val exitTransition =
- record.featureMap[AnimatedNavDisplay.EXIT_TRANSITION_KEY] as? ExitTransition
- ?: fadeOut(animationSpec = tween(700))
-
- // if there is a dialog, we should create a transition with the next to last entry instead.
- val transition =
- if (record.featureMap[AnimatedNavDisplay.DIALOG_KEY] == true) {
- if (backstack.size > 1) {
- val previousKey = backstack[backstack.size - 2]
- updateTransition(targetState = previousKey, label = previousKey.toString())
- } else {
- null
- }
- } else {
- updateTransition(targetState = key, label = key.toString())
- }
-
- transition?.AnimatedContent(
- modifier = modifier,
- transitionSpec = {
- ContentTransform(
- targetContentEnter = enterTransition,
- initialContentExit = exitTransition,
- sizeTransform = sizeTransform
- )
- },
- contentAlignment = contentAlignment
- ) { innerKey ->
- wrapperManager.ContentForRecord(recordProvider.invoke(innerKey))
- }
-
- if (record.featureMap[AnimatedNavDisplay.DIALOG_KEY] == true) {
- Dialog(onBack) { wrapperManager.ContentForRecord(record) }
- }
-}
diff --git a/navigation3/navigation3/src/androidMain/kotlin/androidx/navigation3/NavDisplay.android.kt b/navigation3/navigation3/src/androidMain/kotlin/androidx/navigation3/NavDisplay.android.kt
index ae0ce29..cdcff77 100644
--- a/navigation3/navigation3/src/androidMain/kotlin/androidx/navigation3/NavDisplay.android.kt
+++ b/navigation3/navigation3/src/androidMain/kotlin/androidx/navigation3/NavDisplay.android.kt
@@ -13,62 +13,138 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package androidx.navigation3
import androidx.activity.compose.BackHandler
-import androidx.compose.foundation.layout.Box
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.ContentTransform
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.SizeTransform
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.core.updateTransition
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.window.Dialog
+import androidx.navigation3.NavDisplay.DEFAULT_TRANSITION_DURATION_MILLISECOND
/** Object that indicates the features that can be handled by the [NavDisplay] */
public object NavDisplay {
/**
* Function to be called on the [NavRecord.featureMap] to notify the [NavDisplay] that the
+ * content should be animated using the provided transitions.
+ */
+ public fun transition(enter: EnterTransition?, exit: ExitTransition?): Map<String, Any> =
+ if (enter == null || exit == null) emptyMap()
+ else mapOf(ENTER_TRANSITION_KEY to enter, EXIT_TRANSITION_KEY to exit)
+
+ /**
+ * Function to be called on the [NavRecord.featureMap] to notify the [NavDisplay] that the
* content should be displayed inside of a [Dialog]
*/
public fun isDialog(boolean: Boolean): Map<String, Any> =
if (!boolean) emptyMap() else mapOf(DIALOG_KEY to true)
+ internal const val ENTER_TRANSITION_KEY = "enterTransition"
+ internal const val EXIT_TRANSITION_KEY = "exitTransition"
internal const val DIALOG_KEY = "dialog"
+ internal const val DEFAULT_TRANSITION_DURATION_MILLISECOND = 700
}
/**
- * Simple display for Composable content that displays a single pane of content at a time.
+ * Display for Composable content that displays a single pane of content at a time, but can move
+ * that content in and out with customized transitions.
*
* The NavDisplay displays the content associated with the last key on the back stack in most
* circumstances. If that content wants to be displayed as a dialog, as communicated by adding
* [NavDisplay.isDialog] to a [NavRecord.featureMap], then the last key's content is a dialog and
* the second to last key is a displayed in the background.
*
- * @param modifier the modifier to be applied to the layout.
* @param backstack the collection of keys that represents the state that needs to be handled
* @param wrapperManager the manager that combines all of the [NavContentWrapper]s
+ * @param modifier the modifier to be applied to the layout.
+ * @param contentAlignment The [Alignment] of the [AnimatedContent]
+ * * @param enterTransition Default [EnterTransition] for all [NavRecord]s. Can be overridden
+ * * individually for each [NavRecord] by passing in the record's transitions through
+ * * [NavRecord.featureMap].
+ * * @param exitTransition Default [ExitTransition] for all [NavRecord]s. Can be overridden
+ * * individually for each [NavRecord] by passing in the record's transitions through
+ * * [NavRecord.featureMap].
+ *
* @param onBack a callback for handling system back presses
* @param recordProvider lambda used to construct each possible [NavRecord]
- * @sample androidx.navigation3.samples.BasicNav
+ * @sample androidx.navigation3.samples.BaseNav
*/
@Composable
public fun <T : Any> NavDisplay(
backstack: List<T>,
- wrapperManager: NavWrapperManager,
modifier: Modifier = Modifier,
- onBack: () -> Unit = {},
+ wrapperManager: NavWrapperManager = rememberNavWrapperManager(emptyList()),
+ contentAlignment: Alignment = Alignment.TopStart,
+ sizeTransform: SizeTransform? = null,
+ enterTransition: EnterTransition =
+ fadeIn(
+ animationSpec =
+ tween(
+ DEFAULT_TRANSITION_DURATION_MILLISECOND,
+ )
+ ),
+ exitTransition: ExitTransition =
+ fadeOut(
+ animationSpec =
+ tween(
+ DEFAULT_TRANSITION_DURATION_MILLISECOND,
+ )
+ ),
+ onBack: () -> Unit = { if (backstack is MutableList) backstack.removeAt(backstack.size - 1) },
recordProvider: (key: T) -> NavRecord<out T>
) {
BackHandler(backstack.size > 1, onBack)
wrapperManager.PrepareBackStack(backStack = backstack)
val key = backstack.last()
val record = recordProvider.invoke(key)
- if (record.featureMap[NavDisplay.DIALOG_KEY] == true) {
- if (backstack.size > 1) {
- val previousKey = backstack[backstack.size - 2]
- val lastRecord = recordProvider.invoke(previousKey)
- Box(modifier = modifier) { wrapperManager.ContentForRecord(lastRecord) }
+
+ // Incoming record defines transitions, otherwise it uses default transitions from NavDisplay
+ val finalEnterTransition =
+ record.featureMap[NavDisplay.ENTER_TRANSITION_KEY] as? EnterTransition ?: enterTransition
+ val finalExitTransition =
+ record.featureMap[NavDisplay.EXIT_TRANSITION_KEY] as? ExitTransition ?: exitTransition
+
+ val isDialog = record.featureMap[NavDisplay.DIALOG_KEY] == true
+
+ // if there is a dialog, we should create a transition with the next to last entry instead.
+ val transition =
+ if (isDialog) {
+ if (backstack.size > 1) {
+ val previousKey = backstack[backstack.size - 2]
+ val previousRecord = recordProvider.invoke(previousKey)
+ updateTransition(targetState = previousRecord, label = previousKey.toString())
+ } else {
+ null
+ }
+ } else {
+ updateTransition(targetState = record, label = key.toString())
}
+
+ transition?.AnimatedContent(
+ modifier = modifier,
+ transitionSpec = {
+ ContentTransform(
+ targetContentEnter = finalEnterTransition,
+ initialContentExit = finalExitTransition,
+ sizeTransform = sizeTransform
+ )
+ },
+ contentAlignment = contentAlignment,
+ contentKey = { it.key }
+ ) { innerRecord ->
+ wrapperManager.ContentForRecord(innerRecord)
+ }
+
+ if (isDialog) {
Dialog(onBack) { wrapperManager.ContentForRecord(record) }
- } else {
- Box(modifier = modifier) { wrapperManager.ContentForRecord(record) }
}
}
diff --git a/navigation3/navigation3/src/commonMain/kotlin/androidx/navigation3/NavWrapperManager.kt b/navigation3/navigation3/src/commonMain/kotlin/androidx/navigation3/NavWrapperManager.kt
index 114ac13..4a137df 100644
--- a/navigation3/navigation3/src/commonMain/kotlin/androidx/navigation3/NavWrapperManager.kt
+++ b/navigation3/navigation3/src/commonMain/kotlin/androidx/navigation3/NavWrapperManager.kt
@@ -36,7 +36,7 @@
*
* @param navContentWrappers the [NavContentWrapper]s that are providing data to the content
*/
-public class NavWrapperManager(navContentWrappers: List<NavContentWrapper>) {
+public class NavWrapperManager(navContentWrappers: List<NavContentWrapper> = emptyList()) {
/**
* Final list of wrappers. This always adds a [SaveableStateNavContentWrapper] by default, as it
* is required. It then filters out any duplicates to ensure there is always one instance of any
diff --git a/navigation3/navigation3/src/commonMain/kotlin/androidx/navigation3/SaveableStateNavContentWrapper.kt b/navigation3/navigation3/src/commonMain/kotlin/androidx/navigation3/SaveableStateNavContentWrapper.kt
index cab7204..9c6443f 100644
--- a/navigation3/navigation3/src/commonMain/kotlin/androidx/navigation3/SaveableStateNavContentWrapper.kt
+++ b/navigation3/navigation3/src/commonMain/kotlin/androidx/navigation3/SaveableStateNavContentWrapper.kt
@@ -72,7 +72,12 @@
DisposableEffect(key1 = key) {
refCount[key] = refCount.getOrDefault(key, 0).plus(1)
onDispose {
- if (refCount[key] == 0) {
+ // We need to check to make sure that the refcount has been cleared here because
+ // when we are using animations, if the entire back stack is changed, we will
+ // execute the onDispose above that clears all of the counts before we finish the
+ // transition and run this onDispose so our count will already be gone and we
+ // should just remove the state.
+ if (!refCount.contains(key) || refCount[key] == 0) {
savedStateHolder?.removeState(key)
} else {
refCount[key] =
diff --git a/pdf/pdf-document-service/src/androidTest/kotlin/androidx/pdf/service/connect/FakePdfServiceConnection.kt b/pdf/pdf-document-service/src/androidTest/kotlin/androidx/pdf/service/connect/FakePdfServiceConnection.kt
index 49e7a5e3..5c54d7a 100644
--- a/pdf/pdf-document-service/src/androidTest/kotlin/androidx/pdf/service/connect/FakePdfServiceConnection.kt
+++ b/pdf/pdf-document-service/src/androidTest/kotlin/androidx/pdf/service/connect/FakePdfServiceConnection.kt
@@ -28,13 +28,16 @@
override val context: Context,
override val isConnected: Boolean,
override var documentBinder: PdfDocumentRemote? = null,
+ override var needsToReopenDocument: Boolean = false,
private val onServiceConnected: () -> Unit = {}
) : PdfServiceConnection {
- override suspend fun bindAndConnect(uri: Uri) {
+ override suspend fun connect(uri: Uri) {
documentBinder = PdfDocumentRemoteImpl(PdfDocumentRendererFactoryImpl())
onServiceConnected(null, null)
}
+ override suspend fun blockUntilConnected() {}
+
override fun disconnect() {
documentBinder?.closePdfDocument()
}
diff --git a/pdf/pdf-document-service/src/main/kotlin/androidx/pdf/SandboxedPdfDocument.kt b/pdf/pdf-document-service/src/main/kotlin/androidx/pdf/SandboxedPdfDocument.kt
index 097d35b..fdecb66 100644
--- a/pdf/pdf-document-service/src/main/kotlin/androidx/pdf/SandboxedPdfDocument.kt
+++ b/pdf/pdf-document-service/src/main/kotlin/androidx/pdf/SandboxedPdfDocument.kt
@@ -22,6 +22,7 @@
import android.graphics.Rect
import android.net.Uri
import android.os.Build
+import android.os.DeadObjectException
import android.os.ParcelFileDescriptor
import android.util.Size
import android.util.SparseArray
@@ -63,8 +64,9 @@
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class SandboxedPdfDocument(
override val uri: Uri,
+ public val connection: PdfServiceConnection,
+ private val password: String?,
private val fileDescriptor: ParcelFileDescriptor,
- private val connection: PdfServiceConnection,
private val dispatcher: CoroutineDispatcher,
override val pageCount: Int,
override val isLinearized: Boolean,
@@ -186,9 +188,16 @@
}
private suspend fun <T> withDocument(block: (PdfDocumentRemote) -> T): T {
+ connection.blockUntilConnected()
+
val binder =
connection.documentBinder
- ?: throw IllegalStateException("Binder object to the service must not be null!")
+ ?: throw DeadObjectException("Binder object to the service must not be null!")
+
+ if (connection.needsToReopenDocument) {
+ binder.openPdfDocument(fileDescriptor, password)
+ connection.needsToReopenDocument = false
+ }
return withContext(dispatcher) { block(binder) }
}
diff --git a/pdf/pdf-document-service/src/main/kotlin/androidx/pdf/SandboxedPdfLoader.kt b/pdf/pdf-document-service/src/main/kotlin/androidx/pdf/SandboxedPdfLoader.kt
index e34854c..97190e4 100644
--- a/pdf/pdf-document-service/src/main/kotlin/androidx/pdf/SandboxedPdfLoader.kt
+++ b/pdf/pdf-document-service/src/main/kotlin/androidx/pdf/SandboxedPdfLoader.kt
@@ -58,7 +58,7 @@
val connection: PdfServiceConnection =
testingConnection ?: PdfServiceConnectionImpl(context)
if (!connection.isConnected) {
- connection.bindAndConnect(uri)
+ connection.connect(uri)
}
return withContext(dispatcher) { openDocumentUri(uri, password, connection) }
@@ -80,8 +80,9 @@
PdfLoadingStatus.SUCCESS -> {
return SandboxedPdfDocument(
uri,
- pfd,
connection,
+ password,
+ pfd,
dispatcher,
binder.numPages(),
binder.isPdfLinearized(),
diff --git a/pdf/pdf-document-service/src/main/kotlin/androidx/pdf/service/connect/PdfServiceConnection.kt b/pdf/pdf-document-service/src/main/kotlin/androidx/pdf/service/connect/PdfServiceConnection.kt
index 5af9214..97a22cd 100644
--- a/pdf/pdf-document-service/src/main/kotlin/androidx/pdf/service/connect/PdfServiceConnection.kt
+++ b/pdf/pdf-document-service/src/main/kotlin/androidx/pdf/service/connect/PdfServiceConnection.kt
@@ -32,11 +32,21 @@
public val context: Context
/** True if the service is actively bound */
public val isConnected: Boolean
+
+ /**
+ * True if the document needs to be reopened. This is expected to be set when the service
+ * connection is re-established after unexpected disconnection.
+ */
+ public var needsToReopenDocument: Boolean
+
/** The [PdfDocumentRemote] instance, if the service is actively bound */
public val documentBinder: PdfDocumentRemote?
/** Initiates binding to the service, and suspends until the service is bound */
- public suspend fun bindAndConnect(uri: Uri)
+ public suspend fun connect(uri: Uri)
+
+ /** Blocks the current thread of execution until the connection is established */
+ public suspend fun blockUntilConnected()
/** Immediately unbinds the service */
public fun disconnect()
diff --git a/pdf/pdf-document-service/src/main/kotlin/androidx/pdf/service/connect/PdfServiceConnectionImpl.kt b/pdf/pdf-document-service/src/main/kotlin/androidx/pdf/service/connect/PdfServiceConnectionImpl.kt
index 9035a14..114d334 100644
--- a/pdf/pdf-document-service/src/main/kotlin/androidx/pdf/service/connect/PdfServiceConnectionImpl.kt
+++ b/pdf/pdf-document-service/src/main/kotlin/androidx/pdf/service/connect/PdfServiceConnectionImpl.kt
@@ -32,6 +32,8 @@
internal class PdfServiceConnectionImpl(override val context: Context) : PdfServiceConnection {
private val _eventStateFlow: MutableStateFlow<ConnectionState> = MutableStateFlow(Disconnected)
+ override var needsToReopenDocument: Boolean = false
+
override val isConnected: Boolean
get() = _eventStateFlow.value is Connected
@@ -44,10 +46,11 @@
}
override fun onServiceDisconnected(name: ComponentName?) {
+ needsToReopenDocument = true
_eventStateFlow.update { Disconnected }
}
- override suspend fun bindAndConnect(uri: Uri) {
+ override suspend fun connect(uri: Uri) {
val intent =
Intent(context, PdfDocumentServiceImpl::class.java).apply {
// Providing a different Intent to the Service per document is required to obtain a
@@ -59,6 +62,10 @@
_eventStateFlow.first { it is Connected }
}
+ override suspend fun blockUntilConnected() {
+ _eventStateFlow.first { it is Connected }
+ }
+
override fun disconnect() {
if (isConnected) {
documentBinder?.closePdfDocument()
diff --git a/pdf/pdf-viewer-fragment/src/main/res/drawable/drag_indicator.xml b/pdf/pdf-viewer-fragment/src/main/res/drawable/drag_indicator.xml
new file mode 100644
index 0000000..4a0d1b6
--- /dev/null
+++ b/pdf/pdf-viewer-fragment/src/main/res/drawable/drag_indicator.xml
@@ -0,0 +1,29 @@
+<!--
+ Copyright 2024 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:autoMirrored="true"
+ android:tint="?attr/colorOnSurfaceVariant"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+
+ <path
+ android:fillColor="?attr/colorOnSurfaceVariant"
+ android:pathData="M480,880L240,640L297,583L480,766L663,583L720,640L480,880ZM298,376L240,320L480,80L720,320L662,376L480,194L298,376Z" />
+
+</vector>
diff --git a/pdf/pdf-viewer-fragment/src/main/res/drawable/fast_scroll_vertical_thumb_drawable.xml b/pdf/pdf-viewer-fragment/src/main/res/drawable/fast_scroll_vertical_thumb_drawable.xml
new file mode 100644
index 0000000..872b369
--- /dev/null
+++ b/pdf/pdf-viewer-fragment/src/main/res/drawable/fast_scroll_vertical_thumb_drawable.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2024 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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+
+ <solid android:color="?attr/colorSurfaceContainerHigh" />
+</shape>
diff --git a/pdf/pdf-viewer-fragment/src/main/res/layout/pdf_viewer_fragment.xml b/pdf/pdf-viewer-fragment/src/main/res/layout/pdf_viewer_fragment.xml
index 931ab6b..c2af5a9 100644
--- a/pdf/pdf-viewer-fragment/src/main/res/layout/pdf_viewer_fragment.xml
+++ b/pdf/pdf-viewer-fragment/src/main/res/layout/pdf_viewer_fragment.xml
@@ -28,7 +28,10 @@
app:layout_constraintBottom_toTopOf="@+id/pdfSearchView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
+ app:layout_constraintTop_toTopOf="parent"
+ app:fastScrollVerticalThumbDrawable="@drawable/fast_scroll_vertical_thumb_drawable"
+ app:fastScrollVerticalTrackDrawable="@drawable/drag_indicator"
+ app:fastScrollPageIndicatorBackgroundDrawable="@drawable/page_indicator_background" />
<!-- ProgressBar for loading -->
<ProgressBar
diff --git a/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/AccessibilityPageHelperTest.kt b/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/AccessibilityPageHelperTest.kt
index 7789656..3f618fd 100644
--- a/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/AccessibilityPageHelperTest.kt
+++ b/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/AccessibilityPageHelperTest.kt
@@ -16,16 +16,10 @@
package androidx.pdf.view
-import android.graphics.Point
import android.graphics.RectF
-import android.net.Uri
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
-import androidx.pdf.PdfDocument
-import androidx.pdf.content.PdfPageGotoLinkContent
-import androidx.pdf.content.PdfPageLinkContent
-import androidx.pdf.content.PdfPageTextContent
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso
import androidx.test.espresso.matcher.ViewMatchers.withId
@@ -46,43 +40,7 @@
private lateinit var pdfView: PdfView
private lateinit var activityScenario: ActivityScenario<PdfViewTestActivity>
- private val pdfDocument =
- FakePdfDocument(
- pages = List(10) { Point(100, 200) },
- textContents =
- List(10) { index ->
- PdfPageTextContent(
- bounds = listOf(RectF(0f, 0f, 100f, 200f)),
- text = "Sample text for page ${index + 1}"
- )
- },
- pageLinks =
- mapOf(
- 0 to
- PdfDocument.PdfPageLinks(
- gotoLinks =
- listOf(
- PdfPageGotoLinkContent(
- bounds = listOf(RectF(25f, 30f, 75f, 50f)),
- destination =
- PdfPageGotoLinkContent.Destination(
- pageNumber = VALID_PAGE_NUMBER,
- xCoordinate = 10f,
- yCoordinate = 40f,
- zoom = 1f
- )
- )
- ),
- externalLinks =
- listOf(
- PdfPageLinkContent(
- bounds = listOf(RectF(25f, 60f, 75f, 80f)),
- uri = Uri.parse(URI_WITH_VALID_SCHEME)
- )
- ),
- )
- )
- )
+ private val pdfDocument = FakePdfDocument.newInstance()
@Before
fun setupPdfView() {
@@ -139,8 +97,8 @@
)
testCases.forEach { (x, y, expectedPage) ->
- val adjustedX = pdfView.toViewCoord(x, pdfView.zoom, pdfView.scrollX)
- val adjustedY = pdfView.toViewCoord(y, pdfView.zoom, pdfView.scrollY)
+ val adjustedX = PdfView.toViewCoord(x, pdfView.zoom, pdfView.scrollX)
+ val adjustedY = PdfView.toViewCoord(y, pdfView.zoom, pdfView.scrollY)
assertThat(accessibilityPageHelper.getVirtualViewAt(adjustedX, adjustedY))
.isEqualTo(expectedPage)
@@ -248,5 +206,3 @@
/** Arbitrary fixed ID for PdfView */
private const val PDF_VIEW_ID = 123456789
-private const val URI_WITH_VALID_SCHEME = "https://www.example.com"
-private const val VALID_PAGE_NUMBER = 4
diff --git a/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/FakePdfDocument.kt b/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/FakePdfDocument.kt
index 0f93d6b..82e8088 100644
--- a/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/FakePdfDocument.kt
+++ b/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/FakePdfDocument.kt
@@ -21,6 +21,7 @@
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
+import android.graphics.RectF
import android.net.Uri
import android.os.Build
import android.util.Size
@@ -30,6 +31,8 @@
import androidx.pdf.PdfDocument
import androidx.pdf.content.PageMatchBounds
import androidx.pdf.content.PageSelection
+import androidx.pdf.content.PdfPageGotoLinkContent
+import androidx.pdf.content.PdfPageLinkContent
import androidx.pdf.content.PdfPageTextContent
import androidx.pdf.content.SelectionBoundary
import kotlin.random.Random
@@ -186,6 +189,49 @@
/* No-op, fake */
}
}
+
+ companion object {
+ const val URI_WITH_VALID_SCHEME = "https://www.example.com"
+ const val VALID_PAGE_NUMBER = 4
+
+ fun newInstance(): FakePdfDocument =
+ FakePdfDocument(
+ pages = List(10) { Point(100, 200) },
+ textContents =
+ List(10) { index ->
+ PdfPageTextContent(
+ bounds = listOf(RectF(0f, 0f, 100f, 200f)),
+ text = "Sample text for page ${index + 1}"
+ )
+ },
+ pageLinks =
+ mapOf(
+ 0 to
+ PdfDocument.PdfPageLinks(
+ gotoLinks =
+ listOf(
+ PdfPageGotoLinkContent(
+ bounds = listOf(RectF(25f, 30f, 75f, 50f)),
+ destination =
+ PdfPageGotoLinkContent.Destination(
+ pageNumber = VALID_PAGE_NUMBER,
+ xCoordinate = 10f,
+ yCoordinate = 40f,
+ zoom = 1f
+ )
+ )
+ ),
+ externalLinks =
+ listOf(
+ PdfPageLinkContent(
+ bounds = listOf(RectF(25f, 60f, 75f, 80f)),
+ uri = Uri.parse(URI_WITH_VALID_SCHEME)
+ )
+ ),
+ )
+ )
+ )
+ }
}
/** Represents the size and scale of a [Bitmap] requested from [PdfDocument.BitmapSource] */
diff --git a/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/fastscroll/FastScrollCalculatorTest.kt b/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/fastscroll/FastScrollCalculatorTest.kt
new file mode 100644
index 0000000..46b5957
--- /dev/null
+++ b/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/fastscroll/FastScrollCalculatorTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2025 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.pdf.view.fastscroll
+
+import android.content.Context
+import androidx.pdf.R
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import junit.framework.TestCase.assertEquals
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FastScrollCalculatorTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private var scrollerTopMarginDp: Int =
+ context.getDimensions(R.dimen.scroller_top_margin).toInt()
+ private var scrollerBottomMarginDp: Int =
+ context.getDimensions(R.dimen.scroller_bottom_margin).toInt()
+
+ @Test
+ fun constrainScrollPosition_withinBounds_returnsSameValue() = runTest {
+ val calculator = FastScrollCalculator(context)
+
+ val constrainedPosition =
+ calculator.constrainScrollPosition(scrollY = 200f, viewHeight = 500, thumbHeightPx = 50)
+
+ val expectedValue = 200
+ assertEquals(expectedValue, constrainedPosition)
+ }
+
+ @Test
+ fun constrainScrollPosition_belowLowerBound_returnsTopMargin() = runTest {
+ val calculator = FastScrollCalculator(context)
+
+ val constrainedPosition =
+ calculator.constrainScrollPosition(scrollY = -50f, viewHeight = 500, thumbHeightPx = 50)
+
+ val expectedValue = scrollerTopMarginDp.dpToPx(context)
+ assertEquals(expectedValue, constrainedPosition)
+ }
+
+ @Test
+ fun constrainScrollPosition_aboveUpperBound_returnsHeightAdjustedBottomMargin() = runTest {
+ val calculator = FastScrollCalculator(context)
+ val viewHeight = 500
+ val thumbHeightPx = 50
+
+ val constrainedPosition =
+ calculator.constrainScrollPosition(scrollY = 600f, viewHeight, thumbHeightPx)
+
+ val expectedValue = viewHeight - (scrollerBottomMarginDp.dpToPx(context) + thumbHeightPx)
+ assertEquals(expectedValue, constrainedPosition)
+ }
+
+ @Test
+ fun computeThumbPosition() = runTest {
+ val calculator = FastScrollCalculator(context)
+
+ val fastScrollY =
+ calculator.computeThumbPosition(
+ scrollY = 100,
+ zoom = 1f,
+ viewHeight = 500,
+ thumbHeightPx = 50,
+ estimatedFullHeight = 1000
+ )
+
+ val expectedScrollValue = 107
+ assertEquals(expectedScrollValue, fastScrollY)
+ }
+}
diff --git a/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/fastscroll/FastScrollDrawerTest.kt b/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/fastscroll/FastScrollDrawerTest.kt
new file mode 100644
index 0000000..79fda6e
--- /dev/null
+++ b/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/fastscroll/FastScrollDrawerTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2025 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.pdf.view.fastscroll
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.util.Range
+import androidx.core.content.ContextCompat
+import androidx.pdf.PdfDocument
+import androidx.pdf.R
+import androidx.pdf.view.FakePdfDocument
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import junit.framework.TestCase.assertEquals
+import junit.framework.TestCase.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.kotlin.any
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FastScrollDrawerTest {
+ private lateinit var context: Context
+
+ private lateinit var pdfDocument: PdfDocument
+ private lateinit var thumbDrawable: Drawable
+ private lateinit var trackDrawable: Drawable
+ private lateinit var pageIndicatorBackgroundDrawable: Drawable
+ private lateinit var spyCanvas: Canvas
+ private lateinit var fastScrollDrawer: FastScrollDrawer
+
+ @Before
+ fun setup() {
+ context = ApplicationProvider.getApplicationContext()
+ pdfDocument = FakePdfDocument.newInstance()
+ thumbDrawable = spy(ContextCompat.getDrawable(context, R.drawable.fastscroll_background)!!)
+ trackDrawable = ContextCompat.getDrawable(context, R.drawable.drag_indicator)!!
+ pageIndicatorBackgroundDrawable =
+ ContextCompat.getDrawable(context, R.drawable.page_indicator_background)!!
+ spyCanvas = spy(Canvas())
+
+ fastScrollDrawer =
+ FastScrollDrawer(
+ context,
+ pdfDocument,
+ thumbDrawable,
+ trackDrawable,
+ pageIndicatorBackgroundDrawable
+ )
+ }
+
+ @Test
+ fun draw_withinVisibleArea_verifyDrawOnCanvas() {
+ val zoom = 1.5f
+ val scrollY = 100
+ val visibleAreaPx = Rect(0, 0, 500, 800)
+ val visiblePages = Range(1, 5)
+
+ fastScrollDrawer.draw(spyCanvas, zoom, scrollY, visibleAreaPx, visiblePages)
+
+ val leftCaptor = ArgumentCaptor.forClass(Int::class.java)
+ val topCaptor = ArgumentCaptor.forClass(Int::class.java)
+ val rightCaptor = ArgumentCaptor.forClass(Int::class.java)
+ val bottomCaptor = ArgumentCaptor.forClass(Int::class.java)
+ verify(thumbDrawable)
+ .setBounds(
+ leftCaptor.capture(),
+ topCaptor.capture(),
+ rightCaptor.capture(),
+ bottomCaptor.capture()
+ )
+ verify(thumbDrawable).draw(spyCanvas)
+
+ val expectedLeftRange = Range(600, 700)
+ val expectedTopRange = Range(100, 200)
+ val expectedRightRange = Range(700, 800)
+ val expectedBottomRange = Range(200, 300)
+ assertTrue(expectedLeftRange.contains(leftCaptor.value))
+ assertTrue(expectedTopRange.contains(topCaptor.value))
+ assertTrue(expectedRightRange.contains(rightCaptor.value))
+ assertTrue(expectedBottomRange.contains(bottomCaptor.value))
+
+ val textCaptor = ArgumentCaptor.forClass(String::class.java)
+ verify(spyCanvas).drawText(textCaptor.capture(), anyFloat(), anyFloat(), any())
+
+ // Hyphens are being interpreted in unicode rather than ascii which is failing the assertion
+ // hence forcing ascii conversion
+ val expectedLabelValue = "2-6 / 10".replace("\u2014", "-")
+ assertEquals(expectedLabelValue, textCaptor.value)
+ }
+}
diff --git a/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/fastscroll/FastScrollGestureDetectorTest.kt b/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/fastscroll/FastScrollGestureDetectorTest.kt
new file mode 100644
index 0000000..cf29e7a
--- /dev/null
+++ b/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/fastscroll/FastScrollGestureDetectorTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2025 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.pdf.view.fastscroll
+
+import android.content.Context
+import android.view.MotionEvent
+import androidx.core.content.ContextCompat
+import androidx.pdf.PdfDocument
+import androidx.pdf.R
+import androidx.pdf.view.FakePdfDocument
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.never
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FastScrollGestureDetectorTest {
+ private lateinit var gestureDetector: FastScrollGestureDetector
+
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+ private val pdfDocument: PdfDocument = FakePdfDocument.newInstance()
+ private val thumbDrawable =
+ ContextCompat.getDrawable(context, R.drawable.fastscroll_background)!!
+ private val trackDrawable = ContextCompat.getDrawable(context, R.drawable.drag_indicator)!!
+ private val pageIndicatorBackgroundDrawable =
+ ContextCompat.getDrawable(context, R.drawable.page_indicator_background)!!
+ private val fastScrollDrawer =
+ FastScrollDrawer(
+ context,
+ pdfDocument,
+ thumbDrawable,
+ trackDrawable,
+ pageIndicatorBackgroundDrawable
+ )
+ private val fastScrollCalculator = FastScrollCalculator(context)
+ private val fastScroller = FastScroller(fastScrollDrawer, fastScrollCalculator)
+
+ private val gestureHandler = spy(FakeFastScrollGestureHandler())
+
+ @Before
+ fun setup() {
+ gestureDetector = FastScrollGestureDetector(fastScroller, gestureHandler)
+ }
+
+ @Test
+ fun testHandleEvent_actionDown_outsideBounds_doesNotTrack() {
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 10f, 10f, 0)
+ val viewWidth = 500
+
+ val result = gestureDetector.handleEvent(event, viewWidth)
+
+ assertFalse(result)
+ assertFalse(
+ gestureDetector.handleEvent(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 10f, 10f, 0),
+ viewWidth
+ )
+ )
+ verify(gestureHandler, never()).onFastScrollDetected(10f)
+ }
+
+ @Test
+ fun testHandleEvent_actionDown_withinBounds_tracksAndHandlesMove() {
+ val downEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 450f, 50f, 0)
+ val viewWidth = 500
+
+ val result = gestureDetector.handleEvent(downEvent, viewWidth)
+
+ assertTrue(result)
+
+ val moveEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 450f, 400f, 0)
+ assertTrue(gestureDetector.handleEvent(moveEvent, viewWidth))
+ verify(gestureHandler).onFastScrollDetected(400f)
+ }
+
+ @Test
+ fun testHandleEvent_actionUp_stopsTracking() {
+ val downEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 450f, 50f, 0)
+ val viewWidth = 500
+ gestureDetector.handleEvent(downEvent, viewWidth)
+
+ val moveEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 450f, 400f, 0)
+ assertTrue(gestureDetector.handleEvent(moveEvent, viewWidth))
+ verify(gestureHandler).onFastScrollDetected(400f)
+
+ val upEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 450f, 400f, 0)
+ assertTrue(gestureDetector.handleEvent(upEvent, viewWidth))
+ }
+
+ open class FakeFastScrollGestureHandler : FastScrollGestureDetector.FastScrollGestureHandler {
+ var lastScrollY: Float = 0f
+
+ override fun onFastScrollDetected(scrollY: Float) {
+ lastScrollY = scrollY
+ }
+ }
+}
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfConnection.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfConnection.java
index e78a8b7..0b02803 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfConnection.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/loader/PdfConnection.java
@@ -175,10 +175,14 @@
}
void disconnect() {
- if (mConnected) {
- mContext.unbindService(this);
- mConnected = false;
+ mLock.lock();
+ try {
+ if (mConnected) {
+ mContext.unbindService(this);
+ mConnected = false;
+ }
+ } finally {
+ mLock.unlock();
}
}
-
}
diff --git a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/PageLayoutManager.kt b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/PageLayoutManager.kt
index a70993a..2844056 100644
--- a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/PageLayoutManager.kt
+++ b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/PageLayoutManager.kt
@@ -73,6 +73,16 @@
val visiblePages: StateFlow<Range<Int>>
get() = _visiblePages
+ private val _fullyVisiblePages = MutableStateFlow<Range<Int>>(Range(0, 0))
+
+ /**
+ * A [StateFlow] emitting the range of pages considered to be in the viewport.
+ *
+ * Values in the range are 0-indexed.
+ */
+ val fullyVisiblePages: StateFlow<Range<Int>>
+ get() = _fullyVisiblePages
+
/** The 0-indexed maximum page whose dimensions have been requested */
private var requestedReach: Int = paginationModel.reach
@@ -157,6 +167,13 @@
// Try emit will always succeed for MutableStateFlow
val prevVisiblePages = _visiblePages.value
val newVisiblePages = paginationModel.getPagesInViewport(contentTop, contentBottom)
+
+ val fullyVisiblePageRange =
+ paginationModel.getPagesInViewport(contentTop, contentBottom, includePartial = false)
+ if (fullyVisiblePageRange != _fullyVisiblePages.value) {
+ _fullyVisiblePages.tryEmit(fullyVisiblePageRange)
+ }
+
if (prevVisiblePages != newVisiblePages) {
_visiblePages.tryEmit(newVisiblePages)
increaseReach(
diff --git a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/PaginationModel.kt b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/PaginationModel.kt
index bfea625..c60be6f 100644
--- a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/PaginationModel.kt
+++ b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/PaginationModel.kt
@@ -26,7 +26,6 @@
import java.util.Collections
import kotlin.math.abs
import kotlin.math.max
-import kotlin.math.min
/**
* Stores the size and position of PDF pages. All dimensions and coordinates should be assumed to be
@@ -157,21 +156,22 @@
* [viewportTop] and [viewportBottom], which are expected to be the top and bottom content
* coordinates of the viewport.
*/
- fun getPagesInViewport(viewportTop: Int, viewportBottom: Int): Range<Int> {
- // If the viewport is below all pages, return an empty range at the bottom of this model
- if (reach > 0 && viewportTop > pageBottoms[reach - 1]) {
- return Range(min(reach, numPages - 1), min(reach, numPages - 1))
- }
- // If the viewport is above all pages, return an empty range at the top of this model
- if (viewportBottom < pageTops[0]) {
- return Range(0, 0)
- }
- val rangeStart = abs(Collections.binarySearch(pageBottoms, viewportTop) + 1)
- val rangeEnd = abs(Collections.binarySearch(pageTops, viewportBottom) + 1) - 1
+ fun getPagesInViewport(
+ viewportTop: Int,
+ viewportBottom: Int,
+ includePartial: Boolean = true
+ ): Range<Int> {
+ val startList = if (includePartial) pageBottoms else pageTops
+ val endList = if (includePartial) pageTops else pageBottoms
+
+ val rangeStart = abs(startList.binarySearch(viewportTop) + 1)
+ val rangeEnd = abs(endList.binarySearch(viewportBottom) + 1) - 1
if (rangeEnd < rangeStart) {
- val midPoint = Collections.binarySearch(pageTops, (viewportTop + viewportBottom) / 2)
- val page = maxOf(abs(midPoint + 1) - 1, 0)
+ // No page is entirely visible.
+ val midPoint = (viewportTop + viewportBottom) / 2
+ val midResult = pageTops.binarySearch(midPoint)
+ val page = maxOf(abs(midResult + 1) - 1, 0)
return Range(page, page)
}
diff --git a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/PdfView.kt b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/PdfView.kt
index cf73661..0c1973b 100644
--- a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/PdfView.kt
+++ b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/PdfView.kt
@@ -26,6 +26,7 @@
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF
+import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Looper
import android.os.Parcelable
@@ -49,6 +50,10 @@
import androidx.pdf.util.Accessibility
import androidx.pdf.util.MathUtils
import androidx.pdf.util.ZoomUtils
+import androidx.pdf.view.fastscroll.FastScrollCalculator
+import androidx.pdf.view.fastscroll.FastScrollDrawer
+import androidx.pdf.view.fastscroll.FastScrollGestureDetector
+import androidx.pdf.view.fastscroll.FastScroller
import java.util.LinkedList
import java.util.Queue
import java.util.concurrent.Executors
@@ -79,6 +84,22 @@
@JvmOverloads
constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
View(context, attrs, defStyle) {
+
+ private val fastScrollVerticalThumbDrawable: Drawable?
+ private val fastScrollVerticalTrackDrawable: Drawable?
+ private val fastScrollPageIndicatorBackgroundDrawable: Drawable?
+
+ init {
+ val typedArray = context.obtainStyledAttributes(attrs, R.styleable.PdfView)
+ fastScrollVerticalThumbDrawable =
+ typedArray.getDrawable(R.styleable.PdfView_fastScrollVerticalThumbDrawable)
+ fastScrollVerticalTrackDrawable =
+ typedArray.getDrawable(R.styleable.PdfView_fastScrollVerticalTrackDrawable)
+ fastScrollPageIndicatorBackgroundDrawable =
+ typedArray.getDrawable(R.styleable.PdfView_fastScrollPageIndicatorBackgroundDrawable)
+ typedArray.recycle()
+ }
+
/** Supply a [PdfDocument] to process the PDF content for rendering */
public var pdfDocument: PdfDocument? = null
set(value) {
@@ -108,7 +129,6 @@
checkMainThread()
field = value
onZoomChanged()
- invalidate()
}
/**
@@ -128,6 +148,9 @@
private val visiblePages: Range<Int>
get() = pageLayoutManager?.visiblePages?.value ?: Range(0, 0)
+ private val fullyVisiblePages: Range<Int>
+ get() = pageLayoutManager?.fullyVisiblePages?.value ?: Range(0, 0)
+
/** The first page in the viewport, including partially-visible pages. 0-indexed. */
public val firstVisiblePage: Int
get() = visiblePages.lower
@@ -156,9 +179,20 @@
/** The currently selected PDF content, as [Selection] */
public val currentSelection: Selection?
get() {
- return selectionStateManager?.selectionModel?.selection
+ return selectionStateManager?.selectionModel?.value?.selection
}
+ /** Listener interface to receive updates when the [currentSelection] changes */
+ public interface OnSelectionChangedListener {
+ /** Called when the [Selection] has changed */
+ public fun onSelectionChanged(
+ previousSelection: Selection?,
+ newSelection: Selection?,
+ )
+ }
+
+ private var onSelectionChangedListeners = mutableListOf<OnSelectionChangedListener>()
+
/**
* The [CoroutineScope] used to make suspending calls to [PdfDocument]. The size of the fixed
* thread pool is arbitrary and subject to tuning.
@@ -169,8 +203,8 @@
private var pageLayoutManager: PageLayoutManager? = null
private var pageManager: PageManager? = null
private var visiblePagesCollector: Job? = null
- private var dimensionsCollector: Job? = null
- private var invalidationCollector: Job? = null
+ private var layoutInfoCollector: Job? = null
+ private var pageSignalCollector: Job? = null
private var selectionStateCollector: Job? = null
private var deferredScrollPage: Int? = null
@@ -187,6 +221,9 @@
*/
private var oldWidth: Int? = width
+ private var fastScroller: FastScroller? = null
+ private var fastScrollGestureDetector: FastScrollGestureDetector? = null
+
private val gestureHandler = ZoomScrollGestureHandler()
private val gestureTracker = GestureTracker(context).apply { delegate = gestureHandler }
@@ -197,6 +234,18 @@
// To avoid allocations during drawing
private val visibleAreaRect = Rect()
+ private val fastScrollGestureHandler =
+ object : FastScrollGestureDetector.FastScrollGestureHandler {
+ override fun onFastScrollDetected(scrollY: Float) {
+ fastScroller?.let {
+ val updatedY =
+ it.viewScrollPositionFromFastScroller(scrollY, zoom, height, contentHeight)
+ scrollTo(scrollX, updatedY)
+ invalidate()
+ }
+ }
+ }
+
@VisibleForTesting internal var accessibilityPageHelper: AccessibilityPageHelper? = null
@VisibleForTesting
internal var isTouchExplorationEnabled: Boolean =
@@ -263,6 +312,33 @@
}
}
+ /**
+ * Adds the specified listener to the list of listeners that will be notified of selection
+ * change events.
+ *
+ * @param listener listener to notify when selection change events occur
+ * @see removeOnSelectionChangedListener
+ */
+ public fun addOnSelectionChangedListener(listener: OnSelectionChangedListener) {
+ onSelectionChangedListeners.add(listener)
+ }
+
+ /**
+ * Removes the specified listener from the list of listeners that will be notified of selection
+ * change events.
+ *
+ * @param listener listener to remove
+ */
+ public fun removeOnSelectionChangedListener(listener: OnSelectionChangedListener) {
+ onSelectionChangedListeners.remove(listener)
+ }
+
+ private fun dispatchSelectionChanged(old: Selection?, new: Selection?) {
+ for (listener in onSelectionChangedListeners) {
+ listener.onSelectionChanged(old, new)
+ }
+ }
+
private fun gotoPage(pageNum: Int) {
checkMainThread()
val localPageLayoutManager =
@@ -348,7 +424,7 @@
for (i in visiblePages.lower..visiblePages.upper) {
val pageLoc = localPaginationManager.getPageLocation(i, getVisibleAreaInContentCoords())
pageManager?.drawPage(i, canvas, pageLoc)
- selectionModel?.let {
+ selectionModel?.value?.let {
selectionRenderer.drawSelectionOnPage(
model = it,
pageNum = i,
@@ -359,14 +435,32 @@
}
}
canvas.restore()
+
+ val documentPageCount = pdfDocument?.pageCount ?: 0
+ if (documentPageCount > 1) {
+ fastScroller?.drawScroller(
+ canvas,
+ scrollY,
+ zoom,
+ height,
+ /* visibleArea= */ getVisibleAreaInContentCoords(),
+ fullyVisiblePages,
+ contentHeight
+ )
+ }
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
- var handled = maybeDragSelectionHandle(event)
+ var handled = event?.let { fastScrollGestureDetector?.handleEvent(it, width) } ?: false
+ handled = handled || maybeDragSelectionHandle(event)
handled = handled || event?.let { gestureTracker.feed(it) } ?: false
return handled || super.onTouchEvent(event)
}
+ private fun maybeShowFastScroller() {
+ fastScroller?.show { postInvalidate() }
+ }
+
private fun maybeDragSelectionHandle(event: MotionEvent?): Boolean {
if (event == null) return false
val touchPoint =
@@ -385,6 +479,9 @@
override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
super.onScrollChanged(l, t, oldl, oldt)
+ // TODO(b/390003204): Prevent showing of the scrubber when the document only been
+ // translated on the x-axis
+ maybeShowFastScroller()
onViewportChanged()
}
@@ -433,7 +530,7 @@
}
state.documentUri = pdfDocument?.uri
state.paginationModel = pageLayoutManager?.paginationModel
- state.selectionModel = selectionStateManager?.selectionModel
+ state.selectionModel = selectionStateManager?.selectionModel?.value
return state
}
@@ -579,32 +676,23 @@
CoroutineScope(HandlerCompat.createAsync(handler.looper).asCoroutineDispatcher())
pageLayoutManager?.let { manager ->
// Don't let two copies of this run concurrently
- val dimensionsToJoin = dimensionsCollector?.apply { cancel() }
- dimensionsCollector =
+ val layoutInfoToJoin = layoutInfoCollector?.apply { cancel() }
+ layoutInfoCollector =
mainScope.launch(start = CoroutineStart.UNDISPATCHED) {
- manager.dimensions.collect {
- // Prevent 2 copies from running concurrently
- dimensionsToJoin?.join()
- onPageDimensionsReceived(it.first, it.second)
+ // Prevent 2 copies from running concurrently
+ layoutInfoToJoin?.join()
+ launch {
+ manager.dimensions.collect { onPageDimensionsReceived(it.first, it.second) }
}
- }
- // Don't let two copies of this run concurrently
- val visiblePagesToJoin = visiblePagesCollector?.apply { cancel() }
- visiblePagesCollector =
- mainScope.launch(start = CoroutineStart.UNDISPATCHED) {
- manager.visiblePages.collect {
- // Prevent 2 copies from running concurrently
- visiblePagesToJoin?.join()
- onVisiblePagesChanged()
- }
+ launch { manager.visiblePages.collect { onVisiblePagesChanged() } }
}
}
pageManager?.let { manager ->
- val invalidationToJoin = invalidationCollector?.apply { cancel() }
- invalidationCollector =
+ val pageSignalsToJoin = pageSignalCollector?.apply { cancel() }
+ pageSignalCollector =
mainScope.launch(start = CoroutineStart.UNDISPATCHED) {
// Prevent 2 copies from running concurrently
- invalidationToJoin?.join()
+ pageSignalsToJoin?.join()
launch { manager.invalidationSignalFlow.collect { invalidate() } }
launch {
manager.pageTextReadyFlow.collect { pageNum ->
@@ -619,15 +707,22 @@
mainScope.launch(start = CoroutineStart.UNDISPATCHED) {
// Prevent 2 copies from running concurrently
selectionToJoin?.join()
- manager.selectionUiSignalBus.collect { onSelectionUiSignal(it) }
+ launch { manager.selectionUiSignalBus.collect { onSelectionUiSignal(it) } }
+ var prevSelection = currentSelection
+ launch {
+ manager.selectionModel.collect { newModel ->
+ dispatchSelectionChanged(prevSelection, newModel?.selection)
+ prevSelection = newModel?.selection
+ }
+ }
}
}
}
private fun stopCollectingData() {
- dimensionsCollector?.cancel()
+ layoutInfoCollector?.cancel()
visiblePagesCollector?.cancel()
- invalidationCollector?.cancel()
+ pageSignalCollector?.cancel()
selectionStateCollector?.cancel()
}
@@ -670,6 +765,30 @@
Point(maxBitmapDimensionPx, maxBitmapDimensionPx),
isTouchExplorationEnabled
)
+
+ if (
+ fastScrollVerticalThumbDrawable != null &&
+ fastScrollVerticalTrackDrawable != null &&
+ fastScrollPageIndicatorBackgroundDrawable != null
+ ) {
+
+ val fastScrollCalculator = FastScrollCalculator(context)
+ val fastScrollDrawer =
+ FastScrollDrawer(
+ context,
+ localPdfDocument,
+ fastScrollVerticalThumbDrawable,
+ fastScrollVerticalTrackDrawable,
+ fastScrollPageIndicatorBackgroundDrawable
+ )
+
+ val localFastScroller = FastScroller(fastScrollDrawer, fastScrollCalculator)
+ fastScroller = localFastScroller
+ fastScrollGestureDetector =
+ FastScrollGestureDetector(localFastScroller, fastScrollGestureHandler)
+ maybeShowFastScroller()
+ }
+
// We'll either create our layout and selection managers from restored state, or
// instantiate new ones
if (!maybeRestoreState()) {
@@ -684,6 +803,7 @@
)
setAccessibility()
}
+
// If not, we'll start doing this when we _are_ attached to a visible window
if (isAttachedToVisibleWindow) {
startCollectingData()
@@ -894,10 +1014,6 @@
return toContentCoord(viewY, zoom, scrollY)
}
- internal fun toViewCoord(contentCoord: Float, zoom: Float, scroll: Int): Float {
- return (contentCoord * zoom) - scroll
- }
-
/**
* Converts a one-dimensional coordinate in View space to a one-dimensional coordinate in
* content space
@@ -1266,12 +1382,15 @@
private const val MIN_SCROLL_TO_SWITCH_DP = 30
private const val DEFAULT_PAGE_PREFETCH_RADIUS: Int = 2
- private const val INVALID_ID = -1
private fun checkMainThread() {
check(Looper.myLooper() == Looper.getMainLooper()) {
"Property must be set on the main thread"
}
}
+
+ internal fun toViewCoord(contentCoord: Float, zoom: Float, scroll: Int): Float {
+ return (contentCoord * zoom) - scroll
+ }
}
}
diff --git a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/SelectionModel.kt b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/SelectionModel.kt
index 0ca14c12..ee3f374 100644
--- a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/SelectionModel.kt
+++ b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/SelectionModel.kt
@@ -62,6 +62,23 @@
endBoundary.writeToParcel(dest, flags)
}
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null || other !is SelectionModel) return false
+
+ if (other.selection != selection) return false
+ if (other.startBoundary != startBoundary) return false
+ if (other.endBoundary != endBoundary) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = selection.hashCode()
+ result = 31 * result + startBoundary.hashCode()
+ result = 31 * result + endBoundary.hashCode()
+ return result
+ }
+
companion object {
/** Produces a [SelectionModel] from a single [PageSelection] on a single page */
// TODO(b/386398335) Add support for creating a SelectionModel from selections on 2 pages
@@ -135,6 +152,21 @@
dest.writeBoolean(isRtl)
}
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null || other !is UiSelectionBoundary) return false
+
+ if (other.location != this.location) return false
+ if (other.isRtl != this.isRtl) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = location.hashCode()
+ result = 31 * result + isRtl.hashCode()
+ return result
+ }
+
companion object CREATOR : Parcelable.Creator<UiSelectionBoundary> {
override fun createFromParcel(parcel: Parcel): UiSelectionBoundary {
return UiSelectionBoundary(parcel)
diff --git a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/SelectionStateManager.kt b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/SelectionStateManager.kt
index 72ec107..3f6cdd7 100644
--- a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/SelectionStateManager.kt
+++ b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/SelectionStateManager.kt
@@ -28,7 +28,10 @@
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
/** Owns and updates all mutable state related to content selection in [PdfView] */
@@ -39,8 +42,10 @@
initialSelection: SelectionModel? = null,
) {
/** The current [Selection] */
- var selectionModel: SelectionModel? = initialSelection
- @VisibleForTesting internal set
+ @VisibleForTesting val _selectionModel = MutableStateFlow<SelectionModel?>(initialSelection)
+
+ val selectionModel: StateFlow<SelectionModel?>
+ get() = _selectionModel
/** Replay at few values in case of an UI signal issued while [PdfView] is not collecting */
private val _selectionUiSignalBus = MutableSharedFlow<SelectionUiSignal>(replay = 3)
@@ -95,11 +100,12 @@
setSelectionJob = null
_selectionUiSignalBus.tryEmit(SelectionUiSignal.ToggleActionMode(show = false))
_selectionUiSignalBus.tryEmit(SelectionUiSignal.Invalidate)
- selectionModel = null
+ // tryEmit will always succeed for StateFlow
+ _selectionModel.tryEmit(null)
}
fun maybeShowActionMode() {
- if (selectionModel != null) {
+ if (selectionModel.value != null) {
_selectionUiSignalBus.tryEmit(SelectionUiSignal.ToggleActionMode(show = true))
}
}
@@ -124,7 +130,7 @@
}
private fun maybeHandleActionDown(location: PdfPoint, currentZoom: Float): Boolean {
- val currentSelection = selectionModel ?: return false
+ val currentSelection = selectionModel.value ?: return false
val start = currentSelection.startBoundary.location
val end = currentSelection.endBoundary.location
val touchTargetContentSize = handleTouchTargetSizePx / currentZoom
@@ -234,7 +240,9 @@
end.pagePoint
)
if (newSelection != null && newSelection.hasBounds) {
- selectionModel = SelectionModel.fromSinglePageSelection(newSelection)
+ _selectionModel.update {
+ SelectionModel.fromSinglePageSelection(newSelection)
+ }
_selectionUiSignalBus.tryEmit(SelectionUiSignal.Invalidate)
// Show the action mode if the user is not actively dragging the handles
if (draggingState == null) {
diff --git a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/fastscroll/FastScrollCalculator.kt b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/fastscroll/FastScrollCalculator.kt
new file mode 100644
index 0000000..5d42c15c
--- /dev/null
+++ b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/fastscroll/FastScrollCalculator.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2025 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.pdf.view.fastscroll
+
+import android.content.Context
+import androidx.pdf.R
+
+/** Class to compute scroll for [FastScroller] */
+internal class FastScrollCalculator(
+ private val context: Context,
+) {
+ internal val scrollerTopMarginDp = context.getDimensions(R.dimen.scroller_top_margin).toInt()
+ internal val scrollerBottomMarginDp =
+ context.getDimensions(R.dimen.scroller_bottom_margin).toInt()
+
+ /**
+ * Constrains the vertical scroll position to ensure it remains within the valid bounds of the
+ * view.
+ *
+ * @param scrollY The raw vertical scroll position in pixels.
+ * @param viewHeight The height of the view in pixels.
+ * @param thumbHeightPx Thumbnail height in pixels
+ * @return The constrained vertical scroll position.
+ */
+ fun constrainScrollPosition(scrollY: Float, viewHeight: Int, thumbHeightPx: Int): Int {
+ return scrollY
+ .toInt()
+ .coerceIn(
+ scrollerTopMarginDp.dpToPx(context),
+ viewHeight - (scrollerBottomMarginDp.dpToPx(context) + thumbHeightPx)
+ )
+ }
+
+ /**
+ * Calculates the vertical position of the fast scroll scrubber.
+ *
+ * This method determines the scrubber's position based on the current scroll state, zoom level,
+ * and the estimated height of the document content. It takes into account the visible height of
+ * the view and the scrollable range to accurately position the scrubber within the fast scroll
+ * track.
+ *
+ * @param scrollY The current vertical scroll position in pixels.
+ * @param zoom The current zoom level.
+ * @param viewHeight The height of the view in pixels.
+ * @param thumbHeightPx Height of the thumbnail in pixels
+ * @param estimatedFullHeight Estimated height of the document including all pages
+ * @return The calculated vertical position of the fast scroll scrubber, constrained to the
+ * valid bounds of the view.
+ */
+ fun computeThumbPosition(
+ scrollY: Int,
+ zoom: Float,
+ viewHeight: Int,
+ thumbHeightPx: Int,
+ estimatedFullHeight: Int
+ ): Int {
+ val scrollbarBottom = viewHeight - scrollerBottomMarginDp.dpToPx(context)
+ val scrollbarLength = scrollbarBottom - scrollerTopMarginDp.dpToPx(context)
+
+ val position = scrollY / zoom
+ val scrollRange = estimatedFullHeight - (viewHeight / zoom)
+ val tempThumbY =
+ (scrollbarLength * position / scrollRange).toInt() + scrollerTopMarginDp.dpToPx(context)
+ return constrainScrollPosition(tempThumbY.toFloat(), viewHeight, thumbHeightPx)
+ }
+
+ /**
+ * Calculates the content scroll position corresponding to a given fast scroll position.
+ *
+ * This method determines the vertical scroll position within the document content that
+ * corresponds to the provided fast scroll position. It takes into account the estimated height
+ * of the content, the view height, and the current zoom level to accurately map the fast scroll
+ * position to the content scroll position.
+ *
+ * @param fastScrollY The vertical position of the fast scroll scrubber in pixels.
+ * @param viewHeight The height of the view in pixels.
+ * @param zoom The current zoom level. Defaults to 1.0f (no zoom).
+ * @param estimatedFullHeight Estimated height of the document including all pages
+ * @return The calculated content scroll position in pixels.
+ */
+ fun computeViewScroll(
+ fastScrollY: Int,
+ viewHeight: Int,
+ zoom: Float,
+ estimatedFullHeight: Int
+ ): Int {
+ val scrollbarBottom = viewHeight - scrollerBottomMarginDp.dpToPx(context)
+ val scrollbarLength = scrollbarBottom - scrollerTopMarginDp.dpToPx(context)
+
+ val fraction = fastScrollY.toFloat() / scrollbarLength
+ val scrollRange = estimatedFullHeight - (viewHeight / zoom)
+ return (scrollRange * fraction * zoom).toInt()
+ }
+}
diff --git a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/fastscroll/FastScrollDrawer.kt b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/fastscroll/FastScrollDrawer.kt
new file mode 100644
index 0000000..5e0da0b
--- /dev/null
+++ b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/fastscroll/FastScrollDrawer.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2025 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.pdf.view.fastscroll
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.text.TextPaint
+import android.util.Range
+import androidx.core.content.ContextCompat
+import androidx.pdf.PdfDocument
+import androidx.pdf.R
+import androidx.pdf.view.PdfView
+import com.google.android.material.color.MaterialColors
+
+/**
+ * Draws the visual elements of the fast scroller.
+ *
+ * This class is responsible for rendering the fast scroller UI, including the thumb, drag handle
+ * (track), and page indicator. It uses drawables and text to provide a visual representation of the
+ * current scroll position and allow for quick navigation within a PDF document.
+ *
+ * @param context The UI context used for loading resources and respecting configuration changes
+ * @param pdfDocument The PDF document being displayed.
+ * @param thumbDrawable The drawable used for the fast scroller thumb.
+ * @param trackDrawable The drawable used for the fast scroller track (drag handle).
+ * @param pageIndicatorBackground The drawable used for the background of the page indicator.
+ */
+internal class FastScrollDrawer(
+ private val context: Context,
+ private val pdfDocument: PdfDocument,
+ private val thumbDrawable: Drawable,
+ private val trackDrawable: Drawable,
+ private val pageIndicatorBackground: Drawable,
+) {
+
+ private val thumbWidthDp = context.getDimensions(R.dimen.default_thumb_width).toInt()
+ private val thumbHeightDp = context.getDimensions(R.dimen.default_thumb_height).toInt()
+ private val trackWidthDp = context.getDimensions(R.dimen.default_track_width).toInt()
+ private val trackHeightDp = context.getDimensions(R.dimen.default_track_height).toInt()
+ private val scrubberEdgeOffsetDp = context.getDimensions(R.dimen.scrubber_edge_offset).toInt()
+ private val pageIndicatorHeightDp = context.getDimensions(R.dimen.page_indicator_height).toInt()
+ private val pageIndicatorRightMarginDp =
+ context.getDimensions(R.dimen.page_indicator_right_margin).toInt()
+ private val pageIndicatorTextOffsetDp =
+ context.getDimensions(R.dimen.page_indicator_text_offset).toInt()
+ private val pageIndicatorTextXOffsetDp =
+ context.getDimensions(R.dimen.page_indicator_text_offset_x).toInt()
+ private val pageIndicatorTextYOffsetDp =
+ context.getDimensions(R.dimen.page_indicator_text_offset_y).toInt()
+ private val pageIndicatorTextSize = context.getDimensions(R.dimen.page_indicator_text_size)
+
+ private val textPaint: TextPaint =
+ TextPaint().apply {
+ color =
+ MaterialColors.getColor(
+ context,
+ com.google.android.material.R.attr.colorOnSurface,
+ Color.BLACK
+ )
+ textSize = pageIndicatorTextSize.dpToPx(context)
+ textAlign = Paint.Align.CENTER
+ }
+
+ private val thumbShadowDrawable: Drawable? =
+ ContextCompat.getDrawable(context, R.drawable.drag_indicator_shadow)
+
+ internal val thumbWidthPx = thumbWidthDp.dpToPx(context)
+ internal val thumbHeightPx = thumbHeightDp.dpToPx(context)
+
+ internal var alpha: Int = VISIBLE_ALPHA
+ set(value) {
+ thumbDrawable.alpha = value
+ trackDrawable.alpha = value
+ pageIndicatorBackground.alpha = value
+ thumbShadowDrawable?.alpha = value
+ textPaint.alpha = value
+ }
+
+ /**
+ * Draws the fast scroller on the canvas.
+ *
+ * This method performs the actual drawing of the fast scroll scrubber, including the thumb, the
+ * drag handle (track), and the page indicator. It calculates the positions of these elements
+ * based on the provided parameters and then uses the provided drawables to render them on the
+ * canvas.
+ *
+ * @param canvas The canvas on which to draw the scrubber.
+ * @param zoom The current zoom level.
+ * @param scrollY The vertical position of the scrubber in pixels.
+ * @param visibleAreaPx The rectangular area of the view that is currently visible.
+ * @param visiblePages The range of pages that are currently visible.
+ */
+ fun draw(
+ canvas: Canvas,
+ zoom: Float,
+ scrollY: Int,
+ visibleAreaPx: Rect,
+ visiblePages: Range<Int>
+ ) {
+ val thumbLeftPx =
+ (PdfView.toViewCoord(visibleAreaPx.right.toFloat(), zoom, scroll = 0) -
+ thumbWidthDp.dpToPx(context))
+ .toInt() + scrubberEdgeOffsetDp.dpToPx(context)
+ val thumbTopPx =
+ (scrollY + PdfView.toViewCoord(visibleAreaPx.top.toFloat(), zoom, scroll = 0)).toInt()
+ val thumbBottomPx = thumbTopPx + thumbHeightDp.dpToPx(context)
+ val thumbRightPx =
+ PdfView.toViewCoord(visibleAreaPx.right.toFloat(), zoom, scroll = 0).toInt() +
+ scrubberEdgeOffsetDp.dpToPx(context)
+
+ thumbShadowDrawable?.setBounds(
+ thumbLeftPx - SHADOW_OFFSET_FROM_SCRUBBER_DP.dpToPx(context),
+ thumbTopPx - SHADOW_OFFSET_FROM_SCRUBBER_DP.dpToPx(context),
+ thumbRightPx + SHADOW_OFFSET_FROM_SCRUBBER_DP.dpToPx(context),
+ thumbBottomPx + SHADOW_OFFSET_FROM_SCRUBBER_DP.dpToPx(context)
+ )
+ thumbShadowDrawable?.draw(canvas)
+
+ thumbDrawable.setBounds(thumbLeftPx, thumbTopPx, thumbRightPx, thumbBottomPx)
+ thumbDrawable.draw(canvas)
+
+ drawDragHandle(canvas, thumbRightPx, thumbTopPx)
+ drawPageIndicator(canvas, thumbLeftPx, thumbTopPx, visiblePages)
+ }
+
+ private fun drawPageIndicator(
+ canvas: Canvas,
+ thumbLeftPx: Int,
+ thumbTopPx: Int,
+ visiblePages: Range<Int>
+ ) {
+ val label = generateLabel(visiblePages)
+ val pageIndicatorWidthPx = (label.length + (2 * pageIndicatorTextOffsetDp)).dpToPx(context)
+
+ val indicatorLeftPx =
+ thumbLeftPx - pageIndicatorWidthPx - pageIndicatorRightMarginDp.dpToPx(context)
+ val indicatorTopPx =
+ thumbTopPx +
+ ((thumbHeightDp.dpToPx(context) - pageIndicatorHeightDp.dpToPx(context)) / 2)
+ pageIndicatorBackground.setBounds(
+ /* left= */ indicatorLeftPx,
+ /* top= */ indicatorTopPx,
+ /* right= */ indicatorLeftPx + pageIndicatorWidthPx,
+ /* bottom= */ indicatorTopPx + pageIndicatorHeightDp.dpToPx(context)
+ )
+ pageIndicatorBackground.draw(canvas)
+
+ val xPos =
+ indicatorLeftPx +
+ ((pageIndicatorWidthPx - label.length) / 2) +
+ pageIndicatorTextXOffsetDp
+ val yPos = indicatorTopPx + pageIndicatorTextYOffsetDp
+ canvas.drawText(label, xPos.toFloat(), yPos.toFloat(), textPaint)
+ }
+
+ private fun drawDragHandle(canvas: Canvas, thumbRight: Int, thumbTop: Int) {
+ val thumbCenterX = thumbRight - thumbWidthDp.dpToPx(context) / 2
+ val thumbCenterY = thumbTop + thumbHeightDp.dpToPx(context) / 2
+
+ // Calculate the top-left corner of the track to center it
+ val trackLeft = thumbCenterX - trackWidthDp.dpToPx(context) / 2
+ val trackTop = thumbCenterY - trackHeightDp.dpToPx(context) / 2
+
+ trackDrawable.setBounds(
+ trackLeft,
+ trackTop,
+ trackLeft + trackWidthDp.dpToPx(context),
+ trackTop + trackHeightDp.dpToPx(context)
+ )
+ trackDrawable.draw(canvas)
+ }
+
+ private fun generateLabel(range: Range<Int>): String {
+ val res = context.resources
+
+ return if (range.length() == 0) {
+ res.getString(R.string.label_page_single, range.upper, pdfDocument.pageCount)
+ } else if (range.length() == 1) {
+ res.getString(R.string.label_page_single, range.lower + 1, pdfDocument.pageCount)
+ } else {
+ res.getString(
+ R.string.label_page_range,
+ range.lower + 1,
+ range.upper + 1,
+ pdfDocument.pageCount
+ )
+ }
+ }
+
+ companion object {
+ private const val SHADOW_OFFSET_FROM_SCRUBBER_DP = 2
+ internal const val VISIBLE_ALPHA = 255
+ internal const val GONE_ALPHA = 0
+ }
+}
diff --git a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/fastscroll/FastScrollGestureDetector.kt b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/fastscroll/FastScrollGestureDetector.kt
new file mode 100644
index 0000000..5eb5027
--- /dev/null
+++ b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/fastscroll/FastScrollGestureDetector.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2025 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.pdf.view.fastscroll
+
+import android.view.MotionEvent
+
+/**
+ * Handles touch events related to the fast scroll functionality.
+ *
+ * This class is responsible for detecting and processing touch events that interact with the fast
+ * scroll scrubber. It determines if a touch event is within the bounds of the scrubber and notifies
+ * a [FastScrollGestureHandler] when a fast scroll gesture (dragging the scrubber) is detected.
+ *
+ * @param fastScoller The [FastScroller] instance associated with this handler.
+ * @param gestureHandler The [FastScrollGestureHandler] that will be notified of fast scroll events.
+ */
+internal class FastScrollGestureDetector(
+ private val fastScoller: FastScroller,
+ private val gestureHandler: FastScrollGestureHandler
+) {
+ private var trackingFastScrollGesture: Boolean = false
+
+ /**
+ * Handles touch events and detects fast scroll gestures.
+ *
+ * This method processes the provided [MotionEvent] and determines if it represents a fast
+ * scroll interaction. If a fast scroll gesture is detected (e.g., the user starts dragging the
+ * fast scroll scrubber), the [gestureHandler] is notified.
+ *
+ * @param event The [MotionEvent] to handle.
+ * @param viewWidth Width of the view in pixels.
+ * @return True if the event was handled as a fast scroll gesture, false otherwise.
+ */
+ fun handleEvent(event: MotionEvent, viewWidth: Int): Boolean {
+ if (event.actionMasked == MotionEvent.ACTION_DOWN) {
+ if (isPointWithinVisibleBounds(event, viewWidth)) {
+ trackingFastScrollGesture = true
+ return true
+ }
+ }
+
+ if (trackingFastScrollGesture) {
+ if (event.actionMasked == MotionEvent.ACTION_MOVE) {
+ gestureHandler.onFastScrollDetected(event.y)
+ } else if (event.actionMasked == MotionEvent.ACTION_UP) {
+ trackingFastScrollGesture = false
+ }
+ return true
+ }
+
+ return false
+ }
+
+ /**
+ * Checks if a touch event is within the visible bounds of the fast scroll scrubber.
+ *
+ * @param event The [MotionEvent] to check.
+ * @param viewWidth Width of the view in pixels
+ * @return True if the touch event is within the bounds of the scrubber, false otherwise.
+ */
+ private fun isPointWithinVisibleBounds(event: MotionEvent, viewWidth: Int): Boolean {
+ return event.x > (viewWidth - fastScoller.fastScrollDrawer.thumbWidthPx)
+ // Deliberately ignore (x < getWidth() - scrollbarMarginRight) to make it easier
+ // to grab it.
+ &&
+ event.y >= fastScoller.fastScrollY &&
+ event.y <= fastScoller.fastScrollY + fastScoller.fastScrollDrawer.thumbHeightPx
+ }
+
+ /** An interface for receiving notifications about fast scroll gestures. */
+ interface FastScrollGestureHandler {
+ /**
+ * Called when a fast scroll gesture is detected.
+ *
+ * @param scrollY The vertical scroll position in pixels indicated by the fast scroll
+ * gesture.
+ */
+ fun onFastScrollDetected(scrollY: Float)
+ }
+}
diff --git a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/fastscroll/FastScroller.kt b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/fastscroll/FastScroller.kt
new file mode 100644
index 0000000..b568e5c
--- /dev/null
+++ b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/fastscroll/FastScroller.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2025 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.pdf.view.fastscroll
+
+import android.animation.ValueAnimator
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.util.Range
+
+/**
+ * Manages and draws the fast scroller UI element.
+ *
+ * This class is responsible for controlling the behavior and rendering of the fast scroller, which
+ * provides a visual indicator of the current scroll position and allows for quick navigation within
+ * a scrollable view. It collaborates with a [FastScrollDrawer] to handle the drawing and a
+ * [FastScrollCalculator] to perform scroll calculations.
+ *
+ * @param fastScrollDrawer The [FastScrollDrawer] that handles the actual drawing of the fast
+ * scroller UI.
+ * @param scrollCalculator The [FastScrollCalculator] that performs scroll-related calculations.
+ */
+internal class FastScroller(
+ val fastScrollDrawer: FastScrollDrawer,
+ private val scrollCalculator: FastScrollCalculator
+) {
+ internal var fastScrollY: Int = 0
+
+ // This is used to optimize performance. If the scroll position has already been updated
+ // by another method the calculation is skipped.
+ private var lastScrollY: Int = 0
+
+ private var hideValueAnimator: ValueAnimator? = null
+
+ /**
+ * Draws the fast scroller on the canvas.
+ *
+ * This method handles the drawing of the fast scroller, which provides a visual indication of
+ * the current scroll position and allows for quick navigation within a scrollable view. It
+ * calculates the vertical position of the scroller and then delegates the actual drawing to the
+ * `renderer`.
+ *
+ * @param canvas The canvas on which to draw the scroller.
+ * @param scrollY The raw vertical scroll position in pixels.
+ * @param zoom The current zoom level.
+ * @param viewHeight The height of the view in pixels.
+ * @param visibleArea The rectangular area of the view that is currently visible.
+ * @param visiblePages The range of pages that are currently visible.
+ */
+ fun drawScroller(
+ canvas: Canvas,
+ scrollY: Int,
+ zoom: Float,
+ viewHeight: Int,
+ visibleArea: Rect,
+ visiblePages: Range<Int>,
+ estimatedFullHeight: Int
+ ) {
+ if (scrollY != lastScrollY) {
+ fastScrollY =
+ scrollCalculator.computeThumbPosition(
+ scrollY,
+ zoom,
+ viewHeight,
+ fastScrollDrawer.thumbHeightPx,
+ estimatedFullHeight
+ )
+ lastScrollY = scrollY
+ }
+
+ fastScrollDrawer.draw(canvas, zoom, fastScrollY, visibleArea, visiblePages)
+ }
+
+ /**
+ * Calculates the content scroll position based on the fast scroller position.
+ *
+ * This method determines the new vertical scroll position for the displayed content based on
+ * the current position of the fast scroller. It uses the `ScrollCalculator` to constrain the
+ * fast scroller position and then compute the corresponding content scroll position, taking
+ * into account the zoom level.
+ *
+ * @param scrollY The raw vertical scroll position of the fast scroller in pixels.
+ * @param zoom The current zoom level.
+ * @param viewHeight The height of the view in pixels.
+ * @return The calculated content scroll position in pixels.
+ */
+ fun viewScrollPositionFromFastScroller(
+ scrollY: Float,
+ zoom: Float,
+ viewHeight: Int,
+ estimatedFullHeight: Int
+ ): Int {
+ fastScrollY =
+ scrollCalculator.constrainScrollPosition(
+ scrollY,
+ viewHeight,
+ fastScrollDrawer.thumbHeightPx
+ )
+
+ return scrollCalculator.computeViewScroll(
+ fastScrollY,
+ viewHeight,
+ zoom,
+ estimatedFullHeight
+ )
+ }
+
+ fun show(onAnimationUpdate: () -> Unit) {
+ hideValueAnimator?.cancel()
+ fastScrollDrawer.alpha = FastScrollDrawer.VISIBLE_ALPHA
+ animate(onAnimationUpdate)
+ }
+
+ private fun animate(onAnimationUpdate: () -> Unit) {
+ hideValueAnimator =
+ ValueAnimator.ofInt(FastScrollDrawer.VISIBLE_ALPHA, FastScrollDrawer.GONE_ALPHA).apply {
+ startDelay = HIDE_DELAY_MS
+ duration = HIDE_ANIMATION_DURATION_MILLIS
+ addUpdateListener { animation ->
+ fastScrollDrawer.alpha = animation.animatedValue as Int
+ onAnimationUpdate()
+ }
+ start()
+ }
+ }
+
+ companion object {
+ private const val HIDE_ANIMATION_DURATION_MILLIS = 200L
+ private const val HIDE_DELAY_MS = 1300L
+ }
+}
diff --git a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/fastscroll/extensions.kt b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/fastscroll/extensions.kt
new file mode 100644
index 0000000..1cb727c
--- /dev/null
+++ b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/fastscroll/extensions.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2025 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.pdf.view.fastscroll
+
+import android.content.Context
+import android.util.Range
+
+/**
+ * Converts density-independent pixels (dp) to pixels (px) for an integer value.
+ *
+ * @param context The application context used to access resources.
+ * @return The equivalent pixel value as an integer.
+ */
+internal fun Int.dpToPx(context: Context): Int =
+ (this * context.resources.displayMetrics.density).toInt()
+
+/**
+ * Converts density-independent pixels (dp) to pixels (px) for a float value.
+ *
+ * @param context The application context used to access resources.
+ * @return The equivalent pixel value as a float.
+ */
+internal fun Float.dpToPx(context: Context): Float = this * context.resources.displayMetrics.density
+
+/**
+ * Calculates the length of an integer range.
+ *
+ * @return The length of the range (inclusive).
+ */
+internal fun Range<Int>.length(): Int = 1 + this.upper - this.lower
+
+/**
+ * Retrieves a dimension value for a particular resource ID.
+ *
+ * @param id The resource ID of the dimension to retrieve.
+ * @return The dimension resource value. This is a float.
+ */
+internal fun Context.getDimensions(id: Int): Float = this.resources.getDimension(id)
diff --git a/pdf/pdf-viewer/src/main/res/drawable/drag_indicator_shadow.xml b/pdf/pdf-viewer/src/main/res/drawable/drag_indicator_shadow.xml
new file mode 100644
index 0000000..f72b4d3
--- /dev/null
+++ b/pdf/pdf-viewer/src/main/res/drawable/drag_indicator_shadow.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2025 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+ <gradient
+ android:type="radial"
+ android:gradientRadius="53%p"
+ android:startColor="#80000000"
+ android:endColor="@android:color/transparent" />
+</shape>
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/res/values-af/strings.xml b/pdf/pdf-viewer/src/main/res/values-af/strings.xml
index e17d86d..faf7bd3 100644
--- a/pdf/pdf-viewer/src/main/res/values-af/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-af/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Bladsy is vir die PDF-dokument gebreek"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Onvoldoende data om die PDF-dokument te verwerk"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Kan nie PDF-lêer oopmaak nie"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-am/strings.xml b/pdf/pdf-viewer/src/main/res/values-am/strings.xml
index 90f9b15..2c973fa 100644
--- a/pdf/pdf-viewer/src/main/res/values-am/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-am/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"ለPDF ሰነዱ ገፅ ተበላሽቷል"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF ሰነዱን ለማሰናዳት በቂ ያልሆነ ውሂብ"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF ፋይል መክፈት አይቻልም"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ar/strings.xml b/pdf/pdf-viewer/src/main/res/values-ar/strings.xml
index 7531dc0..1958e32 100644
--- a/pdf/pdf-viewer/src/main/res/values-ar/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ar/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"تعذّر تحميل صفحة من مستند PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"البيانات غير كافية لمعالجة مستند PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"يتعذّر فتح ملف PDF"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-as/strings.xml b/pdf/pdf-viewer/src/main/res/values-as/strings.xml
index ed565c3..be66544 100644
--- a/pdf/pdf-viewer/src/main/res/values-as/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-as/strings.xml
@@ -60,4 +60,5 @@
<string name="page_broken" msgid="2968770793669433462">"PDF নথিৰ বাবে পৃষ্ঠাখন বিসংগতিপূৰ্ণ"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF নথিখন প্ৰক্ৰিয়াকৰণ কৰিবলৈ অপৰ্যাপ্ত ডেটা"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF ফাইল খুলিব নোৱাৰি"</string>
+ <string name="clipboard_label" msgid="8943155331324944981">"PDFৰ পৰা সমল প্ৰতিলিপি কৰা হৈছে"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-az/strings.xml b/pdf/pdf-viewer/src/main/res/values-az/strings.xml
index 8c60f13..4f4045e 100644
--- a/pdf/pdf-viewer/src/main/res/values-az/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-az/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF sənədi üçün səhifədə xəta var"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF sənədini emal etmək üçün kifayət qədər data yoxdur"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF faylını açmaq olmur"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-b+sr+Latn/strings.xml b/pdf/pdf-viewer/src/main/res/values-b+sr+Latn/strings.xml
index f43a280..156e778 100644
--- a/pdf/pdf-viewer/src/main/res/values-b+sr+Latn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-b+sr+Latn/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Neispravna stranica za PDF dokument"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Nedovoljno podataka za obradu PDF dokumenta"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Otvaranje PDF fajla nije uspelo"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-be/strings.xml b/pdf/pdf-viewer/src/main/res/values-be/strings.xml
index b815f0c..e1a2a77 100644
--- a/pdf/pdf-viewer/src/main/res/values-be/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-be/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Старонка дакумента PDF пашкоджана"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Не хапае даных для апрацоўкі дакумента PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Не ўдаецца адкрыць файл PDF"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-bg/strings.xml b/pdf/pdf-viewer/src/main/res/values-bg/strings.xml
index eae25dd..ba05b00 100644
--- a/pdf/pdf-viewer/src/main/res/values-bg/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-bg/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"поле за парола"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"неправилна парола"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Грешна парола. Опитайте отново."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"страница <xliff:g id="PAGE">%1$d</xliff:g> от <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"Процент на промяна на мащаба: <xliff:g id="FIRST">%1$d</xliff:g>"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Към страница <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Телефон: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"начало на избраното"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"край на избраното"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g> – <xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g> – <xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"страници <xliff:g id="FIRST">%1$d</xliff:g> до <xliff:g id="LAST">%2$d</xliff:g> от <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Изображение: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Търсете във файла"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Невалидна страница в PDF документа"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Няма достатъчно данни за обработването на PDF документа"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF файлът не може да се отвори"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-bn/strings.xml b/pdf/pdf-viewer/src/main/res/values-bn/strings.xml
index 12df717..e617105 100644
--- a/pdf/pdf-viewer/src/main/res/values-bn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-bn/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"পিডিএফ ডকুমেন্টের ক্ষেত্রে পৃষ্ঠা ভেঙে গেছে"</string>
<string name="needs_more_data" msgid="3520133467908240802">"পিডিএফ ডকুমেন্ট প্রসেস করার জন্য যথেষ্ট ডেটা নেই"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF ফাইল খোলা যাচ্ছে না"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-bs/strings.xml b/pdf/pdf-viewer/src/main/res/values-bs/strings.xml
index aded442..a90e957 100644
--- a/pdf/pdf-viewer/src/main/res/values-bs/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-bs/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"polje za lozinku"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"pogrešna lozinka"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Pogrešna lozinka Ponovni pokušaj."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"<xliff:g id="PAGE">%1$d</xliff:g>. stranica od <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"zumiranje <xliff:g id="FIRST">%1$d</xliff:g> posto"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Odlazak na <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>. stranicu"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Stranica je prelomljena za PDF dokument"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Nema dovoljno podataka za obradu PDF dokumenta"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Nije moguće otvoriti PDF fajl"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ca/strings.xml b/pdf/pdf-viewer/src/main/res/values-ca/strings.xml
index c2cbf68..5f52251 100644
--- a/pdf/pdf-viewer/src/main/res/values-ca/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ca/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"camp de contrasenya"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"contrasenya incorrecta"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Contrasenya incorrecta. Torna-ho a provar."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"pàgina <xliff:g id="PAGE">%1$d</xliff:g> de <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> per cent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Ves a la pàgina <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"La pàgina no funciona per al document PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Les dades són insuficients per processar el document PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"No es pot obrir el fitxer PDF"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-cs/strings.xml b/pdf/pdf-viewer/src/main/res/values-cs/strings.xml
index cb13104..d521d11 100644
--- a/pdf/pdf-viewer/src/main/res/values-cs/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-cs/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Dokument PDF obsahuje poškozenou stránku"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Nedostatek dat ke zpracování dokumentu PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Soubor PDF se nepodařilo otevřít"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-da/strings.xml b/pdf/pdf-viewer/src/main/res/values-da/strings.xml
index cd5475f..012a1a5 100644
--- a/pdf/pdf-viewer/src/main/res/values-da/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-da/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"felt til adgangskode"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"adgangskoden er forkert"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Forkert adgangskode. Prøv igen."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"side <xliff:g id="PAGE">%1$d</xliff:g> af <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> %%"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Gå til side <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Telefonnummer: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"start på markering"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"slut på markering"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"side <xliff:g id="FIRST">%1$d</xliff:g> til <xliff:g id="LAST">%2$d</xliff:g> af <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Billede: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Søg i fil"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Siden er ødelagt for PDF-dokumentet"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Der er ikke nok data til at behandle PDF-dokumentet"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF-filen kan ikke åbnes"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-de/strings.xml b/pdf/pdf-viewer/src/main/res/values-de/strings.xml
index 48345c4..3732805 100644
--- a/pdf/pdf-viewer/src/main/res/values-de/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-de/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"Passwortfeld"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"Falsches Passwort"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Falsches Passwort. Versuch es nochmal."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"Seite <xliff:g id="PAGE">%1$d</xliff:g> von <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"Zoom: <xliff:g id="FIRST">%1$d</xliff:g> %%"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Gehe zu Seite <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Telefonnummer: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"Beginn der Auswahl"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"Ende der Auswahl"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"Seiten <xliff:g id="FIRST">%1$d</xliff:g> bis <xliff:g id="LAST">%2$d</xliff:g> von <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Bild: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"In Datei suchen"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Seite für PDF-Dokument ist fehlerhaft"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Keine ausreichenden Daten, um das PDF-Dokument zu verarbeiten"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF‑Datei kann nicht geöffnet werden"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-el/strings.xml b/pdf/pdf-viewer/src/main/res/values-el/strings.xml
index d7e9e18..bfef9c9 100644
--- a/pdf/pdf-viewer/src/main/res/values-el/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-el/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Δεν ήταν δυνατή η φόρτωση του εγγράφου PDF από τη σελίδα"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Μη επαρκή δεδομένα για την επεξεργασία του εγγράφου PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Δεν είναι δυνατό το άνοιγμα του αρχείου PDF"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-en-rAU/strings.xml b/pdf/pdf-viewer/src/main/res/values-en-rAU/strings.xml
index 18652f6..37aa01f 100644
--- a/pdf/pdf-viewer/src/main/res/values-en-rAU/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-en-rAU/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"password field"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"password incorrect"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Wrong password. Try again."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"page <xliff:g id="PAGE">%1$d</xliff:g> of <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> per cent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Go to page <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Phone: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"selection start"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"selection end"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"pages <xliff:g id="FIRST">%1$d</xliff:g> to <xliff:g id="LAST">%2$d</xliff:g> of <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Image: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Find in file"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Page broken for the PDF document"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Insufficient data for processing the PDF document"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Can\'t open PDF file"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-en-rCA/strings.xml b/pdf/pdf-viewer/src/main/res/values-en-rCA/strings.xml
index ab2748f..d0d51124 100644
--- a/pdf/pdf-viewer/src/main/res/values-en-rCA/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-en-rCA/strings.xml
@@ -60,4 +60,5 @@
<string name="page_broken" msgid="2968770793669433462">"Page broken for the PDF document"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Insufficient data for processing the PDF document"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Can\'t open PDF file"</string>
+ <string name="clipboard_label" msgid="8943155331324944981">"Content copied from PDF"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-en-rGB/strings.xml b/pdf/pdf-viewer/src/main/res/values-en-rGB/strings.xml
index 18652f6..37aa01f 100644
--- a/pdf/pdf-viewer/src/main/res/values-en-rGB/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-en-rGB/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"password field"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"password incorrect"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Wrong password. Try again."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"page <xliff:g id="PAGE">%1$d</xliff:g> of <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> per cent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Go to page <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Phone: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"selection start"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"selection end"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"pages <xliff:g id="FIRST">%1$d</xliff:g> to <xliff:g id="LAST">%2$d</xliff:g> of <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Image: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Find in file"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Page broken for the PDF document"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Insufficient data for processing the PDF document"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Can\'t open PDF file"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-en-rIN/strings.xml b/pdf/pdf-viewer/src/main/res/values-en-rIN/strings.xml
index 18652f6..37aa01f 100644
--- a/pdf/pdf-viewer/src/main/res/values-en-rIN/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-en-rIN/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"password field"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"password incorrect"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Wrong password. Try again."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"page <xliff:g id="PAGE">%1$d</xliff:g> of <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> per cent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Go to page <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Phone: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"selection start"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"selection end"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"pages <xliff:g id="FIRST">%1$d</xliff:g> to <xliff:g id="LAST">%2$d</xliff:g> of <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Image: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Find in file"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Page broken for the PDF document"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Insufficient data for processing the PDF document"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Can\'t open PDF file"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-es-rUS/strings.xml b/pdf/pdf-viewer/src/main/res/values-es-rUS/strings.xml
index 262b082..38a7d86f 100644
--- a/pdf/pdf-viewer/src/main/res/values-es-rUS/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-es-rUS/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"La página no funciona para el documento PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"No hay datos suficientes para procesar el documento PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"No se puede abrir el archivo PDF"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-es/strings.xml b/pdf/pdf-viewer/src/main/res/values-es/strings.xml
index 80c08b9..54e909e 100644
--- a/pdf/pdf-viewer/src/main/res/values-es/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-es/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"campo de contraseña"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"contraseña incorrecta"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Contraseña incorrecta. Inténtalo de nuevo."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"página <xliff:g id="PAGE">%1$d</xliff:g> de <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> por ciento"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Ir a la página <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Teléfono: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"inicio de la selección"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"final de la selección"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"páginas <xliff:g id="FIRST">%1$d</xliff:g> a <xliff:g id="LAST">%2$d</xliff:g> de <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Imagen: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Buscar en el archivo"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"La página no funciona para el documento PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Datos insuficientes para procesar el documento PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"No se puede abrir el archivo PDF"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-et/strings.xml b/pdf/pdf-viewer/src/main/res/values-et/strings.xml
index c66d8c7..7bdf417 100644
--- a/pdf/pdf-viewer/src/main/res/values-et/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-et/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"parooliväli"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"parool on vale"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Vale parool. Proovige uuesti."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"lk <xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"suum <xliff:g id="FIRST">%1$d</xliff:g> protsenti"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Mine lehele <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Telefon: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"valiku algus"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"valiku lõpp"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"lk <xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>-st"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Pilt: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Otsige failist"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Rikutud leht PDF-dokumendis"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF-dokumendi töötlemiseks pole piisavalt andmeid"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF-faili ei saa avada"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-eu/strings.xml b/pdf/pdf-viewer/src/main/res/values-eu/strings.xml
index f8f77c2..48c22ae 100644
--- a/pdf/pdf-viewer/src/main/res/values-eu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-eu/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"pasahitzaren eremua"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"pasahitza okerra da"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Pasahitza ez da zuzena. Saiatu berriro."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"<xliff:g id="TOTAL">%2$d</xliff:g> orritatik <xliff:g id="PAGE">%1$d</xliff:g>garrena"</string>
<string name="desc_zoom" msgid="7318480946145947242">"zooma ehuneko <xliff:g id="FIRST">%1$d</xliff:g>"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Joan <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>. orrira"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Telefono-zenbakia: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"hautapenaren hasiera"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"hautapenaren amaiera"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="FIRST">%1$d</xliff:g> eta <xliff:g id="LAST">%2$d</xliff:g> bitarteko orriak, guztira <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Irudia: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Bilatu fitxategia"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF dokumentuaren orria hondatuta dago"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Ez dago behar adina daturik PDF dokumentua prozesatzeko"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Ezin da ireki PDF fitxategia"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-fa/strings.xml b/pdf/pdf-viewer/src/main/res/values-fa/strings.xml
index bd85b34..c741e2c 100644
--- a/pdf/pdf-viewer/src/main/res/values-fa/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fa/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"صفحه سند PDF خراب است"</string>
<string name="needs_more_data" msgid="3520133467908240802">"دادهها برای پردازش سند PDF کافی نیست"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"فایل PDF باز نشد"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-fi/strings.xml b/pdf/pdf-viewer/src/main/res/values-fi/strings.xml
index 7808d36..53cc957 100644
--- a/pdf/pdf-viewer/src/main/res/values-fi/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fi/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF-dokumenttiin liittyvä sivu on rikki"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Riittämätön data PDF-dokumentin käsittelyyn"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF-tiedostoa ei voi avata"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-fr-rCA/strings.xml b/pdf/pdf-viewer/src/main/res/values-fr-rCA/strings.xml
index f5fffbd..ccd0b38 100644
--- a/pdf/pdf-viewer/src/main/res/values-fr-rCA/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fr-rCA/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"champ du mot de passe"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"mot de passe incorrect"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Mot de passe incorrect. Réessayez."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"page <xliff:g id="PAGE">%1$d</xliff:g> sur <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> pour cent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Accéder à la page <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Numéro de téléphone : <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"début de la sélection"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"fin de la sélection"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"pages <xliff:g id="FIRST">%1$d</xliff:g> à <xliff:g id="LAST">%2$d</xliff:g> sur <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Image : <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Trouver dans fichier"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Page brisée pour le document PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Données insuffisantes pour le traitement du document PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Impossible d\'ouvrir le fichier PDF"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-fr/strings.xml b/pdf/pdf-viewer/src/main/res/values-fr/strings.xml
index 5f42dc2..05c1ad3 100644
--- a/pdf/pdf-viewer/src/main/res/values-fr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fr/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"champ de mot de passe"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"mot de passe incorrect"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Mot de passe incorrect. Réessayez."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"page <xliff:g id="PAGE">%1$d</xliff:g> sur <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"zoom : <xliff:g id="FIRST">%1$d</xliff:g> pour cent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Accéder à la page <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Téléphone : <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"début de la sélection"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"fin de la sélection"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"pages <xliff:g id="FIRST">%1$d</xliff:g> à <xliff:g id="LAST">%2$d</xliff:g> sur <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Image : <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Rechercher dans fichier"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Page non fonctionnelle pour le document PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Données insuffisantes pour le traitement du document PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Impossible d\'ouvrir le fichier PDF"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-gl/strings.xml b/pdf/pdf-viewer/src/main/res/values-gl/strings.xml
index 17acfbc..b85aea9 100644
--- a/pdf/pdf-viewer/src/main/res/values-gl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-gl/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"campo do contrasinal"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"contrasinal incorrecto"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"O contrasinal é incorrecto. Téntao de novo."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"páxina <xliff:g id="PAGE">%1$d</xliff:g> de <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"zoom ao <xliff:g id="FIRST">%1$d</xliff:g> por cento"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Vai á páxina <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Teléfono: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"inicio da selección"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"fin da selección"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"páxinas da <xliff:g id="FIRST">%1$d</xliff:g> á <xliff:g id="LAST">%2$d</xliff:g> dun total de <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Imaxe: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Busca no ficheiro"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Non funciona a páxina para o documento PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Os datos non son suficientes para procesar o documento PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Non se puido abrir o PDF"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-gu/strings.xml b/pdf/pdf-viewer/src/main/res/values-gu/strings.xml
index 5d0c8c5..a06a2b3 100644
--- a/pdf/pdf-viewer/src/main/res/values-gu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-gu/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF દસ્તાવેજ માટે પેજ લોડ થઈ રહ્યું નથી"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF દસ્તાવેજ પર પ્રક્રિયા કરવા માટે પર્યાપ્ત ડેટા નથી"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF ફાઇલ ખોલી શકાતી નથી"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-hi/strings.xml b/pdf/pdf-viewer/src/main/res/values-hi/strings.xml
index 215e57a..038f5ad 100644
--- a/pdf/pdf-viewer/src/main/res/values-hi/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hi/strings.xml
@@ -60,4 +60,5 @@
<string name="page_broken" msgid="2968770793669433462">"PDF दस्तावेज़ के लिए पेज लोड नहीं हो रहा है"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF दस्तावेज़ को प्रोसेस करने के लिए, ज़रूरत के मुताबिक डेटा नहीं है"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF फ़ाइल नहीं खोली जा सकी"</string>
+ <string name="clipboard_label" msgid="8943155331324944981">"PDF से कॉपी किया गया कॉन्टेंट"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-hr/strings.xml b/pdf/pdf-viewer/src/main/res/values-hr/strings.xml
index a1608c4..0b612cc 100644
--- a/pdf/pdf-viewer/src/main/res/values-hr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hr/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"polje zaporke"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"zaporka nije točna"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Pogrešna zaporka. Pokušajte ponovo."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"stranica <xliff:g id="PAGE">%1$d</xliff:g> od <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"zumiranje <xliff:g id="FIRST">%1$d</xliff:g> posto"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Idite na stranicu <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Telefonski broj: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"početak odabira"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"završetak odabira"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g> – <xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g> – <xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"stranice od <xliff:g id="FIRST">%1$d</xliff:g> do <xliff:g id="LAST">%2$d</xliff:g> od ukupno <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Slika: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Pronađi u datoteci"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Stranica je raščlanjena za PDF dokument"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Nema dovoljno podataka za obradu PDF dokumenta"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF datoteka ne može se otvoriti"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-hu/strings.xml b/pdf/pdf-viewer/src/main/res/values-hu/strings.xml
index c85e515..39d0f1e 100644
--- a/pdf/pdf-viewer/src/main/res/values-hu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hu/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"jelszómező"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"helytelen jelszó"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Helytelen jelszó. Próbálkozzon újra."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="TOTAL">%2$d</xliff:g>/<xliff:g id="PAGE">%1$d</xliff:g>."</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="TOTAL">%2$d</xliff:g> / <xliff:g id="PAGE">%1$d</xliff:g>."</string>
<string name="desc_page_single" msgid="8459583146661044094">"<xliff:g id="TOTAL">%2$d</xliff:g>/<xliff:g id="PAGE">%1$d</xliff:g>. oldal"</string>
<string name="desc_zoom" msgid="7318480946145947242">"<xliff:g id="FIRST">%1$d</xliff:g> százalékos nagyítás"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Ugrás erre az oldalra: <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Telefon: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"kijelölés kezdete"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"kijelölés vége"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="TOTAL">%3$d</xliff:g>/<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g>."</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="TOTAL">%3$d</xliff:g> / <xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g>."</string>
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="TOTAL">%3$d</xliff:g>/<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g>. oldal"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Kép: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Keresés a fájlban"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Az oldal nem tölt be a PDF-dokumentumban"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Nem áll rendelkezésre elegendő adat a PDF-dokumentum feldolgozásához"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Nem sikerült megnyitni a PDF-fájlt"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-hy/strings.xml b/pdf/pdf-viewer/src/main/res/values-hy/strings.xml
index eca3c77..2b64b83 100644
--- a/pdf/pdf-viewer/src/main/res/values-hy/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hy/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"գաղտնաբառի դաշտ"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"գաղտնաբառը սխալ է"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Գաղտնաբառը սխալ է։ Նորից փորձեք։"</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"Էջ <xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"մասշտաբ՝ <xliff:g id="FIRST">%1$d</xliff:g> տոկոս"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Անցեք <xliff:g id="PAGE_NUMBER">%1$d</xliff:g> էջ"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Հեռախոս՝ <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"ընտրված տեքստի սկիզբ"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"ընտրված տեքստի վերջ"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"էջ <xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g>՝ <xliff:g id="TOTAL">%3$d</xliff:g>-ից"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Պատկեր՝ <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Գտեք ֆայլում"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF փաստաթղթի էջը վնասված է"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Ոչ բավարար տվյալներ PDF փաստաթղթի մշակման համար"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Չհաջողվեց բացել PDF ֆայլը"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-in/strings.xml b/pdf/pdf-viewer/src/main/res/values-in/strings.xml
index 521fc2d..b10c88d 100644
--- a/pdf/pdf-viewer/src/main/res/values-in/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-in/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"kolom sandi"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"sandi salah"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Sandi salah. Coba lagi."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"halaman <xliff:g id="PAGE">%1$d</xliff:g> dari <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> persen"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Buka halaman <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Halaman dokumen PDF rusak"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Data tidak cukup untuk memproses dokumen PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Tidak dapat membuka file PDF"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-is/strings.xml b/pdf/pdf-viewer/src/main/res/values-is/strings.xml
index 0d923ae..3a9d53d3 100644
--- a/pdf/pdf-viewer/src/main/res/values-is/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-is/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Síða í PDF-skjali er gölluð"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Ekki næg gögn fyrir úrvinnslu á PDF-skjali"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Ekki tókst að opna PDF-skrá"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-it/strings.xml b/pdf/pdf-viewer/src/main/res/values-it/strings.xml
index eaaba33..48da371 100644
--- a/pdf/pdf-viewer/src/main/res/values-it/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-it/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"campo password"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"password errata"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Password errata. Riprova."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"pagina <xliff:g id="PAGE">%1$d</xliff:g> di <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g>%%"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Vai alla pagina <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Telefono: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"inizio selezione"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"fine selezione"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"pagine da <xliff:g id="FIRST">%1$d</xliff:g> a <xliff:g id="LAST">%2$d</xliff:g> di <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Immagine: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Trova nel file"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Pagina inaccessibile per il documento PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Dati insufficienti per l\'elaborazione del documento PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Impossibile aprire il file PDF"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-iw/strings.xml b/pdf/pdf-viewer/src/main/res/values-iw/strings.xml
index 03c20c6..294e58c 100644
--- a/pdf/pdf-viewer/src/main/res/values-iw/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-iw/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"קישור מנותק בדף למסמך ה-PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"אין מספיק נתונים כדי לעבד את מסמך ה-PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"לא ניתן לפתוח את קובץ ה-PDF"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ja/strings.xml b/pdf/pdf-viewer/src/main/res/values-ja/strings.xml
index c98de41..1684664 100644
--- a/pdf/pdf-viewer/src/main/res/values-ja/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ja/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"パスワードを入力する項目です"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"パスワードが正しくありません"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"パスワードが正しくありません。もう一度お試しください。"</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g> ページ"</string>
<string name="desc_zoom" msgid="7318480946145947242">"ズーム <xliff:g id="FIRST">%1$d</xliff:g> %%"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"<xliff:g id="PAGE_NUMBER">%1$d</xliff:g> ページに移動します"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"電話番号: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"選択範囲の最初"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"選択範囲の最後"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>~<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>~<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="FIRST">%1$d</xliff:g>~<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g> ページ"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"画像: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"ファイル内を検索"</string>
@@ -60,4 +60,5 @@
<string name="page_broken" msgid="2968770793669433462">"PDF ドキュメントのページが壊れています"</string>
<string name="needs_more_data" msgid="3520133467908240802">"データ不足のため PDF ドキュメントを処理できません"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF ファイルを開けません"</string>
+ <string name="clipboard_label" msgid="8943155331324944981">"PDF からコピーしたコンテンツ"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ka/strings.xml b/pdf/pdf-viewer/src/main/res/values-ka/strings.xml
index 38f52dd..d2fc30c 100644
--- a/pdf/pdf-viewer/src/main/res/values-ka/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ka/strings.xml
@@ -60,4 +60,5 @@
<string name="page_broken" msgid="2968770793669433462">"PDF დოკუმენტის გვერდი დაზიანებულია"</string>
<string name="needs_more_data" msgid="3520133467908240802">"მონაცემები არ არის საკმარისი PDF დოკუმენტის დასამუშავებლად"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF ფაილის გახსნა ვერ ხერხდება"</string>
+ <string name="clipboard_label" msgid="8943155331324944981">"კონტენტი კოპირებულია PDF-იდან"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-kk/strings.xml b/pdf/pdf-viewer/src/main/res/values-kk/strings.xml
index fa832ad..4047aae 100644
--- a/pdf/pdf-viewer/src/main/res/values-kk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-kk/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"құпия сөз өрісі"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"құпия сөз қате"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Құпия сөз қате. Қайталап көріңіз."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"Бет: <xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"<xliff:g id="FIRST">%1$d</xliff:g> пайызға масштабтау"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"<xliff:g id="PAGE_NUMBER">%1$d</xliff:g>-бетке өтіңіз."</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Телефон: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"таңдалған мәтіннің басы"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"таңдалған мәтіннің соңы"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"Бет: <xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Сурет: <xliff:g id="ALT_TEXT">%1$s</xliff:g>."</string>
<string name="hint_find" msgid="5385388836603550565">"Файлдан табу"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF құжатының беті бұзылған."</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF құжатын өңдеу үшін деректер жеткіліксіз."</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF файлын ашу мүмкін емес."</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-km/strings.xml b/pdf/pdf-viewer/src/main/res/values-km/strings.xml
index 5657c3e..8192706 100644
--- a/pdf/pdf-viewer/src/main/res/values-km/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-km/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"ទំព័រមិនដំណើរការសម្រាប់ឯកសារ PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"មានទិន្នន័យមិនគ្រប់គ្រាន់សម្រាប់ដំណើរការឯកសារ PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"មិនអាចបើកឯកសារ PDF បានទេ"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-kn/strings.xml b/pdf/pdf-viewer/src/main/res/values-kn/strings.xml
index e41ae2a..a03105f 100644
--- a/pdf/pdf-viewer/src/main/res/values-kn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-kn/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF ಡಾಕ್ಯುಮೆಂಟ್ಗೆ ಸಂಬಂಧಿಸಿದ ಪುಟ ಮುರಿದಿದೆ"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF ಡಾಕ್ಯುಮೆಂಟ್ ಅನ್ನು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸಲು ಸಾಕಷ್ಟು ಡೇಟಾ ಇಲ್ಲ"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF ಫೈಲ್ ಅನ್ನು ತೆರೆಯಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ko/strings.xml b/pdf/pdf-viewer/src/main/res/values-ko/strings.xml
index 1c91838..3cfb9f1 100644
--- a/pdf/pdf-viewer/src/main/res/values-ko/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ko/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"비밀번호 입력란"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"비밀번호가 잘못됨"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"잘못된 비밀번호입니다. 다시 시도하세요."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"<xliff:g id="TOTAL">%2$d</xliff:g>페이지 중 <xliff:g id="PAGE">%1$d</xliff:g>페이지"</string>
<string name="desc_zoom" msgid="7318480946145947242">"<xliff:g id="FIRST">%1$d</xliff:g>%% 확대/축소"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"<xliff:g id="PAGE_NUMBER">%1$d</xliff:g> 페이지로 이동"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"전화번호: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"선택 영역 시작"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"선택 영역 끝"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>~<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>~<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="TOTAL">%3$d</xliff:g>페이지 중 <xliff:g id="FIRST">%1$d</xliff:g>~<xliff:g id="LAST">%2$d</xliff:g>페이지"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"이미지: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"파일에서 찾기"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF 문서의 페이지가 손상되었습니다."</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF 문서 처리를 위한 데이터가 부족합니다."</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF 파일을 열 수 없음"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ky/strings.xml b/pdf/pdf-viewer/src/main/res/values-ky/strings.xml
index a5da24f..c6efa08 100644
--- a/pdf/pdf-viewer/src/main/res/values-ky/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ky/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF документинин барагы бузук"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF документин иштетүү үчүн маалымат жетишсиз"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF файл ачылбай жатат"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-lo/strings.xml b/pdf/pdf-viewer/src/main/res/values-lo/strings.xml
index 0d6dc62..dacf6fa 100644
--- a/pdf/pdf-viewer/src/main/res/values-lo/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-lo/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"ໜ້າເສຍຫາຍສໍາລັບເອກະສານ PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"ຂໍ້ມູນບໍ່ພຽງພໍສໍາລັບການປະມວນຜົນເອກະສານ PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"ບໍ່ສາມາດເປີດໄຟລ໌ PDF"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-lt/strings.xml b/pdf/pdf-viewer/src/main/res/values-lt/strings.xml
index 0737670..9a284fe 100644
--- a/pdf/pdf-viewer/src/main/res/values-lt/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-lt/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Sugadintas PDF dokumento puslapis"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Nepakanka duomenų PDF dokumentui apdoroti"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Nepavyksta atidaryti PDF failo"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-lv/strings.xml b/pdf/pdf-viewer/src/main/res/values-lv/strings.xml
index 21fa396f..3dce52e 100644
--- a/pdf/pdf-viewer/src/main/res/values-lv/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-lv/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"paroles lauks"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"parole nav pareiza"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Nepareiza parole. Mēģiniet vēlreiz."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"<xliff:g id="PAGE">%1$d</xliff:g>. lapa no <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"tālummaiņa procentos ir <xliff:g id="FIRST">%1$d</xliff:g>"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Doties uz <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>. lapu"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Tālruņa numurs: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"atlases sākums"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"atlases beigas"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"<xliff:g id="FIRST">%1$d</xliff:g>.–<xliff:g id="LAST">%2$d</xliff:g>. lapa no <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Attēls: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Meklēt failā"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF dokumenta lapa ir bojāta"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Nepietiekams datu apjoms, lai apstrādātu PDF dokumentu"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Nevar atvērt PDF failu."</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-mk/strings.xml b/pdf/pdf-viewer/src/main/res/values-mk/strings.xml
index d7ba10e..1d569ea 100644
--- a/pdf/pdf-viewer/src/main/res/values-mk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-mk/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"поле за лозинка"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"лозинката е неточна"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Погрешна лозинка. Обидете се повторно."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"страница <xliff:g id="PAGE">%1$d</xliff:g> од <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"зумирајте <xliff:g id="FIRST">%1$d</xliff:g> проценти"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Одете на страницата <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Телефон: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"почеток на изборот"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"крај на изборот"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g> – <xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g> – <xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"од страница <xliff:g id="FIRST">%1$d</xliff:g> до <xliff:g id="LAST">%2$d</xliff:g> од <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Слика: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Најдете во датотека"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Страницата не може да го вчита PDF-документот"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Недоволно податоци за обработка на PDF-документот"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Не може да се отвори PDF-датотеката"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ml/strings.xml b/pdf/pdf-viewer/src/main/res/values-ml/strings.xml
index 7bce561..2c1a0e8 100644
--- a/pdf/pdf-viewer/src/main/res/values-ml/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ml/strings.xml
@@ -60,4 +60,5 @@
<string name="page_broken" msgid="2968770793669433462">"PDF ഡോക്യുമെന്റിനായി പേജ് ലോഡ് ചെയ്യാനായില്ല"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF ഡോക്യുമെന്റ് പ്രോസസ് ചെയ്യാൻ മതിയായ ഡാറ്റയില്ല"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF ഫയൽ തുറക്കാനാകുന്നില്ല"</string>
+ <string name="clipboard_label" msgid="8943155331324944981">"ഉള്ളടക്കം PDF-ൽ നിന്ന് പകർത്തി"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-mn/strings.xml b/pdf/pdf-viewer/src/main/res/values-mn/strings.xml
index 71cf402..930c2b7 100644
--- a/pdf/pdf-viewer/src/main/res/values-mn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-mn/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF баримт бичгийн хуудас эвдэрсэн"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF баримт бичгийг боловсруулахад өгөгдөл хангалтгүй байна"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF файлыг нээх боломжгүй"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-mr/strings.xml b/pdf/pdf-viewer/src/main/res/values-mr/strings.xml
index 57f16e3..41b2446 100644
--- a/pdf/pdf-viewer/src/main/res/values-mr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-mr/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"पीडीएफ दस्तऐवजासाठी पेज खंडित झाले आहे"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF दस्तऐवजावर प्रक्रिया करण्यासाठी डेटा पुरेसा नाही"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF फाइल उघडू शकत नाही"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ms/strings.xml b/pdf/pdf-viewer/src/main/res/values-ms/strings.xml
index b76b58e..300a487 100644
--- a/pdf/pdf-viewer/src/main/res/values-ms/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ms/strings.xml
@@ -60,4 +60,5 @@
<string name="page_broken" msgid="2968770793669433462">"Halaman rosak untuk dokumen PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Data tidak mencukupi untuk memproses dokumen PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Tidak dapat membuka fail PDF"</string>
+ <string name="clipboard_label" msgid="8943155331324944981">"Kandungan disalin daripada PDF"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-my/strings.xml b/pdf/pdf-viewer/src/main/res/values-my/strings.xml
index 1225fad..f7705fe 100644
--- a/pdf/pdf-viewer/src/main/res/values-my/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-my/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF မှတ်တမ်းအတွက် စာမျက်နှာ ပျက်နေသည်"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF မှတ်တမ်း လုပ်ဆောင်ရန်အတွက် ဒေတာ မလုံလောက်ပါ"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF ဖိုင်ကို ဖွင့်၍မရပါ"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-nb/strings.xml b/pdf/pdf-viewer/src/main/res/values-nb/strings.xml
index b21081b..fd76435 100644
--- a/pdf/pdf-viewer/src/main/res/values-nb/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-nb/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"passordfelt"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"feil passord"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Feil passord. Prøv på nytt."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"side <xliff:g id="PAGE">%1$d</xliff:g> av <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> prosent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Gå til side <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Telefonnummer: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"starten av utvalget"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"slutten av utvalget"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"side <xliff:g id="FIRST">%1$d</xliff:g> til <xliff:g id="LAST">%2$d</xliff:g> av <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Bilde: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Finn i filen"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Siden er ødelagt for PDF-dokumentet"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Det er utilstrekkelige data for behandling av PDF-dokumentet"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Kan ikke åpne PDF-filen"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ne/strings.xml b/pdf/pdf-viewer/src/main/res/values-ne/strings.xml
index ac4b875..ca0437a 100644
--- a/pdf/pdf-viewer/src/main/res/values-ne/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ne/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF डकुमेन्टको पेज लोड गर्न सकिएन"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF डकुमेन्ट प्रोसेस गर्न पर्याप्त जानकारी छैन"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF फाइल खोल्न सकिएन"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-nl/strings.xml b/pdf/pdf-viewer/src/main/res/values-nl/strings.xml
index 17a892b..2f98f70 100644
--- a/pdf/pdf-viewer/src/main/res/values-nl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-nl/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"wachtwoordveld"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"wachtwoord is onjuist"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Onjuist wachtwoord. Probeer het opnieuw."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"pagina <xliff:g id="PAGE">%1$d</xliff:g> van <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"zoom <xliff:g id="FIRST">%1$d</xliff:g> procent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Ga naar pagina <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Pagina van het pdf-document kan niet worden geladen"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Onvoldoende gegevens om het pdf-document te verwerken"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Kan pdf-bestand niet openen"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-or/strings.xml b/pdf/pdf-viewer/src/main/res/values-or/strings.xml
index eb9dcee..8a0582a 100644
--- a/pdf/pdf-viewer/src/main/res/values-or/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-or/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF ଡକ୍ୟୁମେଣ୍ଟ ପାଇଁ ପୃଷ୍ଠା ବିଭାଜିତ ହୋଇଛି"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF ଡକ୍ୟୁମେଣ୍ଟ ପ୍ରକ୍ରିୟାକରଣ ପାଇଁ ପର୍ଯ୍ୟାପ୍ତ ଡାଟା ନାହିଁ"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF ଫାଇଲକୁ ଖୋଲାଯାଇପାରିବ ନାହିଁ"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-pa/strings.xml b/pdf/pdf-viewer/src/main/res/values-pa/strings.xml
index b6cdd23..ff925a3 100644
--- a/pdf/pdf-viewer/src/main/res/values-pa/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pa/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF ਦਸਤਾਵੇਜ਼ ਲਈ ਪੰਨਾ ਲੋਡ ਨਹੀਂ ਹੋ ਰਿਹਾ"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF ਦਸਤਾਵੇਜ਼ \'ਤੇ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਲਈ ਲੋੜੀਂਦਾ ਡਾਟਾ ਨਹੀਂ ਹੈ"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF ਫ਼ਾਈਲ ਖੋਲ੍ਹੀ ਨਹੀਂ ਜਾ ਸਕਦੀ"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-pl/strings.xml b/pdf/pdf-viewer/src/main/res/values-pl/strings.xml
index ec60ef7..c06a100 100644
--- a/pdf/pdf-viewer/src/main/res/values-pl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pl/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"pole hasła"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"nieprawidłowe hasło"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Błędne hasło. Spróbuj ponownie."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"strona <xliff:g id="PAGE">%1$d</xliff:g> z <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"powiększenie <xliff:g id="FIRST">%1$d</xliff:g> procent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Idź do strony <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -60,4 +60,5 @@
<string name="page_broken" msgid="2968770793669433462">"Strona w dokumencie PDF jest uszkodzona"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Brak wystarczającej ilości danych do przetworzenia dokumentu PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Nie można otworzyć pliku PDF"</string>
+ <string name="clipboard_label" msgid="8943155331324944981">"Treść została skopiowana z pliku PDF"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-pt-rBR/strings.xml b/pdf/pdf-viewer/src/main/res/values-pt-rBR/strings.xml
index 901115c..87937e2 100644
--- a/pdf/pdf-viewer/src/main/res/values-pt-rBR/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pt-rBR/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Página do documento PDF corrompida"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Dados insuficientes para processamento do documento PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Não é possível abrir o arquivo PDF"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-pt-rPT/strings.xml b/pdf/pdf-viewer/src/main/res/values-pt-rPT/strings.xml
index 61f9a19..d9db7c2 100644
--- a/pdf/pdf-viewer/src/main/res/values-pt-rPT/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pt-rPT/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"campo palavra-passe"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"palavra-passe incorreta"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Palavra-passe incorreta. Tente novamente."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"página <xliff:g id="PAGE">%1$d</xliff:g> de <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"zoom a <xliff:g id="FIRST">%1$d</xliff:g> por cento"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Ir para a página <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Telefone: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"início da seleção"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"fim da seleção"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"páginas <xliff:g id="FIRST">%1$d</xliff:g> a <xliff:g id="LAST">%2$d</xliff:g> de <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Imagem: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Procure no ficheiro"</string>
@@ -60,4 +60,5 @@
<string name="page_broken" msgid="2968770793669433462">"Página danificada para o documento PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Dados insuficientes para processar o documento PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Não é possível abrir o ficheiro PDF"</string>
+ <string name="clipboard_label" msgid="8943155331324944981">"Conteúdo copiado de PDF"</string>
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-pt/strings.xml b/pdf/pdf-viewer/src/main/res/values-pt/strings.xml
index 901115c..87937e2 100644
--- a/pdf/pdf-viewer/src/main/res/values-pt/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pt/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Página do documento PDF corrompida"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Dados insuficientes para processamento do documento PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Não é possível abrir o arquivo PDF"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ro/strings.xml b/pdf/pdf-viewer/src/main/res/values-ro/strings.xml
index 06cb930..1d04a93 100644
--- a/pdf/pdf-viewer/src/main/res/values-ro/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ro/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Pagină deteriorată pentru documentul PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Date insuficiente pentru procesarea documentului PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Nu se poate deschide fișierul PDF"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ru/strings.xml b/pdf/pdf-viewer/src/main/res/values-ru/strings.xml
index 12794a4..c4670b90 100644
--- a/pdf/pdf-viewer/src/main/res/values-ru/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ru/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Страница документа PDF повреждена"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Недостаточно данных для обработки документа PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Не удается открыть PDF-файл."</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-si/strings.xml b/pdf/pdf-viewer/src/main/res/values-si/strings.xml
index 2f98180..d92f11a 100644
--- a/pdf/pdf-viewer/src/main/res/values-si/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-si/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF ලේඛනය සඳහා පිටුව හානි වී ඇත"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF ලේඛනය සැකසීම සඳහා ප්රමාණවත් දත්ත නොමැත"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF ගොනුව විවෘත කළ නොහැක"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-sk/strings.xml b/pdf/pdf-viewer/src/main/res/values-sk/strings.xml
index d4d7d03..dcf8c81 100644
--- a/pdf/pdf-viewer/src/main/res/values-sk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sk/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Stránka sa v dokumente vo formáte PDF nedá načítať"</string>
<string name="needs_more_data" msgid="3520133467908240802">"V dokumente vo formáte PDF nie je dostatok údajov na spracovanie"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Súbor PDF sa nepodarilo otvoriť"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-sl/strings.xml b/pdf/pdf-viewer/src/main/res/values-sl/strings.xml
index 585e83d..b57be5b 100644
--- a/pdf/pdf-viewer/src/main/res/values-sl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sl/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"polje za geslo"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"napačno geslo"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Napačno geslo. Poskusite znova."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"stran <xliff:g id="PAGE">%1$d</xliff:g> od <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"povečava <xliff:g id="FIRST">%1$d</xliff:g> %%"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Pojdi na stran <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Telefon: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"začetek izbire"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"konec izbire"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"strani <xliff:g id="FIRST">%1$d</xliff:g> do <xliff:g id="LAST">%2$d</xliff:g> od <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Slika: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Iskanje v datoteki"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Strani iz dokumenta PDF ni mogoče prikazati"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Nezadostni podatki za obdelavo dokumenta PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Datoteke PDF ni mogoče odpreti"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-sq/strings.xml b/pdf/pdf-viewer/src/main/res/values-sq/strings.xml
index 218f09b..517bdae 100644
--- a/pdf/pdf-viewer/src/main/res/values-sq/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sq/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Faqe e dëmtuar për dokumentin PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Të dhëna të pamjaftueshme për përpunimin e dokumentit PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Skedari PDF nuk mund të hapet"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-sr/strings.xml b/pdf/pdf-viewer/src/main/res/values-sr/strings.xml
index d88bae3..305c103 100644
--- a/pdf/pdf-viewer/src/main/res/values-sr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sr/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Неисправна страница за PDF документ"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Недовољно података за обраду PDF документа"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Отварање PDF фајла није успело"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-sv/strings.xml b/pdf/pdf-viewer/src/main/res/values-sv/strings.xml
index 7966f88a..53838ff 100644
--- a/pdf/pdf-viewer/src/main/res/values-sv/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sv/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"lösenordsfält"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"felaktigt lösenord"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Fel lösenord. Försök igen."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"sida <xliff:g id="PAGE">%1$d</xliff:g> av <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"zooma <xliff:g id="FIRST">%1$d</xliff:g> procent"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Öppna sidan <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Telefonnummer: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"textmarkeringens början"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"textmarkeringens slut"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>–<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"sidorna <xliff:g id="FIRST">%1$d</xliff:g> till <xliff:g id="LAST">%2$d</xliff:g> av <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Bild: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Hitta i filen"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Det gick inte att läsa in en sida i PDF-dokumentet"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Otillräcklig data för att behandla PDF-dokumentet"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Det går inte att öppna PDF-filen"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-sw/strings.xml b/pdf/pdf-viewer/src/main/res/values-sw/strings.xml
index d9c1f71..19d3afe 100644
--- a/pdf/pdf-viewer/src/main/res/values-sw/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sw/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Ukurasa wa hati ya PDF una tatizo"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Hamna data ya kutosha kuchakata hati ya PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Imeshindwa kufungua faili ya PDF"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ta/strings.xml b/pdf/pdf-viewer/src/main/res/values-ta/strings.xml
index 2dcdc17..3a526a2 100644
--- a/pdf/pdf-viewer/src/main/res/values-ta/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ta/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF ஆவணத்தை ஏற்ற முடியவில்லை"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF ஆவணத்தைச் செயலாக்குவதற்குப் போதுமான தரவு இல்லை"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF ஃபைலைத் திறக்க முடியவில்லை"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-te/strings.xml b/pdf/pdf-viewer/src/main/res/values-te/strings.xml
index b8733ab..2c91b5c 100644
--- a/pdf/pdf-viewer/src/main/res/values-te/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-te/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF డాక్యుమెంట్కు సంబంధించి పేజీ బ్రేక్ అయింది"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF డాక్యుమెంట్ను ప్రాసెస్ చేయడానికి డేటా తగినంత లేదు"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF ఫైల్ను తెరవడం సాధ్యపడదు"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-th/strings.xml b/pdf/pdf-viewer/src/main/res/values-th/strings.xml
index 561d5b4..2f73d7c 100644
--- a/pdf/pdf-viewer/src/main/res/values-th/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-th/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"ช่องรหัสผ่าน"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"รหัสผ่านไม่ถูกต้อง"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"รหัสผ่านไม่ถูกต้อง โปรดลองอีกครั้ง"</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"หน้า <xliff:g id="PAGE">%1$d</xliff:g> จาก <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"ซูม <xliff:g id="FIRST">%1$d</xliff:g> เปอร์เซ็นต์"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"ไปที่หน้า <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"หมายเลขโทรศัพท์: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"เริ่มส่วนที่เลือก"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"สิ้นสุดส่วนที่เลือก"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"หน้า <xliff:g id="FIRST">%1$d</xliff:g> ถึง <xliff:g id="LAST">%2$d</xliff:g> จาก <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"รูปภาพ: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"ค้นหาในไฟล์"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"หน้าในเอกสาร PDF เสียหาย"</string>
<string name="needs_more_data" msgid="3520133467908240802">"ข้อมูลไม่เพียงพอสำหรับการประมวลผลเอกสาร PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"เปิดไฟล์ PDF ไม่ได้"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-tl/strings.xml b/pdf/pdf-viewer/src/main/res/values-tl/strings.xml
index c350fb1..1d51aaa 100644
--- a/pdf/pdf-viewer/src/main/res/values-tl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-tl/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Sira ang page para sa PDF na dokumento"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Kulang ang data para maproseso ang PDF na dokumento"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Hindi mabuksan ang PDF file"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-tr/strings.xml b/pdf/pdf-viewer/src/main/res/values-tr/strings.xml
index e942abd..14b1abc 100644
--- a/pdf/pdf-viewer/src/main/res/values-tr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-tr/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"şifre alanı"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"şifre yanlış"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Yanlış şifre. Tekrar deneyin."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"sayfa <xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"yakınlaştırma yüzdesi <xliff:g id="FIRST">%1$d</xliff:g>"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Şu sayfaya git <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Telefon: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"seçim başlangıcı"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"seçim sonu"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"sayfa <xliff:g id="FIRST">%1$d</xliff:g>-<xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Resim: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Dosyada bul"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF dokümanının sayfası bozuk"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF dokümanını işleyecek kadar yeterli veri yok"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF dosyası açılamıyor"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-uk/strings.xml b/pdf/pdf-viewer/src/main/res/values-uk/strings.xml
index dab20cf..c622b57 100644
--- a/pdf/pdf-viewer/src/main/res/values-uk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-uk/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Сторінку документа PDF пошкоджено"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Недостатньо даних для обробки документа PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Не вдалося відкрити файл PDF"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-ur/strings.xml b/pdf/pdf-viewer/src/main/res/values-ur/strings.xml
index cb3ff36..6b542e8 100644
--- a/pdf/pdf-viewer/src/main/res/values-ur/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ur/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF دستاویز کیلئے شکستہ صفحہ"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF دستاویز پر کارروائی کرنے کیلئے ڈیٹا ناکافی ہے"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF فائل کو کھولا نہیں جا سکتا"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-uz/strings.xml b/pdf/pdf-viewer/src/main/res/values-uz/strings.xml
index b0e3a94..0dfea8a 100644
--- a/pdf/pdf-viewer/src/main/res/values-uz/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-uz/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF hujjat sahifasi yaroqsiz"</string>
<string name="needs_more_data" msgid="3520133467908240802">"PDF hujjatni qayta ishlash uchun kerakli axborotlar yetarli emas"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"PDF fayk ochilmadi"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-vi/strings.xml b/pdf/pdf-viewer/src/main/res/values-vi/strings.xml
index 4eacb6e..76e2782 100644
--- a/pdf/pdf-viewer/src/main/res/values-vi/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-vi/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"trường nhập mật khẩu"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"mật khẩu không chính xác"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"Sai mật khẩu. Hãy thử lại."</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"trang <xliff:g id="PAGE">%1$d</xliff:g> trong số <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_zoom" msgid="7318480946145947242">"thu phóng <xliff:g id="FIRST">%1$d</xliff:g> phần trăm"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"Chuyển đến trang <xliff:g id="PAGE_NUMBER">%1$d</xliff:g>"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"Số điện thoại: <xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"đầu văn bản đã chọn"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"cuối văn bản đã chọn"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g> – <xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g> – <xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"các trang <xliff:g id="FIRST">%1$d</xliff:g> đến <xliff:g id="LAST">%2$d</xliff:g> trong số <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"Hình ảnh: <xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"Tìm trong tệp"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Tài liệu PDF này bị lỗi trang"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Không đủ dữ liệu để xử lý tài liệu PDF này"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Không mở được tệp PDF"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml b/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml
index 4352a26..683dddd 100644
--- a/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF 文档的页面已损坏"</string>
<string name="needs_more_data" msgid="3520133467908240802">"数据不足,无法处理 PDF 文档"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"无法打开 PDF 文件"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-zh-rHK/strings.xml b/pdf/pdf-viewer/src/main/res/values-zh-rHK/strings.xml
index b157798..6c6c835 100644
--- a/pdf/pdf-viewer/src/main/res/values-zh-rHK/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zh-rHK/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"密碼欄位"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"密碼不正確"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"密碼錯誤,請再試一次。"</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"第 <xliff:g id="PAGE">%1$d</xliff:g> 頁,共 <xliff:g id="TOTAL">%2$d</xliff:g> 頁"</string>
<string name="desc_zoom" msgid="7318480946145947242">"縮放 <xliff:g id="FIRST">%1$d</xliff:g>%%"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"前往第 <xliff:g id="PAGE_NUMBER">%1$d</xliff:g> 頁"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"電話:<xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"選取開頭"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"選取結尾"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g> - <xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g> - <xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"第 <xliff:g id="FIRST">%1$d</xliff:g> 至 <xliff:g id="LAST">%2$d</xliff:g> 頁 (共 <xliff:g id="TOTAL">%3$d</xliff:g> 頁)"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"圖片:<xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"在檔案中搜尋"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF 文件頁面已損毀"</string>
<string name="needs_more_data" msgid="3520133467908240802">"沒有足夠資料處理 PDF 文件"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"無法開啟 PDF 檔案"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-zh-rTW/strings.xml b/pdf/pdf-viewer/src/main/res/values-zh-rTW/strings.xml
index 7f2ca64..bb54eca 100644
--- a/pdf/pdf-viewer/src/main/res/values-zh-rTW/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zh-rTW/strings.xml
@@ -25,7 +25,7 @@
<string name="desc_password" msgid="6636473611443746739">"密碼欄位"</string>
<string name="desc_password_incorrect" msgid="4718823067483963845">"密碼不正確"</string>
<string name="desc_password_incorrect_message" msgid="4676206571020946870">"密碼錯誤,請再試一次。"</string>
- <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="label_page_single" msgid="456123685879261101">"<xliff:g id="PAGE">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="desc_page_single" msgid="8459583146661044094">"第 <xliff:g id="PAGE">%1$d</xliff:g> 頁,共 <xliff:g id="TOTAL">%2$d</xliff:g> 頁"</string>
<string name="desc_zoom" msgid="7318480946145947242">"縮放 <xliff:g id="FIRST">%1$d</xliff:g>%%"</string>
<string name="desc_goto_link" msgid="2461368384824849714">"前往第 <xliff:g id="PAGE_NUMBER">%1$d</xliff:g> 頁"</string>
@@ -40,7 +40,7 @@
<string name="desc_phone_link" msgid="4986414958429253135">"電話號碼:<xliff:g id="PHONE_NUMBER">%1$s</xliff:g>"</string>
<string name="desc_selection_start" msgid="3249210022376857070">"選取開頭"</string>
<string name="desc_selection_stop" msgid="2690168835536726898">"選取結尾"</string>
- <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g> - <xliff:g id="LAST">%2$d</xliff:g>/<xliff:g id="TOTAL">%3$d</xliff:g>"</string>
+ <string name="label_page_range" msgid="8290964180158460076">"<xliff:g id="FIRST">%1$d</xliff:g> - <xliff:g id="LAST">%2$d</xliff:g> / <xliff:g id="TOTAL">%3$d</xliff:g>"</string>
<string name="desc_page_range" msgid="5286496438609641577">"第 <xliff:g id="FIRST">%1$d</xliff:g> 到 <xliff:g id="LAST">%2$d</xliff:g> 頁,共 <xliff:g id="TOTAL">%3$d</xliff:g> 頁"</string>
<string name="desc_image_alt_text" msgid="7700601988820586333">"圖片:<xliff:g id="ALT_TEXT">%1$s</xliff:g>"</string>
<string name="hint_find" msgid="5385388836603550565">"在檔案中搜尋"</string>
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"PDF 文件的頁面損毀"</string>
<string name="needs_more_data" msgid="3520133467908240802">"資料不足,無法處理 PDF 文件"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"無法開啟 PDF 檔案"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values-zu/strings.xml b/pdf/pdf-viewer/src/main/res/values-zu/strings.xml
index dda6441..d5e0c57 100644
--- a/pdf/pdf-viewer/src/main/res/values-zu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zu/strings.xml
@@ -60,4 +60,6 @@
<string name="page_broken" msgid="2968770793669433462">"Ikhasi eliphuliwe ledokhumenti ye-PDF"</string>
<string name="needs_more_data" msgid="3520133467908240802">"Idatha enganele yokucubungula idokhumenti ye-PDF"</string>
<string name="error_cannot_open_pdf" msgid="2361919778558145071">"Ayikwazi ukuvula ifayela le-PDF"</string>
+ <!-- no translation found for clipboard_label (8943155331324944981) -->
+ <skip />
</resources>
diff --git a/pdf/pdf-viewer/src/main/res/values/attrs.xml b/pdf/pdf-viewer/src/main/res/values/attrs.xml
index a20d373..21af49c 100644
--- a/pdf/pdf-viewer/src/main/res/values/attrs.xml
+++ b/pdf/pdf-viewer/src/main/res/values/attrs.xml
@@ -15,6 +15,12 @@
-->
<resources>
+ <declare-styleable name="PdfView">
+ <attr name="fastScrollVerticalThumbDrawable" format="reference" />
+ <attr name="fastScrollVerticalTrackDrawable" format="reference" />
+ <attr name="fastScrollPageIndicatorBackgroundDrawable" format="reference" />
+ </declare-styleable>
+
<declare-styleable name="FastScrollView">
<attr name="scrollbarMarginTop" format="dimension" />
<attr name="scrollbarMarginBottom" format="dimension" />
diff --git a/pdf/pdf-viewer/src/main/res/values/dimensions.xml b/pdf/pdf-viewer/src/main/res/values/dimensions.xml
index 34b4179..d490f64 100644
--- a/pdf/pdf-viewer/src/main/res/values/dimensions.xml
+++ b/pdf/pdf-viewer/src/main/res/values/dimensions.xml
@@ -29,4 +29,18 @@
<dimen name="text_select_handle_touch_size">48dp</dimen>
<dimen name="pdf_horizontal_padding">0dp</dimen>
+ <!--Fast scroller -->
+ <dimen name="default_thumb_width">16dp</dimen>
+ <dimen name="default_thumb_height">16dp</dimen>
+ <dimen name="default_track_width">9dp</dimen>
+ <dimen name="default_track_height">9dp</dimen>
+ <dimen name="scrubber_edge_offset">3dp</dimen>
+ <dimen name="page_indicator_height">12dp</dimen>
+ <dimen name="page_indicator_right_margin">3dp</dimen>
+ <dimen name="page_indicator_text_offset">9dp</dimen>
+ <dimen name="page_indicator_text_offset_x">1dp</dimen>
+ <dimen name="page_indicator_text_offset_y">20dp</dimen>
+ <dimen name="page_indicator_text_size">4.8dp</dimen>
+ <dimen name="scroller_top_margin">2dp</dimen>
+ <dimen name="scroller_bottom_margin">2dp</dimen>
</resources>
diff --git a/pdf/pdf-viewer/src/test/kotlin/androidx/pdf/view/SelectionStateManagerTest.kt b/pdf/pdf-viewer/src/test/kotlin/androidx/pdf/view/SelectionStateManagerTest.kt
index b1f4dd3..7980a55 100644
--- a/pdf/pdf-viewer/src/test/kotlin/androidx/pdf/view/SelectionStateManagerTest.kt
+++ b/pdf/pdf-viewer/src/test/kotlin/androidx/pdf/view/SelectionStateManagerTest.kt
@@ -29,6 +29,7 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -99,7 +100,7 @@
selectionStateManager.maybeSelectWordAtPoint(selectionPoint)
testDispatcher.scheduler.runCurrent()
- val selectionModel = selectionStateManager.selectionModel
+ val selectionModel = selectionStateManager.selectionModel.value
assertThat(selectionModel).isNotNull()
assertThat(selectionModel?.selection).isInstanceOf(TextSelection::class.java)
val selection = requireNotNull(selectionModel?.selection as TextSelection)
@@ -146,7 +147,7 @@
selectionStateManager.maybeSelectWordAtPoint(selectionPoint2)
testDispatcher.scheduler.runCurrent()
- val selectionModel = selectionStateManager.selectionModel
+ val selectionModel = selectionStateManager.selectionModel.value
assertThat(selectionModel).isNotNull()
assertThat(selectionModel?.selection).isInstanceOf(TextSelection::class.java)
val selection = requireNotNull(selectionModel?.selection as TextSelection)
@@ -184,7 +185,7 @@
assertThat(selectionStateManager.selectionModel).isNotNull()
selectionStateManager.clearSelection()
- assertThat(selectionStateManager.selectionModel).isNull()
+ assertThat(selectionStateManager.selectionModel.value).isNull()
// We only care about the final 2 signals that should occur as a result of cancellation
// hide action mode
assertThat(uiSignals[uiSignals.size - 2])
@@ -201,18 +202,18 @@
// Start a selection and don't finish it (i.e. no runCurrent)
selectionStateManager.maybeSelectWordAtPoint(selectionPoint)
- assertThat(selectionStateManager.selectionModel).isNull()
+ assertThat(selectionStateManager.selectionModel.value).isNull()
// Clear selection, flush the scheduler, and make sure selection remains null (i.e. the work
// enqueued by our initial selection doesn't finish and supersede the cleared state)
selectionStateManager.clearSelection()
testDispatcher.scheduler.runCurrent()
- assertThat(selectionStateManager.selectionModel).isNull()
+ assertThat(selectionStateManager.selectionModel.value).isNull()
}
@Test
fun maybeDragHandle_actionDownOutsideHandle_returnFalse() {
- selectionStateManager.selectionModel = initialSelectionForDragging
+ selectionStateManager._selectionModel.update { initialSelectionForDragging }
assertThat(
selectionStateManager.maybeDragSelectionHandle(
@@ -226,7 +227,7 @@
@Test
fun maybeDragHandle_actionDownInsideStartHandle_returnTrue() {
- selectionStateManager.selectionModel = initialSelectionForDragging
+ selectionStateManager._selectionModel.update { initialSelectionForDragging }
// Chose a point inside the start handle touch target (below and behind the start position)
val insideStartHandle =
PointF(initialSelectionForDragging.startBoundary.location.pagePoint).apply {
@@ -246,7 +247,7 @@
@Test
fun maybeDragHandle_actionDownInsideEndHandle_returnTrue() {
- selectionStateManager.selectionModel = initialSelectionForDragging
+ selectionStateManager._selectionModel.update { initialSelectionForDragging }
// Chose a point inside the end handle touch target (below and ahead the end position)
val insideEndHandle =
PointF(initialSelectionForDragging.endBoundary.location.pagePoint).apply {
@@ -266,7 +267,7 @@
@Test
fun maybeDragHandle_actionMove_updateSelection() {
- selectionStateManager.selectionModel = initialSelectionForDragging
+ selectionStateManager._selectionModel.update { initialSelectionForDragging }
// "Grab" the start handle
val insideStartHandle =
PointF(initialSelectionForDragging.startBoundary.location.pagePoint).apply {
@@ -295,7 +296,7 @@
// Make sure the selection is updated appropriately
testDispatcher.scheduler.runCurrent()
- val selection = selectionStateManager.selectionModel?.selection
+ val selection = selectionStateManager.selectionModel.value?.selection
assertThat(selection).isInstanceOf(TextSelection::class.java)
val expectedStartLoc = initialSelectionForDragging.endBoundary.location.pagePoint
val expectedEndLoc =
@@ -308,7 +309,7 @@
@Test
fun maybeDragHandle_actionMoveOutsidePage_returnTrue() {
- selectionStateManager.selectionModel = initialSelectionForDragging
+ selectionStateManager._selectionModel.update { initialSelectionForDragging }
// "Grab" the start handle
val insideStartHandle =
PointF(initialSelectionForDragging.startBoundary.location.pagePoint).apply {
@@ -337,7 +338,7 @@
@Test
fun maybeDragHandle_actionMoveWithoutActionDown_returnFalse() {
- selectionStateManager.selectionModel = initialSelectionForDragging
+ selectionStateManager._selectionModel.update { initialSelectionForDragging }
// Chose a point inside the start handle touch target (below and behind the start position)
val insideStartHandle =
PointF(initialSelectionForDragging.startBoundary.location.pagePoint).apply {
@@ -358,7 +359,7 @@
@Test
fun maybeDragHandle_actionUpWithoutActionDown_returnFalse() {
- selectionStateManager.selectionModel = initialSelectionForDragging
+ selectionStateManager._selectionModel.update { initialSelectionForDragging }
// Chose a point inside the start handle touch target (below and behind the start position)
val insideStartHandle =
PointF(initialSelectionForDragging.startBoundary.location.pagePoint).apply {
@@ -379,7 +380,7 @@
@Test
fun maybeDragHandle_actionUp_returnTrueAndStopHandlingEvents() {
- selectionStateManager.selectionModel = initialSelectionForDragging
+ selectionStateManager._selectionModel.update { initialSelectionForDragging }
// Chose a point inside the start handle touch target (below and behind the start position)
val insideStartHandle =
PointF(initialSelectionForDragging.startBoundary.location.pagePoint).apply {
diff --git a/performance/performance-unsafe/README.md b/performance/performance-unsafe/README.md
deleted file mode 100644
index b69d197..0000000
--- a/performance/performance-unsafe/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-This library is a dependency for Android only.
-It is not to be used for other targets.
diff --git a/performance/performance-unsafe/api/api_lint.ignore b/performance/performance-unsafe/api/api_lint.ignore
deleted file mode 100644
index 8d44e3d..0000000
--- a/performance/performance-unsafe/api/api_lint.ignore
+++ /dev/null
@@ -1,127 +0,0 @@
-// Baseline format: 1.0
-MissingNullability: sun.misc.Unsafe#allocateInstance(Class<?>):
- Missing nullability on method `allocateInstance` return
-MissingNullability: sun.misc.Unsafe#allocateInstance(Class<?>) parameter #0:
- Missing nullability on parameter `c` in method `allocateInstance`
-MissingNullability: sun.misc.Unsafe#arrayBaseOffset(Class<?>) parameter #0:
- Missing nullability on parameter `clazz` in method `arrayBaseOffset`
-MissingNullability: sun.misc.Unsafe#arrayIndexScale(Class<?>) parameter #0:
- Missing nullability on parameter `clazz` in method `arrayIndexScale`
-MissingNullability: sun.misc.Unsafe#compareAndSwapInt(Object, long, int, int) parameter #0:
- Missing nullability on parameter `obj` in method `compareAndSwapInt`
-MissingNullability: sun.misc.Unsafe#compareAndSwapLong(Object, long, long, long) parameter #0:
- Missing nullability on parameter `obj` in method `compareAndSwapLong`
-MissingNullability: sun.misc.Unsafe#compareAndSwapObject(Object, long, Object, Object) parameter #0:
- Missing nullability on parameter `obj` in method `compareAndSwapObject`
-MissingNullability: sun.misc.Unsafe#compareAndSwapObject(Object, long, Object, Object) parameter #2:
- Missing nullability on parameter `expectedValue` in method `compareAndSwapObject`
-MissingNullability: sun.misc.Unsafe#compareAndSwapObject(Object, long, Object, Object) parameter #3:
- Missing nullability on parameter `newValue` in method `compareAndSwapObject`
-MissingNullability: sun.misc.Unsafe#copyMemoryFromPrimitiveArray(Object, long, long, long) parameter #0:
- Missing nullability on parameter `src` in method `copyMemoryFromPrimitiveArray`
-MissingNullability: sun.misc.Unsafe#copyMemoryToPrimitiveArray(long, Object, long, long) parameter #1:
- Missing nullability on parameter `dst` in method `copyMemoryToPrimitiveArray`
-MissingNullability: sun.misc.Unsafe#getAndAddInt(Object, long, int) parameter #0:
- Missing nullability on parameter `o` in method `getAndAddInt`
-MissingNullability: sun.misc.Unsafe#getAndAddLong(Object, long, long) parameter #0:
- Missing nullability on parameter `o` in method `getAndAddLong`
-MissingNullability: sun.misc.Unsafe#getAndSetInt(Object, long, int) parameter #0:
- Missing nullability on parameter `o` in method `getAndSetInt`
-MissingNullability: sun.misc.Unsafe#getAndSetLong(Object, long, long) parameter #0:
- Missing nullability on parameter `o` in method `getAndSetLong`
-MissingNullability: sun.misc.Unsafe#getAndSetObject(Object, long, Object):
- Missing nullability on method `getAndSetObject` return
-MissingNullability: sun.misc.Unsafe#getAndSetObject(Object, long, Object) parameter #0:
- Missing nullability on parameter `o` in method `getAndSetObject`
-MissingNullability: sun.misc.Unsafe#getAndSetObject(Object, long, Object) parameter #2:
- Missing nullability on parameter `newValue` in method `getAndSetObject`
-MissingNullability: sun.misc.Unsafe#getBoolean(Object, long) parameter #0:
- Missing nullability on parameter `obj` in method `getBoolean`
-MissingNullability: sun.misc.Unsafe#getByte(Object, long) parameter #0:
- Missing nullability on parameter `obj` in method `getByte`
-MissingNullability: sun.misc.Unsafe#getChar(Object, long) parameter #0:
- Missing nullability on parameter `obj` in method `getChar`
-MissingNullability: sun.misc.Unsafe#getDouble(Object, long) parameter #0:
- Missing nullability on parameter `obj` in method `getDouble`
-MissingNullability: sun.misc.Unsafe#getFloat(Object, long) parameter #0:
- Missing nullability on parameter `obj` in method `getFloat`
-MissingNullability: sun.misc.Unsafe#getInt(Object, long) parameter #0:
- Missing nullability on parameter `obj` in method `getInt`
-MissingNullability: sun.misc.Unsafe#getIntVolatile(Object, long) parameter #0:
- Missing nullability on parameter `obj` in method `getIntVolatile`
-MissingNullability: sun.misc.Unsafe#getLong(Object, long) parameter #0:
- Missing nullability on parameter `obj` in method `getLong`
-MissingNullability: sun.misc.Unsafe#getLongVolatile(Object, long) parameter #0:
- Missing nullability on parameter `obj` in method `getLongVolatile`
-MissingNullability: sun.misc.Unsafe#getObject(Object, long):
- Missing nullability on method `getObject` return
-MissingNullability: sun.misc.Unsafe#getObject(Object, long) parameter #0:
- Missing nullability on parameter `obj` in method `getObject`
-MissingNullability: sun.misc.Unsafe#getObjectVolatile(Object, long):
- Missing nullability on method `getObjectVolatile` return
-MissingNullability: sun.misc.Unsafe#getObjectVolatile(Object, long) parameter #0:
- Missing nullability on parameter `obj` in method `getObjectVolatile`
-MissingNullability: sun.misc.Unsafe#getShort(Object, long) parameter #0:
- Missing nullability on parameter `obj` in method `getShort`
-MissingNullability: sun.misc.Unsafe#getUnsafe():
- Missing nullability on method `getUnsafe` return
-MissingNullability: sun.misc.Unsafe#objectFieldOffset(java.lang.reflect.Field) parameter #0:
- Missing nullability on parameter `field` in method `objectFieldOffset`
-MissingNullability: sun.misc.Unsafe#putBoolean(Object, long, boolean) parameter #0:
- Missing nullability on parameter `obj` in method `putBoolean`
-MissingNullability: sun.misc.Unsafe#putByte(Object, long, byte) parameter #0:
- Missing nullability on parameter `obj` in method `putByte`
-MissingNullability: sun.misc.Unsafe#putChar(Object, long, char) parameter #0:
- Missing nullability on parameter `obj` in method `putChar`
-MissingNullability: sun.misc.Unsafe#putDouble(Object, long, double) parameter #0:
- Missing nullability on parameter `obj` in method `putDouble`
-MissingNullability: sun.misc.Unsafe#putFloat(Object, long, float) parameter #0:
- Missing nullability on parameter `obj` in method `putFloat`
-MissingNullability: sun.misc.Unsafe#putInt(Object, long, int) parameter #0:
- Missing nullability on parameter `obj` in method `putInt`
-MissingNullability: sun.misc.Unsafe#putIntVolatile(Object, long, int) parameter #0:
- Missing nullability on parameter `obj` in method `putIntVolatile`
-MissingNullability: sun.misc.Unsafe#putLong(Object, long, long) parameter #0:
- Missing nullability on parameter `obj` in method `putLong`
-MissingNullability: sun.misc.Unsafe#putLongVolatile(Object, long, long) parameter #0:
- Missing nullability on parameter `obj` in method `putLongVolatile`
-MissingNullability: sun.misc.Unsafe#putObject(Object, long, Object) parameter #0:
- Missing nullability on parameter `obj` in method `putObject`
-MissingNullability: sun.misc.Unsafe#putObject(Object, long, Object) parameter #2:
- Missing nullability on parameter `newValue` in method `putObject`
-MissingNullability: sun.misc.Unsafe#putObjectVolatile(Object, long, Object) parameter #0:
- Missing nullability on parameter `obj` in method `putObjectVolatile`
-MissingNullability: sun.misc.Unsafe#putObjectVolatile(Object, long, Object) parameter #2:
- Missing nullability on parameter `newValue` in method `putObjectVolatile`
-MissingNullability: sun.misc.Unsafe#putOrderedInt(Object, long, int) parameter #0:
- Missing nullability on parameter `obj` in method `putOrderedInt`
-MissingNullability: sun.misc.Unsafe#putOrderedLong(Object, long, long) parameter #0:
- Missing nullability on parameter `obj` in method `putOrderedLong`
-MissingNullability: sun.misc.Unsafe#putOrderedObject(Object, long, Object) parameter #0:
- Missing nullability on parameter `obj` in method `putOrderedObject`
-MissingNullability: sun.misc.Unsafe#putOrderedObject(Object, long, Object) parameter #2:
- Missing nullability on parameter `newValue` in method `putOrderedObject`
-MissingNullability: sun.misc.Unsafe#putShort(Object, long, short) parameter #0:
- Missing nullability on parameter `obj` in method `putShort`
-MissingNullability: sun.misc.Unsafe#unpark(Object) parameter #0:
- Missing nullability on parameter `obj` in method `unpark`
-
-
-NoByteOrShort: sun.misc.Unsafe#getByte(Object, long):
- Should avoid odd sized primitives; use `int` instead of `byte` in method sun.misc.Unsafe.getByte(Object,long)
-NoByteOrShort: sun.misc.Unsafe#getByte(long):
- Should avoid odd sized primitives; use `int` instead of `byte` in method sun.misc.Unsafe.getByte(long)
-NoByteOrShort: sun.misc.Unsafe#getShort(Object, long):
- Should avoid odd sized primitives; use `int` instead of `short` in method sun.misc.Unsafe.getShort(Object,long)
-NoByteOrShort: sun.misc.Unsafe#getShort(long):
- Should avoid odd sized primitives; use `int` instead of `short` in method sun.misc.Unsafe.getShort(long)
-NoByteOrShort: sun.misc.Unsafe#putByte(Object, long, byte) parameter #2:
- Should avoid odd sized primitives; use `int` instead of `byte` in parameter newValue in sun.misc.Unsafe.putByte(Object obj, long offset, byte newValue)
-NoByteOrShort: sun.misc.Unsafe#putByte(long, byte) parameter #1:
- Should avoid odd sized primitives; use `int` instead of `byte` in parameter x in sun.misc.Unsafe.putByte(long address, byte x)
-NoByteOrShort: sun.misc.Unsafe#putShort(Object, long, short) parameter #2:
- Should avoid odd sized primitives; use `int` instead of `short` in parameter newValue in sun.misc.Unsafe.putShort(Object obj, long offset, short newValue)
-NoByteOrShort: sun.misc.Unsafe#putShort(long, short) parameter #1:
- Should avoid odd sized primitives; use `int` instead of `short` in parameter x in sun.misc.Unsafe.putShort(long address, short x)
-NoByteOrShort: sun.misc.Unsafe#setMemory(long, long, byte) parameter #2:
- Should avoid odd sized primitives; use `int` instead of `byte` in parameter value in sun.misc.Unsafe.setMemory(long address, long bytes, byte value)
diff --git a/performance/performance-unsafe/api/current.txt b/performance/performance-unsafe/api/current.txt
deleted file mode 100644
index ecc05bb..0000000
--- a/performance/performance-unsafe/api/current.txt
+++ /dev/null
@@ -1,76 +0,0 @@
-// Signature format: 4.0
-package sun.misc {
-
- public final class Unsafe {
- method public int addressSize();
- method public Object! allocateInstance(Class<? extends java.lang.Object!>!);
- method public long allocateMemory(long);
- method public int arrayBaseOffset(Class<? extends java.lang.Object!>!);
- method public int arrayIndexScale(Class<? extends java.lang.Object!>!);
- method public boolean compareAndSwapInt(Object!, long, int, int);
- method public boolean compareAndSwapLong(Object!, long, long, long);
- method public boolean compareAndSwapObject(Object!, long, Object!, Object!);
- method public void copyMemory(long, long, long);
- method public void copyMemoryFromPrimitiveArray(Object!, long, long, long);
- method public void copyMemoryToPrimitiveArray(long, Object!, long, long);
- method public void freeMemory(long);
- method public void fullFence();
- method public int getAndAddInt(Object!, long, int);
- method public long getAndAddLong(Object!, long, long);
- method public int getAndSetInt(Object!, long, int);
- method public long getAndSetLong(Object!, long, long);
- method public Object! getAndSetObject(Object!, long, Object!);
- method public boolean getBoolean(Object!, long);
- method public byte getByte(Object!, long);
- method public byte getByte(long);
- method public char getChar(Object!, long);
- method public char getChar(long);
- method public double getDouble(Object!, long);
- method public double getDouble(long);
- method public float getFloat(Object!, long);
- method public float getFloat(long);
- method public int getInt(Object!, long);
- method public int getInt(long);
- method public int getIntVolatile(Object!, long);
- method public long getLong(Object!, long);
- method public long getLong(long);
- method public long getLongVolatile(Object!, long);
- method public Object! getObject(Object!, long);
- method public Object! getObjectVolatile(Object!, long);
- method public short getShort(Object!, long);
- method public short getShort(long);
- method public static sun.misc.Unsafe! getUnsafe();
- method public void loadFence();
- method public long objectFieldOffset(java.lang.reflect.Field!);
- method public int pageSize();
- method public void park(boolean, long);
- method public void putBoolean(Object!, long, boolean);
- method public void putByte(Object!, long, byte);
- method public void putByte(long, byte);
- method public void putChar(Object!, long, char);
- method public void putChar(long, char);
- method public void putDouble(Object!, long, double);
- method public void putDouble(long, double);
- method public void putFloat(Object!, long, float);
- method public void putFloat(long, float);
- method public void putInt(Object!, long, int);
- method public void putInt(long, int);
- method public void putIntVolatile(Object!, long, int);
- method public void putLong(Object!, long, long);
- method public void putLong(long, long);
- method public void putLongVolatile(Object!, long, long);
- method public void putObject(Object!, long, Object!);
- method public void putObjectVolatile(Object!, long, Object!);
- method public void putOrderedInt(Object!, long, int);
- method public void putOrderedLong(Object!, long, long);
- method public void putOrderedObject(Object!, long, Object!);
- method public void putShort(Object!, long, short);
- method public void putShort(long, short);
- method public void setMemory(long, long, byte);
- method public void storeFence();
- method public void unpark(Object!);
- field public static final int INVALID_FIELD_OFFSET = -1; // 0xffffffff
- }
-
-}
-
diff --git a/performance/performance-unsafe/api/restricted_current.txt b/performance/performance-unsafe/api/restricted_current.txt
deleted file mode 100644
index ecc05bb..0000000
--- a/performance/performance-unsafe/api/restricted_current.txt
+++ /dev/null
@@ -1,76 +0,0 @@
-// Signature format: 4.0
-package sun.misc {
-
- public final class Unsafe {
- method public int addressSize();
- method public Object! allocateInstance(Class<? extends java.lang.Object!>!);
- method public long allocateMemory(long);
- method public int arrayBaseOffset(Class<? extends java.lang.Object!>!);
- method public int arrayIndexScale(Class<? extends java.lang.Object!>!);
- method public boolean compareAndSwapInt(Object!, long, int, int);
- method public boolean compareAndSwapLong(Object!, long, long, long);
- method public boolean compareAndSwapObject(Object!, long, Object!, Object!);
- method public void copyMemory(long, long, long);
- method public void copyMemoryFromPrimitiveArray(Object!, long, long, long);
- method public void copyMemoryToPrimitiveArray(long, Object!, long, long);
- method public void freeMemory(long);
- method public void fullFence();
- method public int getAndAddInt(Object!, long, int);
- method public long getAndAddLong(Object!, long, long);
- method public int getAndSetInt(Object!, long, int);
- method public long getAndSetLong(Object!, long, long);
- method public Object! getAndSetObject(Object!, long, Object!);
- method public boolean getBoolean(Object!, long);
- method public byte getByte(Object!, long);
- method public byte getByte(long);
- method public char getChar(Object!, long);
- method public char getChar(long);
- method public double getDouble(Object!, long);
- method public double getDouble(long);
- method public float getFloat(Object!, long);
- method public float getFloat(long);
- method public int getInt(Object!, long);
- method public int getInt(long);
- method public int getIntVolatile(Object!, long);
- method public long getLong(Object!, long);
- method public long getLong(long);
- method public long getLongVolatile(Object!, long);
- method public Object! getObject(Object!, long);
- method public Object! getObjectVolatile(Object!, long);
- method public short getShort(Object!, long);
- method public short getShort(long);
- method public static sun.misc.Unsafe! getUnsafe();
- method public void loadFence();
- method public long objectFieldOffset(java.lang.reflect.Field!);
- method public int pageSize();
- method public void park(boolean, long);
- method public void putBoolean(Object!, long, boolean);
- method public void putByte(Object!, long, byte);
- method public void putByte(long, byte);
- method public void putChar(Object!, long, char);
- method public void putChar(long, char);
- method public void putDouble(Object!, long, double);
- method public void putDouble(long, double);
- method public void putFloat(Object!, long, float);
- method public void putFloat(long, float);
- method public void putInt(Object!, long, int);
- method public void putInt(long, int);
- method public void putIntVolatile(Object!, long, int);
- method public void putLong(Object!, long, long);
- method public void putLong(long, long);
- method public void putLongVolatile(Object!, long, long);
- method public void putObject(Object!, long, Object!);
- method public void putObjectVolatile(Object!, long, Object!);
- method public void putOrderedInt(Object!, long, int);
- method public void putOrderedLong(Object!, long, long);
- method public void putOrderedObject(Object!, long, Object!);
- method public void putShort(Object!, long, short);
- method public void putShort(long, short);
- method public void setMemory(long, long, byte);
- method public void storeFence();
- method public void unpark(Object!);
- field public static final int INVALID_FIELD_OFFSET = -1; // 0xffffffff
- }
-
-}
-
diff --git a/performance/performance-unsafe/build.gradle b/performance/performance-unsafe/build.gradle
deleted file mode 100644
index 152abd0..0000000
--- a/performance/performance-unsafe/build.gradle
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2024 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.
- */
-
-import androidx.build.LibraryType
-
-plugins {
- id("AndroidXPlugin")
- id("java-library")
-}
-
-java {
- sourceCompatibility = JavaVersion.VERSION_1_8
- targetCompatibility = JavaVersion.VERSION_1_8
-}
-
-androidx {
- name = "Performance - Unsafe"
- type = LibraryType.PUBLISHED_LIBRARY
- inceptionYear = "2024"
- description = "Compile-time support for sun.misc.Unsafe."
-}
diff --git a/performance/performance-unsafe/src/main/java/sun/misc/Unsafe.java b/performance/performance-unsafe/src/main/java/sun/misc/Unsafe.java
deleted file mode 100644
index a45a360..0000000
--- a/performance/performance-unsafe/src/main/java/sun/misc/Unsafe.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright 2024 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 sun.misc;
-
-// This code is not generated but the name must follow exactly the original names, which violate
-// CheckStyle's rules (theUnsafe for instance).
-// CHECKSTYLE:OFF Generated code
-@SuppressWarnings({"JavaJniMissingFunction", "unused", "UnknownNullness"})
-public final class Unsafe {
- public static final int INVALID_FIELD_OFFSET = -1;
-
- private static final sun.misc.Unsafe THE_ONE;
- private static final sun.misc.Unsafe theUnsafe;
-
- static {
- THE_ONE = null;
- theUnsafe = null;
- }
-
- private Unsafe() {
- throw new RuntimeException();
- }
-
- public static sun.misc.Unsafe getUnsafe() {
- throw new RuntimeException();
- }
-
- public long objectFieldOffset(java.lang.reflect.Field field) {
- throw new RuntimeException();
- }
-
- public int arrayBaseOffset(java.lang.Class<?> clazz) {
- throw new RuntimeException();
- }
-
- public int arrayIndexScale(java.lang.Class<?> clazz) {
- throw new RuntimeException();
- }
-
- private static native int getArrayBaseOffsetForComponentType(
- java.lang.Class<?> componentClass);
-
- private static native int getArrayIndexScaleForComponentType(
- java.lang.Class<?> componentClass);
-
- public native boolean compareAndSwapInt(
- java.lang.Object obj, long offset, int expectedValue, int newValue);
-
- public native boolean compareAndSwapLong(
- java.lang.Object obj, long offset, long expectedValue, long newValue);
-
- public native boolean compareAndSwapObject(java.lang.Object obj, long offset,
- java.lang.Object expectedValue, java.lang.Object newValue);
-
- public native int getIntVolatile(java.lang.Object obj, long offset);
-
- public native void putIntVolatile(java.lang.Object obj, long offset, int newValue);
-
- public native long getLongVolatile(java.lang.Object obj, long offset);
-
- public native void putLongVolatile(java.lang.Object obj, long offset, long newValue);
-
- public native java.lang.Object getObjectVolatile(java.lang.Object obj, long offset);
-
- public native void putObjectVolatile(
- java.lang.Object obj, long offset, java.lang.Object newValue);
-
- public native int getInt(java.lang.Object obj, long offset);
-
- public native void putInt(java.lang.Object obj, long offset, int newValue);
-
- public native void putOrderedInt(java.lang.Object obj, long offset, int newValue);
-
- public native long getLong(java.lang.Object obj, long offset);
-
- public native void putLong(java.lang.Object obj, long offset, long newValue);
-
- public native void putOrderedLong(java.lang.Object obj, long offset, long newValue);
-
- public native java.lang.Object getObject(java.lang.Object obj, long offset);
-
- public native void putObject(java.lang.Object obj, long offset, java.lang.Object newValue);
-
- public native void putOrderedObject(
- java.lang.Object obj, long offset, java.lang.Object newValue);
-
- public native boolean getBoolean(java.lang.Object obj, long offset);
-
- public native void putBoolean(java.lang.Object obj, long offset, boolean newValue);
-
- public native byte getByte(java.lang.Object obj, long offset);
-
- public native void putByte(java.lang.Object obj, long offset, byte newValue);
-
- public native char getChar(java.lang.Object obj, long offset);
-
- public native void putChar(java.lang.Object obj, long offset, char newValue);
-
- public native short getShort(java.lang.Object obj, long offset);
-
- public native void putShort(java.lang.Object obj, long offset, short newValue);
-
- public native float getFloat(java.lang.Object obj, long offset);
-
- public native void putFloat(java.lang.Object obj, long offset, float newValue);
-
- public native double getDouble(java.lang.Object obj, long offset);
-
- public native void putDouble(java.lang.Object obj, long offset, double newValue);
-
- public void park(boolean absolute, long time) {
- throw new RuntimeException();
- }
-
- public void unpark(java.lang.Object obj) {
- throw new RuntimeException();
- }
-
- public native java.lang.Object allocateInstance(java.lang.Class<?> c);
-
- public native int addressSize();
-
- public native int pageSize();
-
- public native long allocateMemory(long bytes);
-
- public native void freeMemory(long address);
-
- public native void setMemory(long address, long bytes, byte value);
-
- public native byte getByte(long address);
-
- public native void putByte(long address, byte x);
-
- public native short getShort(long address);
-
- public native void putShort(long address, short x);
-
- public native char getChar(long address);
-
- public native void putChar(long address, char x);
-
- public native int getInt(long address);
-
- public native void putInt(long address, int x);
-
- public native long getLong(long address);
-
- public native void putLong(long address, long x);
-
- public native float getFloat(long address);
-
- public native void putFloat(long address, float x);
-
- public native double getDouble(long address);
-
- public native void putDouble(long address, double x);
-
- public native void copyMemoryToPrimitiveArray(
- long srcAddr, java.lang.Object dst, long dstOffset, long bytes);
-
- public native void copyMemoryFromPrimitiveArray(
- java.lang.Object src, long srcOffset, long dstAddr, long bytes);
-
- public native void copyMemory(long srcAddr, long dstAddr, long bytes);
-
- public int getAndAddInt(java.lang.Object o, long offset, int delta) {
- throw new RuntimeException();
- }
-
- public long getAndAddLong(java.lang.Object o, long offset, long delta) {
- throw new RuntimeException();
- }
-
- public int getAndSetInt(java.lang.Object o, long offset, int newValue) {
- throw new RuntimeException();
- }
-
- public long getAndSetLong(java.lang.Object o, long offset, long newValue) {
- throw new RuntimeException();
- }
-
- public java.lang.Object getAndSetObject(
- java.lang.Object o, long offset, java.lang.Object newValue) {
- throw new RuntimeException();
- }
-
- public native void loadFence();
-
- public native void storeFence();
-
- public native void fullFence();
-}
diff --git a/performance/performance-unsafe/src/main/resources/META-INF/proguard/unsafe.pro b/performance/performance-unsafe/src/main/resources/META-INF/proguard/unsafe.pro
deleted file mode 100644
index d908c40..0000000
--- a/performance/performance-unsafe/src/main/resources/META-INF/proguard/unsafe.pro
+++ /dev/null
@@ -1,3 +0,0 @@
--keep class sun.misc.Unsafe {
- *;
-}
diff --git a/preference/preference/src/main/res/layout/preference_list_fragment.xml b/preference/preference/src/main/res/layout/preference_list_fragment.xml
index ad03481..c2ae115 100644
--- a/preference/preference/src/main/res/layout/preference_list_fragment.xml
+++ b/preference/preference/src/main/res/layout/preference_list_fragment.xml
@@ -19,7 +19,6 @@
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="NewApi"
android:orientation="vertical"
- android:fitsSystemWindows="true"
android:layout_height="match_parent"
android:layout_width="match_parent" >
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/MultiTypedPagingSourceTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/MultiTypedPagingSourceTest.kt
index 37efc13..2460822 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/MultiTypedPagingSourceTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/MultiTypedPagingSourceTest.kt
@@ -142,7 +142,7 @@
.containsExactlyElementsIn(
items.createExpected(
// Paging 3 implementation loads starting from initial key
- fromIndex = 98,
+ fromIndex = 91,
toIndex = 100
)
)
@@ -579,7 +579,7 @@
.containsExactlyElementsIn(
items.createExpected(
// Paging 3 implementation loads starting from initial key
- fromIndex = 98,
+ fromIndex = 91,
toIndex = 100
)
)
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
index 1feb79e..5d6cd19 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
@@ -155,6 +155,21 @@
kotlin = com.squareup.kotlinpoet.ENUM
)
+ @JvmField val STRING = String::class.asClassName()
+ @JvmField val ITERABLE = Iterable::class.asClassName()
+ @JvmField val COLLECTION = Collection::class.asClassName()
+ @JvmField val LIST = List::class.asClassName()
+ @JvmField val SET = Set::class.asClassName()
+ @JvmField val MAP = Map::class.asClassName()
+ @JvmField val MAP_ENTRY = Map.Entry::class.asClassName()
+
+ @JvmField val MUTABLE_ITERABLE = Iterable::class.asMutableClassName()
+ @JvmField val MUTABLE_COLLECTION = Collection::class.asMutableClassName()
+ @JvmField val MUTABLE_LIST = List::class.asMutableClassName()
+ @JvmField val MUTABLE_SET = Set::class.asMutableClassName()
+ @JvmField val MUTABLE_MAP = Map::class.asMutableClassName()
+ @JvmField val MUTABLE_MAP_ENTRY = Map.Entry::class.asMutableClassName()
+
@JvmField val PRIMITIVE_BOOLEAN = Boolean::class.asPrimitiveTypeName()
@JvmField val PRIMITIVE_BYTE = Byte::class.asPrimitiveTypeName()
@JvmField val PRIMITIVE_SHORT = Short::class.asPrimitiveTypeName()
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/codegen/XTypeNameTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/codegen/XTypeNameTest.kt
index 0dc26d9..c987304 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/codegen/XTypeNameTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/codegen/XTypeNameTest.kt
@@ -21,6 +21,7 @@
import androidx.room.compiler.processing.KnownTypeNames
import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.getDeclaredField
import androidx.room.compiler.processing.util.getField
import androidx.room.compiler.processing.util.getMethodByJvmName
import androidx.room.compiler.processing.util.runProcessorTest
@@ -351,4 +352,42 @@
}
}
}
+
+ @Test
+ fun testInteropTypes() {
+ fun testIsTypeOf(type: String, typeName: XTypeName) {
+ runProcessorTest(
+ listOf(
+ Source.kotlin(
+ "KotlinSubject.kt",
+ """
+ class KotlinSubject {
+ val field: $type = TODO()
+ }
+ """
+ .trimIndent()
+ )
+ )
+ ) { invocation ->
+ val subject = invocation.processingEnv.requireTypeElement("KotlinSubject")
+ val field = subject.getDeclaredField("field")
+ assertThat(field.type.rawType.asTypeName()).isEqualTo(typeName)
+ }
+ }
+
+ testIsTypeOf("String", XTypeName.STRING)
+ testIsTypeOf("Iterable<Unit>", XTypeName.ITERABLE)
+ testIsTypeOf("MutableIterable<Unit>", XTypeName.MUTABLE_ITERABLE)
+ testIsTypeOf("Collection<Unit>", XTypeName.COLLECTION)
+ testIsTypeOf("MutableCollection<Unit>", XTypeName.MUTABLE_COLLECTION)
+ testIsTypeOf("Set<Unit>", XTypeName.SET)
+ testIsTypeOf("MutableSet<Unit>", XTypeName.MUTABLE_SET)
+ testIsTypeOf("List<Unit>", XTypeName.LIST)
+ testIsTypeOf("MutableList<Unit>", XTypeName.MUTABLE_LIST)
+ testIsTypeOf("Map<Unit, Unit>", XTypeName.MAP)
+ testIsTypeOf("MutableMap<Unit, Unit>", XTypeName.MUTABLE_MAP)
+ testIsTypeOf("Map.Entry<Unit, Unit>", XTypeName.MAP_ENTRY)
+ // Uncomment after kotlinpoet bug is fixed: https://github.com/square/kotlinpoet/issues/2060
+ // testIsTypeOf("MutableMap.MutableEntry<Unit, Unit>", XTypeName.MUTABLE_MAP_ENTRY)
+ }
}
diff --git a/room/room-paging/src/androidInstrumentedTest/kotlin/androidx/room/paging/LimitOffsetPagingSourceTest.kt b/room/room-paging/src/androidInstrumentedTest/kotlin/androidx/room/paging/LimitOffsetPagingSourceTest.kt
index 0cd7d50..e72e6c8 100644
--- a/room/room-paging/src/androidInstrumentedTest/kotlin/androidx/room/paging/LimitOffsetPagingSourceTest.kt
+++ b/room/room-paging/src/androidInstrumentedTest/kotlin/androidx/room/paging/LimitOffsetPagingSourceTest.kt
@@ -247,8 +247,9 @@
dao.addAllItems(ITEMS_LIST)
val result = pager.refresh(initialKey = 40) as LoadResult.Page
- // initial loadSize = 15, but limited by id < 50, should only load items 40 - 50
- assertThat(result.data).containsExactlyElementsIn(ITEMS_LIST.subList(40, 50))
+ // initial loadSize = 15, but limited by id < 50, should treat 50 as end and
+ // load items 35 - 50
+ assertThat(result.data).containsExactlyElementsIn(ITEMS_LIST.subList(35, 50))
// should have 50 items fulfilling condition of id < 50 (TestItem id 0 - 49)
assertThat(pagingSource.itemCount).isEqualTo(50)
}
@@ -575,6 +576,36 @@
assertThat(result.nextKey).isEqualTo(null)
}
+ @Test
+ fun load_refreshKeyOnLastPage() = runPagingSourceTest { pager, _ ->
+ dao.addAllItems(ITEMS_LIST)
+ pager.refresh(initialKey = 70)
+ dao.deleteTestItems(80, 100)
+
+ // assume user was viewing last item of the refresh load with anchorPosition = 85,
+ // initialLoadSize = 15. This mimics how getRefreshKey() calculates refresh key.
+ val refreshKey = 85 - (15 / 2)
+
+ val pagingSource2 = LimitOffsetPagingSourceImpl(database)
+ val pager2 = TestPager(CONFIG, pagingSource2)
+ val result = pager2.refresh(initialKey = refreshKey) as LoadResult.Page
+
+ // database should only have 80 items left. Refresh key should be moved back at this point
+ // to ensure a full load. (greater than item count - loadSize after deletion)
+ assertThat(pagingSource2.itemCount).isEqualTo(80)
+ // ensure that paging source can handle invalid refresh key properly
+ // should load last page with items 65 - 80
+ assertThat(result.data).containsExactlyElementsIn(ITEMS_LIST.subList(65, 80))
+
+ // should account for updated item count to return correct itemsBefore, itemsAfter,
+ // prevKey, nextKey
+ assertThat(result.itemsBefore).isEqualTo(65)
+ assertThat(result.itemsAfter).isEqualTo(0)
+ // no append can be triggered
+ assertThat(result.prevKey).isEqualTo(65)
+ assertThat(result.nextKey).isEqualTo(null)
+ }
+
/**
* Tests the behavior if user was viewing items in the top of the database and those items were
* deleted.
diff --git a/room/room-paging/src/commonMain/kotlin/androidx/room/paging/util/RoomPagingUtil.kt b/room/room-paging/src/commonMain/kotlin/androidx/room/paging/util/RoomPagingUtil.kt
index d08a210..d700b6c 100644
--- a/room/room-paging/src/commonMain/kotlin/androidx/room/paging/util/RoomPagingUtil.kt
+++ b/room/room-paging/src/commonMain/kotlin/androidx/room/paging/util/RoomPagingUtil.kt
@@ -83,7 +83,7 @@
}
is Append -> key
is Refresh ->
- if (key >= itemCount) {
+ if (key >= itemCount - params.loadSize) {
maxOf(0, itemCount - params.loadSize)
} else {
key
diff --git a/security/security-state/src/main/java/androidx/security/state/SecurityPatchState.kt b/security/security-state/src/main/java/androidx/security/state/SecurityPatchState.kt
index 108bd57..455fe21 100644
--- a/security/security-state/src/main/java/androidx/security/state/SecurityPatchState.kt
+++ b/security/security-state/src/main/java/androidx/security/state/SecurityPatchState.kt
@@ -95,9 +95,6 @@
/** Kernel component providing kernel version as VersionedSpl. */
public const val COMPONENT_KERNEL: String = "KERNEL"
- /** WebView component providing default WebView provider version as VersionedSpl. */
- internal const val COMPONENT_WEBVIEW: String = "WEBVIEW"
-
/**
* Vendor component providing ro.vendor.build.security_patch property value as DateBasedSpl.
*/
@@ -117,7 +114,6 @@
COMPONENT_SYSTEM_MODULES,
COMPONENT_KERNEL,
COMPONENT_VENDOR,
- COMPONENT_WEBVIEW,
]
)
internal annotation class Component
@@ -572,9 +568,6 @@
DateBasedSecurityPatchLevel.fromString(vendorSpl)
}
-
- // TODO(musashi): Add support for webview package
- COMPONENT_WEBVIEW -> TODO()
else -> throw IllegalArgumentException("Unknown component: $component")
}
}
@@ -614,9 +607,6 @@
)
}
COMPONENT_KERNEL -> getPublishedKernelVersions()
-
- // TODO(musashi): Add support for webview package
- COMPONENT_WEBVIEW -> TODO()
else -> throw IllegalArgumentException("Unknown component: $component")
}
}
@@ -740,8 +730,7 @@
// These components are expected to use DateBasedSpl
DateBasedSecurityPatchLevel.fromString(securityPatchLevel)
}
- COMPONENT_KERNEL,
- COMPONENT_WEBVIEW -> {
+ COMPONENT_KERNEL -> {
// These components are expected to use VersionedSpl
VersionedSecurityPatchLevel.fromString(securityPatchLevel)
}
@@ -767,13 +756,10 @@
COMPONENT_SYSTEM_MODULES,
COMPONENT_VENDOR,
COMPONENT_KERNEL,
- COMPONENT_WEBVIEW
)
components.forEach { component ->
if (component == COMPONENT_VENDOR && !USE_VENDOR_SPL) return@forEach
- // TODO(musashi): Unblock once support for WebView is present.
- if (component == COMPONENT_WEBVIEW) return@forEach
val deviceSpl =
try {
getDeviceSecurityPatchLevel(component)
diff --git a/security/security-state/src/test/java/androidx/security/state/SecurityPatchStateTest.kt b/security/security-state/src/test/java/androidx/security/state/SecurityPatchStateTest.kt
index 28a6576..440fd0f6 100644
--- a/security/security-state/src/test/java/androidx/security/state/SecurityPatchStateTest.kt
+++ b/security/security-state/src/test/java/androidx/security/state/SecurityPatchStateTest.kt
@@ -622,7 +622,7 @@
fun testGetPatchedCves_ThrowsExceptionForInvalidComponent() {
val spl = SecurityPatchState.DateBasedSecurityPatchLevel.fromString("2023-01-01")
- securityState.getPatchedCves(SecurityPatchState.COMPONENT_WEBVIEW, spl)
+ securityState.getPatchedCves(SecurityPatchState.COMPONENT_KERNEL, spl)
}
@Test
diff --git a/settings.gradle b/settings.gradle
index 3de9fb9..b12b850 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -899,7 +899,6 @@
includeProject(":pdf:pdf-viewer-fragment", [BuildType.MAIN])
includeProject(":percentlayout:percentlayout", [BuildType.MAIN])
includeProject(":performance:performance-annotation", [BuildType.MAIN, BuildType.COMPOSE, BuildType.KMP])
-includeProject(":performance:performance-unsafe", [BuildType.MAIN, BuildType.COMPOSE, BuildType.KMP])
includeProject(":preference:preference", [BuildType.MAIN])
includeProject(":preference:preference-ktx", [BuildType.MAIN])
includeProject(":print:print", [BuildType.MAIN])
diff --git a/transition/OWNERS b/transition/OWNERS
index 838fb4a..c7f4a77 100644
--- a/transition/OWNERS
+++ b/transition/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 461464
[email protected]
[email protected]
\ No newline at end of file
diff --git a/vectordrawable/integration-tests/testapp/OWNERS b/vectordrawable/integration-tests/testapp/OWNERS
index f2f8b9c..a25bfcd 100644
--- a/vectordrawable/integration-tests/testapp/OWNERS
+++ b/vectordrawable/integration-tests/testapp/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 461333
[email protected]
[email protected]
diff --git a/vectordrawable/vectordrawable-seekable/OWNERS b/vectordrawable/vectordrawable-seekable/OWNERS
index f2f8b9c..a25bfcd 100644
--- a/vectordrawable/vectordrawable-seekable/OWNERS
+++ b/vectordrawable/vectordrawable-seekable/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 461333
[email protected]
[email protected]
diff --git a/wear/compose/compose-material3/api/current.txt b/wear/compose/compose-material3/api/current.txt
index 5cca753..df002aa 100644
--- a/wear/compose/compose-material3/api/current.txt
+++ b/wear/compose/compose-material3/api/current.txt
@@ -166,9 +166,10 @@
method @androidx.compose.runtime.Composable public static void ButtonGroup(optional androidx.compose.ui.Modifier modifier, optional float spacing, optional float expansionWidth, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material3.ButtonGroupScope,kotlin.Unit> content);
}
- public final class ButtonGroupScope {
- ctor public ButtonGroupScope();
- method public boolean buttonGroupItem(androidx.compose.foundation.interaction.InteractionSource interactionSource, optional float minWidth, optional float weight, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ public interface ButtonGroupScope {
+ method public androidx.compose.ui.Modifier enlargeOnPress(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
+ method public androidx.compose.ui.Modifier minWidth(androidx.compose.ui.Modifier, optional float minWidth);
+ method public androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, @FloatRange(from=0.0, fromInclusive=false) float weight);
}
public final class ButtonKt {
@@ -477,7 +478,7 @@
}
public final class DatePickerKt {
- method @androidx.compose.runtime.Composable public static void DatePicker(java.time.LocalDate initialDate, kotlin.jvm.functions.Function1<? super java.time.LocalDate,kotlin.Unit> onDatePicked, optional androidx.compose.ui.Modifier modifier, optional java.time.LocalDate? minValidDate, optional java.time.LocalDate? maxValidDate, optional int datePickerType, optional androidx.wear.compose.material3.DatePickerColors colors);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) @androidx.compose.runtime.Composable public static void DatePicker(java.time.LocalDate initialDate, kotlin.jvm.functions.Function1<? super java.time.LocalDate,kotlin.Unit> onDatePicked, optional androidx.compose.ui.Modifier modifier, optional java.time.LocalDate? minValidDate, optional java.time.LocalDate? maxValidDate, optional int datePickerType, optional androidx.wear.compose.material3.DatePickerColors colors);
}
@androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class DatePickerType {
@@ -732,9 +733,9 @@
method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
method @androidx.compose.runtime.Composable public long getSubHeaderContentColor();
method public androidx.compose.foundation.layout.PaddingValues getSubHeaderContentPadding();
- property @androidx.compose.runtime.Composable public final long ContentColor;
property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
property public final androidx.compose.foundation.layout.PaddingValues SubHeaderContentPadding;
+ property @androidx.compose.runtime.Composable public final long contentColor;
property @androidx.compose.runtime.Composable public final long subHeaderContentColor;
field public static final androidx.wear.compose.material3.ListHeaderDefaults INSTANCE;
}
@@ -1122,9 +1123,11 @@
}
public final class SliderDefaults {
- method @androidx.compose.runtime.Composable public void DecreaseIcon(optional androidx.compose.ui.Modifier modifier);
- method @androidx.compose.runtime.Composable public void IncreaseIcon(optional androidx.compose.ui.Modifier modifier);
+ method @androidx.compose.runtime.Composable public void DecreaseIcon(optional androidx.compose.ui.Modifier modifier, optional String contentDescription);
+ method @androidx.compose.runtime.Composable public void IncreaseIcon(optional androidx.compose.ui.Modifier modifier, optional String contentDescription);
+ method @androidx.compose.runtime.Composable public String getDecreaseIconContentDescription();
method public float getIconSize();
+ method @androidx.compose.runtime.Composable public String getIncreaseIconContentDescription();
method public int getMaxSegmentSteps();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.SliderColors sliderColors();
@@ -1133,6 +1136,8 @@
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.SliderColors variantSliderColors(optional long containerColor, optional long buttonIconColor, optional long selectedBarColor, optional long unselectedBarColor, optional long selectedBarSeparatorColor, optional long unselectedBarSeparatorColor, optional long disabledContainerColor, optional long disabledButtonIconColor, optional long disabledSelectedBarColor, optional long disabledUnselectedBarColor, optional long disabledSelectedBarSeparatorColor, optional long disabledUnselectedBarSeparatorColor);
property public final float IconSize;
property public final int MaxSegmentSteps;
+ property @androidx.compose.runtime.Composable public final String decreaseIconContentDescription;
+ property @androidx.compose.runtime.Composable public final String increaseIconContentDescription;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape shape;
field public static final androidx.wear.compose.material3.SliderDefaults INSTANCE;
}
@@ -1622,7 +1627,7 @@
}
public final class TimePickerKt {
- method @androidx.compose.runtime.Composable public static void TimePicker(java.time.LocalTime initialTime, kotlin.jvm.functions.Function1<? super java.time.LocalTime,kotlin.Unit> onTimePicked, optional androidx.compose.ui.Modifier modifier, optional int timePickerType, optional androidx.wear.compose.material3.TimePickerColors colors);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) @androidx.compose.runtime.Composable public static void TimePicker(java.time.LocalTime initialTime, kotlin.jvm.functions.Function1<? super java.time.LocalTime,kotlin.Unit> onTimePicked, optional androidx.compose.ui.Modifier modifier, optional int timePickerType, optional androidx.wear.compose.material3.TimePickerColors colors);
}
@androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class TimePickerType {
diff --git a/wear/compose/compose-material3/api/restricted_current.txt b/wear/compose/compose-material3/api/restricted_current.txt
index 5cca753..df002aa 100644
--- a/wear/compose/compose-material3/api/restricted_current.txt
+++ b/wear/compose/compose-material3/api/restricted_current.txt
@@ -166,9 +166,10 @@
method @androidx.compose.runtime.Composable public static void ButtonGroup(optional androidx.compose.ui.Modifier modifier, optional float spacing, optional float expansionWidth, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material3.ButtonGroupScope,kotlin.Unit> content);
}
- public final class ButtonGroupScope {
- ctor public ButtonGroupScope();
- method public boolean buttonGroupItem(androidx.compose.foundation.interaction.InteractionSource interactionSource, optional float minWidth, optional float weight, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ public interface ButtonGroupScope {
+ method public androidx.compose.ui.Modifier enlargeOnPress(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
+ method public androidx.compose.ui.Modifier minWidth(androidx.compose.ui.Modifier, optional float minWidth);
+ method public androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, @FloatRange(from=0.0, fromInclusive=false) float weight);
}
public final class ButtonKt {
@@ -477,7 +478,7 @@
}
public final class DatePickerKt {
- method @androidx.compose.runtime.Composable public static void DatePicker(java.time.LocalDate initialDate, kotlin.jvm.functions.Function1<? super java.time.LocalDate,kotlin.Unit> onDatePicked, optional androidx.compose.ui.Modifier modifier, optional java.time.LocalDate? minValidDate, optional java.time.LocalDate? maxValidDate, optional int datePickerType, optional androidx.wear.compose.material3.DatePickerColors colors);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) @androidx.compose.runtime.Composable public static void DatePicker(java.time.LocalDate initialDate, kotlin.jvm.functions.Function1<? super java.time.LocalDate,kotlin.Unit> onDatePicked, optional androidx.compose.ui.Modifier modifier, optional java.time.LocalDate? minValidDate, optional java.time.LocalDate? maxValidDate, optional int datePickerType, optional androidx.wear.compose.material3.DatePickerColors colors);
}
@androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class DatePickerType {
@@ -732,9 +733,9 @@
method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
method @androidx.compose.runtime.Composable public long getSubHeaderContentColor();
method public androidx.compose.foundation.layout.PaddingValues getSubHeaderContentPadding();
- property @androidx.compose.runtime.Composable public final long ContentColor;
property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
property public final androidx.compose.foundation.layout.PaddingValues SubHeaderContentPadding;
+ property @androidx.compose.runtime.Composable public final long contentColor;
property @androidx.compose.runtime.Composable public final long subHeaderContentColor;
field public static final androidx.wear.compose.material3.ListHeaderDefaults INSTANCE;
}
@@ -1122,9 +1123,11 @@
}
public final class SliderDefaults {
- method @androidx.compose.runtime.Composable public void DecreaseIcon(optional androidx.compose.ui.Modifier modifier);
- method @androidx.compose.runtime.Composable public void IncreaseIcon(optional androidx.compose.ui.Modifier modifier);
+ method @androidx.compose.runtime.Composable public void DecreaseIcon(optional androidx.compose.ui.Modifier modifier, optional String contentDescription);
+ method @androidx.compose.runtime.Composable public void IncreaseIcon(optional androidx.compose.ui.Modifier modifier, optional String contentDescription);
+ method @androidx.compose.runtime.Composable public String getDecreaseIconContentDescription();
method public float getIconSize();
+ method @androidx.compose.runtime.Composable public String getIncreaseIconContentDescription();
method public int getMaxSegmentSteps();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.SliderColors sliderColors();
@@ -1133,6 +1136,8 @@
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.SliderColors variantSliderColors(optional long containerColor, optional long buttonIconColor, optional long selectedBarColor, optional long unselectedBarColor, optional long selectedBarSeparatorColor, optional long unselectedBarSeparatorColor, optional long disabledContainerColor, optional long disabledButtonIconColor, optional long disabledSelectedBarColor, optional long disabledUnselectedBarColor, optional long disabledSelectedBarSeparatorColor, optional long disabledUnselectedBarSeparatorColor);
property public final float IconSize;
property public final int MaxSegmentSteps;
+ property @androidx.compose.runtime.Composable public final String decreaseIconContentDescription;
+ property @androidx.compose.runtime.Composable public final String increaseIconContentDescription;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape shape;
field public static final androidx.wear.compose.material3.SliderDefaults INSTANCE;
}
@@ -1622,7 +1627,7 @@
}
public final class TimePickerKt {
- method @androidx.compose.runtime.Composable public static void TimePicker(java.time.LocalTime initialTime, kotlin.jvm.functions.Function1<? super java.time.LocalTime,kotlin.Unit> onTimePicked, optional androidx.compose.ui.Modifier modifier, optional int timePickerType, optional androidx.wear.compose.material3.TimePickerColors colors);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) @androidx.compose.runtime.Composable public static void TimePicker(java.time.LocalTime initialTime, kotlin.jvm.functions.Function1<? super java.time.LocalTime,kotlin.Unit> onTimePicked, optional androidx.compose.ui.Modifier modifier, optional int timePickerType, optional androidx.wear.compose.material3.TimePickerColors colors);
}
@androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class TimePickerType {
diff --git a/wear/compose/compose-material3/benchmark/build.gradle b/wear/compose/compose-material3/benchmark/build.gradle
index 9a1b2f5..836aa4e 100644
--- a/wear/compose/compose-material3/benchmark/build.gradle
+++ b/wear/compose/compose-material3/benchmark/build.gradle
@@ -35,7 +35,7 @@
compileSdk = 35
defaultConfig {
- minSdk = 30
+ minSdk = 25
}
buildTypes.configureEach {
consumerProguardFiles "benchmark-proguard-rules.pro"
diff --git a/wear/compose/compose-material3/build.gradle b/wear/compose/compose-material3/build.gradle
index df4dcfa..f0c49b7 100644
--- a/wear/compose/compose-material3/build.gradle
+++ b/wear/compose/compose-material3/build.gradle
@@ -72,7 +72,7 @@
compileSdk = 35
defaultConfig {
- minSdk = 30
+ minSdk = 25
}
// Use Robolectric 4.+
testOptions.unitTests.includeAndroidResources = true
diff --git a/wear/compose/compose-material3/integration-tests/build.gradle b/wear/compose/compose-material3/integration-tests/build.gradle
index 85e2742..956afb7 100644
--- a/wear/compose/compose-material3/integration-tests/build.gradle
+++ b/wear/compose/compose-material3/integration-tests/build.gradle
@@ -55,7 +55,7 @@
android {
compileSdk = 35
defaultConfig {
- minSdk = 30
+ minSdk = 25
}
namespace = "androidx.wear.compose.material3.demos"
}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ButtonGroupDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ButtonGroupDemo.kt
index a12ca7e..76c8ba5 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ButtonGroupDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ButtonGroupDemo.kt
@@ -18,33 +18,161 @@
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
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.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.ButtonGroup
+import androidx.wear.compose.material3.ButtonGroupScope
+import androidx.wear.compose.material3.FilledIconButton
+import androidx.wear.compose.material3.Icon
+import androidx.wear.compose.material3.IconToggleButton
+import androidx.wear.compose.material3.IconToggleButtonDefaults
+import androidx.wear.compose.material3.IconToggleButtonShapes
import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.TextToggleButton
+import androidx.wear.compose.material3.TextToggleButtonDefaults
+import androidx.wear.compose.material3.TextToggleButtonShapes
+import androidx.wear.compose.material3.samples.icons.WifiOffIcon
+import androidx.wear.compose.material3.samples.icons.WifiOnIcon
@Composable
fun ButtonGroupDemo() {
- val interactionSources = remember { Array(3) { MutableInteractionSource() } }
-
+ val interactionSource1 = remember { MutableInteractionSource() }
+ val interactionSource2 = remember { MutableInteractionSource() }
+ val interactionSource3 = remember { MutableInteractionSource() }
Box(Modifier.size(300.dp), contentAlignment = Alignment.Center) {
ButtonGroup(Modifier.fillMaxWidth()) {
- repeat(3) { index ->
- buttonGroupItem(interactionSource = interactionSources[index]) {
- Button(onClick = {}, interactionSource = interactionSources[index]) {
- Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
- Text(listOf("A", "B", "C")[index])
- }
+ Button(
+ onClick = {},
+ Modifier.enlargeOnPress(interactionSource1),
+ interactionSource = interactionSource1
+ ) {
+ Text("<", modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
+ }
+ FilledIconButton(
+ onClick = {},
+ Modifier.enlargeOnPress(interactionSource2),
+ interactionSource = interactionSource2
+ ) {
+ Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Icon(
+ imageVector = Icons.Filled.Favorite,
+ contentDescription = "Favorite icon",
+ modifier = Modifier.size(32.dp)
+ )
+ }
+ }
+ Button(
+ onClick = {},
+ Modifier.enlargeOnPress(interactionSource3),
+ interactionSource = interactionSource3
+ ) {
+ Text(">", modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
+ }
+ }
+ }
+}
+
+@Composable
+fun ButtonGroupToggleButtonsDemo() {
+ val iconSize = 32.dp
+ Box(Modifier.size(300.dp), contentAlignment = Alignment.Center) {
+ Column {
+ ButtonGroup(Modifier.fillMaxWidth()) {
+ MyIconToggleButton(IconToggleButtonDefaults.shapes(), Modifier.weight(1.2f)) {
+ Icon(
+ imageVector = Icons.Filled.Favorite,
+ contentDescription = "Favorite icon",
+ modifier = Modifier.size(iconSize)
+ )
+ }
+ MyIconToggleButton(IconToggleButtonDefaults.animatedShapes()) { checked ->
+ if (checked) {
+ WifiOnIcon(Modifier.size(iconSize))
+ } else {
+ WifiOffIcon(Modifier.size(iconSize))
}
}
}
+ Spacer(Modifier.height(8.dp))
+ ButtonGroup(Modifier.fillMaxWidth()) {
+ MyTextToggleButton(TextToggleButtonDefaults.shapes()) { checked ->
+ Text(
+ text = if (checked) "On" else "Off",
+ style = TextToggleButtonDefaults.defaultButtonTextStyle
+ )
+ }
+ MyTextToggleButton(
+ TextToggleButtonDefaults.animatedShapes(),
+ Modifier.weight(1.2f)
+ ) { checked ->
+ Text(
+ text = if (checked) "On" else "Off",
+ style = TextToggleButtonDefaults.defaultButtonTextStyle
+ )
+ }
+ }
}
}
}
+
+@Composable
+private fun ButtonGroupScope.MyIconToggleButton(
+ shapes: IconToggleButtonShapes,
+ modifier: Modifier = Modifier,
+ content: @Composable (Boolean) -> Unit
+) {
+ val interactionSource = remember { MutableInteractionSource() }
+ var checked by remember { mutableStateOf(false) }
+ IconToggleButton(
+ checked = checked,
+ modifier =
+ modifier
+ .height(IconToggleButtonDefaults.SmallButtonSize)
+ .fillMaxWidth()
+ .enlargeOnPress(interactionSource),
+ onCheckedChange = { checked = !checked },
+ shapes = shapes,
+ interactionSource = interactionSource
+ ) {
+ content(checked)
+ }
+}
+
+@Composable
+private fun ButtonGroupScope.MyTextToggleButton(
+ shapes: TextToggleButtonShapes,
+ modifier: Modifier = Modifier,
+ content: @Composable (Boolean) -> Unit
+) {
+ val interactionSource = remember { MutableInteractionSource() }
+ var checked by remember { mutableStateOf(false) }
+ TextToggleButton(
+ checked = checked,
+ modifier =
+ modifier
+ .height(TextToggleButtonDefaults.DefaultButtonSize)
+ .fillMaxWidth()
+ .enlargeOnPress(interactionSource),
+ onCheckedChange = { checked = !checked },
+ shapes = shapes,
+ interactionSource = interactionSource
+ ) {
+ content(checked)
+ }
+}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/DatePickerDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/DatePickerDemo.kt
index 0c304d1..2e020d7 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/DatePickerDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/DatePickerDemo.kt
@@ -16,6 +16,8 @@
package androidx.wear.compose.material3.demos
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.icons.Icons
@@ -34,23 +36,25 @@
import androidx.wear.compose.material3.DatePickerType
import androidx.wear.compose.material3.Icon
import androidx.wear.compose.material3.Text
-import androidx.wear.compose.material3.samples.DatePickerMinDateMaxDateSample
+import androidx.wear.compose.material3.samples.DatePickerFutureOnlySample
import androidx.wear.compose.material3.samples.DatePickerSample
import androidx.wear.compose.material3.samples.DatePickerYearMonthDaySample
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
+@RequiresApi(Build.VERSION_CODES.O)
val DatePickerDemos =
listOf(
ComposableDemo("Date Year-Month-Day") { DatePickerYearMonthDaySample() },
ComposableDemo("Date Month-Day-Year") { DatePickerDemo(DatePickerType.MonthDayYear) },
ComposableDemo("Date Day-Month-Year") { DatePickerDemo(DatePickerType.DayMonthYear) },
ComposableDemo("Date System date format") { DatePickerSample() },
- ComposableDemo("Date Range") { DatePickerMinDateMaxDateSample() },
+ ComposableDemo("Future only") { DatePickerFutureOnlySample() },
ComposableDemo("Past only") { DatePickerPastOnlyDemo() },
)
+@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun DatePickerDemo(datePickerType: DatePickerType) {
var showDatePicker by remember { mutableStateOf(true) }
@@ -85,6 +89,7 @@
}
}
+@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun DatePickerPastOnlyDemo() {
val currentDate = LocalDate.now()
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/EdgeButtonDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/EdgeButtonDemo.kt
index a82e444..20ff2d3 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/EdgeButtonDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/EdgeButtonDemo.kt
@@ -40,7 +40,9 @@
import androidx.compose.ui.unit.dp
import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
+import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
+import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState
import androidx.wear.compose.integration.demos.common.AdaptiveScreen
import androidx.wear.compose.material3.ButtonDefaults
import androidx.wear.compose.material3.Card
@@ -54,18 +56,21 @@
import androidx.wear.compose.material3.TextButtonDefaults
import androidx.wear.compose.material3.samples.icons.CheckIcon
+fun listOfLabels(): List<String> {
+ return listOf(
+ "Hi",
+ "Hello World",
+ "Hello world again?",
+ "More content as we add stuff",
+ "I don't know if this will fit now, testing",
+ "Really long text that it's going to take multiple lines",
+ "And now we are really pushing it because the screen is really small",
+ )
+}
+
@Composable
fun EdgeButtonBelowLazyColumnDemo() {
- val labels =
- listOf(
- "Hi",
- "Hello World",
- "Hello world again?",
- "More content as we add stuff",
- "I don't know if this will fit now, testing",
- "Really long text that it's going to take multiple lines",
- "And now we are really pushing it because the screen is really small",
- )
+ val labels = listOfLabels()
val selectedLabel = remember { mutableIntStateOf(0) }
AdaptiveScreen {
val state = rememberLazyListState()
@@ -104,16 +109,7 @@
@Composable
fun EdgeButtonBelowScalingLazyColumnDemo() {
- val labels =
- listOf(
- "Hi",
- "Hello World",
- "Hello world again?",
- "More content as we add stuff",
- "I don't know if this will fit now, testing",
- "Really long text that it's going to take multiple lines",
- "And now we are really pushing it because the screen is really small",
- )
+ val labels = listOfLabels()
val selectedLabel = remember { mutableIntStateOf(0) }
AdaptiveScreen {
@@ -151,6 +147,45 @@
}
}
+@Composable
+fun EdgeButtonBelowTransformingLazyColumnDemo() {
+ val labels = listOfLabels()
+ val selectedLabel = remember { mutableIntStateOf(0) }
+ AdaptiveScreen {
+ val state = rememberTransformingLazyColumnState()
+ ScreenScaffold(
+ scrollState = state,
+ contentPadding = PaddingValues(horizontal = 10.dp, vertical = 20.dp),
+ edgeButton = {
+ EdgeButton(
+ onClick = {},
+ buttonSize = EdgeButtonSize.Large,
+ colors = ButtonDefaults.buttonColors(containerColor = Color.DarkGray)
+ ) {
+ Text(labels[selectedLabel.intValue], color = Color.White)
+ }
+ }
+ ) { contentPadding ->
+ TransformingLazyColumn(
+ state = state,
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ contentPadding = contentPadding,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ items(labels.size) {
+ Card(
+ onClick = { selectedLabel.intValue = it },
+ modifier = Modifier.fillMaxWidth(0.9f)
+ ) {
+ Text(labels[it])
+ }
+ }
+ }
+ }
+ }
+}
+
@Suppress("PrimitiveInCollection")
@Composable
fun EdgeButtonMultiDemo() {
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/IconToggleButtonDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/IconToggleButtonDemo.kt
index aa3196d..9ac30f1 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/IconToggleButtonDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/IconToggleButtonDemo.kt
@@ -20,8 +20,6 @@
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Favorite
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -32,7 +30,6 @@
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
-import androidx.wear.compose.material3.Icon
import androidx.wear.compose.material3.IconToggleButton
import androidx.wear.compose.material3.IconToggleButtonDefaults
import androidx.wear.compose.material3.ListHeader
@@ -235,11 +232,11 @@
modifier = Modifier.touchTargetAwareSize(size),
onCheckedChange = { checked = !checked }
) {
- Icon(
- imageVector = Icons.Filled.Favorite,
- contentDescription = "Favorite icon",
- modifier = Modifier.size(IconToggleButtonDefaults.iconSizeFor(size))
- )
+ if (checked) {
+ WifiOnIcon(Modifier.size(IconToggleButtonDefaults.iconSizeFor(size)))
+ } else {
+ WifiOffIcon(Modifier.size(IconToggleButtonDefaults.iconSizeFor(size)))
+ }
}
}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ScrollIndicatorDemos.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ScrollIndicatorDemos.kt
index 4ab5d71..29ea93c9 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ScrollIndicatorDemos.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ScrollIndicatorDemos.kt
@@ -20,9 +20,11 @@
import androidx.wear.compose.material3.samples.ScrollIndicatorWithColumnSample
import androidx.wear.compose.material3.samples.ScrollIndicatorWithLCSample
import androidx.wear.compose.material3.samples.ScrollIndicatorWithSLCSample
+import androidx.wear.compose.material3.samples.ScrollIndicatorWithTLCSample
val ScrollIndicatorDemos =
listOf(
+ ComposableDemo("With TransformingLazyColumn") { ScrollIndicatorWithTLCSample() },
ComposableDemo("With ScalingLazyColumn") { ScrollIndicatorWithSLCSample() },
ComposableDemo("With LazyColumn") { ScrollIndicatorWithLCSample() },
ComposableDemo("With Column") { ScrollIndicatorWithColumnSample() }
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TextToggleButtonDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TextToggleButtonDemo.kt
index 58ca9b8..12ddaf1 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TextToggleButtonDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TextToggleButtonDemo.kt
@@ -36,7 +36,6 @@
import androidx.wear.compose.material3.TextToggleButton
import androidx.wear.compose.material3.TextToggleButtonDefaults
import androidx.wear.compose.material3.samples.LargeTextToggleButtonSample
-import androidx.wear.compose.material3.samples.TextToggleButtonSample
import androidx.wear.compose.material3.touchTargetAwareSize
@Composable
@@ -45,7 +44,7 @@
item { ListHeader { Text("Text Toggle Button", textAlign = TextAlign.Center) } }
item {
Row {
- TextToggleButtonSample() // Enabled and checked
+ TextToggleButtonsDemo(enabled = true, initialChecked = true)
Spacer(modifier = Modifier.width(5.dp))
TextToggleButtonsDemo(enabled = true, initialChecked = false)
}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimePickerDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimePickerDemo.kt
index 7a1f55b..20d4ca7 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimePickerDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimePickerDemo.kt
@@ -16,6 +16,8 @@
package androidx.wear.compose.material3.demos
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.icons.Icons
@@ -39,6 +41,7 @@
import java.time.LocalTime
import java.time.format.DateTimeFormatter
+@RequiresApi(Build.VERSION_CODES.O)
val TimePickerDemos =
listOf(
ComposableDemo("Time HH:MM:SS") { TimePickerWithSecondsSample() },
@@ -47,6 +50,7 @@
ComposableDemo("Time System time format") { TimePickerSample() },
)
+@RequiresApi(Build.VERSION_CODES.O)
@Composable
private fun TimePicker24hWithoutSecondsDemo() {
var showTimePicker by remember { mutableStateOf(true) }
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TransformingLazyColumnDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TransformingLazyColumnDemo.kt
index 4523210..4b6646b 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TransformingLazyColumnDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TransformingLazyColumnDemo.kt
@@ -154,21 +154,25 @@
}
item {
TransformExclusion {
- val interactionSourceLeft = remember { MutableInteractionSource() }
- val interactionSourceRight = remember { MutableInteractionSource() }
+ val interactionSource1 = remember { MutableInteractionSource() }
+ val interactionSource2 = remember { MutableInteractionSource() }
ButtonGroup(Modifier.scrollTransform(this@item)) {
- buttonGroupItem(interactionSource = interactionSourceLeft) {
- Button(onClick = {}, interactionSource = interactionSourceLeft) {
- Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
- Text("L")
- }
+ Button(
+ onClick = {},
+ Modifier.enlargeOnPress(interactionSource1),
+ interactionSource = interactionSource1
+ ) {
+ Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Text("L")
}
}
- buttonGroupItem(interactionSource = interactionSourceRight) {
- Button(onClick = {}, interactionSource = interactionSourceRight) {
- Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
- Text("R")
- }
+ Button(
+ onClick = {},
+ Modifier.enlargeOnPress(interactionSource2),
+ interactionSource = interactionSource2
+ ) {
+ Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Text("R")
}
}
}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
index b4828a7..11844f9 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
@@ -26,6 +26,7 @@
import androidx.wear.compose.material3.samples.AnimatedTextSampleButtonResponse
import androidx.wear.compose.material3.samples.AnimatedTextSampleSharedFontRegistry
import androidx.wear.compose.material3.samples.ButtonGroupSample
+import androidx.wear.compose.material3.samples.ButtonGroupThreeButtonsSample
import androidx.wear.compose.material3.samples.EdgeButtonListSample
import androidx.wear.compose.material3.samples.EdgeButtonSample
import androidx.wear.compose.material3.samples.EdgeSwipeForSwipeToDismiss
@@ -104,13 +105,18 @@
ComposableDemo("Edge Button Below SLC") {
EdgeButtonBelowScalingLazyColumnDemo()
},
+ ComposableDemo("Edge Button Below TLC") {
+ EdgeButtonBelowTransformingLazyColumnDemo()
+ },
)
),
Material3DemoCategory(
"Button Group",
listOf(
ComposableDemo("Two buttons") { ButtonGroupSample() },
- ComposableDemo("Three buttons") { ButtonGroupDemo() },
+ ComposableDemo("ABC") { ButtonGroupThreeButtonsSample() },
+ ComposableDemo("Text And Icon") { ButtonGroupDemo() },
+ ComposableDemo("ToggleButtons") { ButtonGroupToggleButtonsDemo() },
)
),
ComposableDemo("List Header") { Centralize { ListHeaderSample() } },
@@ -135,8 +141,13 @@
Material3DemoCategory("Stepper", StepperDemos),
Material3DemoCategory("Slider", SliderDemos),
Material3DemoCategory("Picker", PickerDemos),
- Material3DemoCategory("TimePicker", TimePickerDemos),
- Material3DemoCategory("DatePicker", DatePickerDemos),
+ // Requires API level 26 or higher due to java.time dependency.
+ *(if (Build.VERSION.SDK_INT >= 26)
+ arrayOf(
+ Material3DemoCategory("TimePicker", TimePickerDemos),
+ Material3DemoCategory("DatePicker", DatePickerDemos)
+ )
+ else emptyArray<Material3DemoCategory>()),
Material3DemoCategory("Progress Indicator", ProgressIndicatorDemos),
Material3DemoCategory("Scroll Indicator", ScrollIndicatorDemos),
Material3DemoCategory("Placeholder", PlaceholderDemos),
diff --git a/wear/compose/compose-material3/macrobenchmark-common/build.gradle b/wear/compose/compose-material3/macrobenchmark-common/build.gradle
index 51008d8..92093e3 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/build.gradle
+++ b/wear/compose/compose-material3/macrobenchmark-common/build.gradle
@@ -10,7 +10,7 @@
compileSdk = 35
defaultConfig {
- minSdk = 30
+ minSdk = 25
}
buildTypes.configureEach {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/ButtonGroupBenchmark.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/ButtonGroupBenchmark.kt
index bfbc1a9..2d5a664 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/ButtonGroupBenchmark.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/ButtonGroupBenchmark.kt
@@ -18,14 +18,12 @@
import android.os.SystemClock
import androidx.benchmark.macro.MacrobenchmarkScope
-import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.contentDescription
@@ -40,30 +38,22 @@
object ButtonGroupBenchmark : MacrobenchmarkScreen {
override val content: @Composable (BoxScope.() -> Unit)
get() = {
- val interactionSourceLeft = remember { MutableInteractionSource() }
- val interactionSourceRight = remember { MutableInteractionSource() }
Box(Modifier.size(300.dp), contentAlignment = Alignment.Center) {
ButtonGroup(Modifier.fillMaxWidth()) {
- buttonGroupItem(interactionSource = interactionSourceLeft) {
- Button(
- modifier = Modifier.semantics { contentDescription = LEFT_BUTTON },
- onClick = {},
- interactionSource = interactionSourceLeft
- ) {
- Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
- Text("Left")
- }
+ Button(
+ modifier = Modifier.semantics { contentDescription = LEFT_BUTTON },
+ onClick = {},
+ ) {
+ Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Text("Left")
}
}
- buttonGroupItem(interactionSource = interactionSourceRight) {
- Button(
- modifier = Modifier.semantics { contentDescription = RIGHT_BUTTON },
- onClick = {},
- interactionSource = interactionSourceRight
- ) {
- Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
- Text("Right")
- }
+ Button(
+ modifier = Modifier.semantics { contentDescription = RIGHT_BUTTON },
+ onClick = {},
+ ) {
+ Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Text("Right")
}
}
}
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/DatePickerBenchmark.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/DatePickerBenchmark.kt
index 3177504..51431d0 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/DatePickerBenchmark.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/DatePickerBenchmark.kt
@@ -16,7 +16,9 @@
package androidx.wear.compose.material3.macrobenchmark.common
+import android.os.Build
import android.os.SystemClock
+import androidx.annotation.RequiresApi
import androidx.benchmark.macro.MacrobenchmarkScope
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.material.icons.Icons
@@ -41,6 +43,7 @@
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
+@RequiresApi(Build.VERSION_CODES.O)
object DatePickerBenchmark : MacrobenchmarkScreen {
override val content: @Composable (BoxScope.() -> Unit)
get() = {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/TimePickerBenchmark.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/TimePickerBenchmark.kt
index 1c9103b..7d15ce7 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/TimePickerBenchmark.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/TimePickerBenchmark.kt
@@ -16,7 +16,9 @@
package androidx.wear.compose.material3.macrobenchmark.common
+import android.os.Build
import android.os.SystemClock
+import androidx.annotation.RequiresApi
import androidx.benchmark.macro.MacrobenchmarkScope
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
@@ -24,6 +26,7 @@
import androidx.wear.compose.material3.TimePickerType
import java.time.LocalTime
+@RequiresApi(Build.VERSION_CODES.O)
object TimePickerBenchmark : MacrobenchmarkScreen {
override val content: @Composable (BoxScope.() -> Unit)
get() = {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/DatePickerScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/DatePickerScreen.kt
index 8e3685d1..04e9ca75 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/DatePickerScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/DatePickerScreen.kt
@@ -16,6 +16,7 @@
package androidx.wear.compose.material3.macrobenchmark.common.baselineprofile
+import android.os.Build
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
import androidx.wear.compose.material3.DatePicker
@@ -27,14 +28,16 @@
object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = {
- val minDate = LocalDate.of(2022, 10, 15)
- val maxDate = LocalDate.of(2025, 2, 4)
- DatePicker(
- initialDate = LocalDate.of(2024, 9, 2),
- onDatePicked = {},
- minValidDate = minDate,
- maxValidDate = maxDate,
- datePickerType = DatePickerType.YearMonthDay
- )
+ if (Build.VERSION.SDK_INT >= 26) {
+ val minDate = LocalDate.of(2022, 10, 15)
+ val maxDate = LocalDate.of(2025, 2, 4)
+ DatePicker(
+ initialDate = LocalDate.of(2024, 9, 2),
+ onDatePicked = {},
+ minValidDate = minDate,
+ maxValidDate = maxDate,
+ datePickerType = DatePickerType.YearMonthDay
+ )
+ }
}
}
diff --git a/wear/compose/compose-material3/macrobenchmark-target/build.gradle b/wear/compose/compose-material3/macrobenchmark-target/build.gradle
index eaba1e7..82913ef 100644
--- a/wear/compose/compose-material3/macrobenchmark-target/build.gradle
+++ b/wear/compose/compose-material3/macrobenchmark-target/build.gradle
@@ -46,4 +46,4 @@
implementation(project(":wear:compose:compose-material3-macrobenchmark-common"))
}
-android.defaultConfig.minSdk = 30
+android.defaultConfig.minSdk = 25
diff --git a/wear/compose/compose-material3/macrobenchmark-target/src/main/AndroidManifest.xml b/wear/compose/compose-material3/macrobenchmark-target/src/main/AndroidManifest.xml
index a68fd42..6ec1884 100644
--- a/wear/compose/compose-material3/macrobenchmark-target/src/main/AndroidManifest.xml
+++ b/wear/compose/compose-material3/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -130,7 +130,8 @@
<activity
android:name=".DatePickerActivity"
android:theme="@style/AppTheme"
- android:exported="true">
+ android:exported="true"
+ tools:targetApi="o">
<intent-filter>
<action android:name=
"androidx.wear.compose.material3.macrobenchmark.target.DATE_PICKER_ACTIVITY" />
@@ -269,7 +270,8 @@
<activity
android:name=".TimePickerActivity"
android:theme="@style/AppTheme"
- android:exported="true">
+ android:exported="true"
+ tools:targetApi="o">
<intent-filter>
<action android:name=
"androidx.wear.compose.material3.macrobenchmark.target.TIME_PICKER_ACTIVITY" />
diff --git a/wear/compose/compose-material3/macrobenchmark-target/src/main/java/androidx/wear/compose/material3/macrobenchmark/target/DatePickerActivity.kt b/wear/compose/compose-material3/macrobenchmark-target/src/main/java/androidx/wear/compose/material3/macrobenchmark/target/DatePickerActivity.kt
index 4256fdc..d18d12d 100644
--- a/wear/compose/compose-material3/macrobenchmark-target/src/main/java/androidx/wear/compose/material3/macrobenchmark/target/DatePickerActivity.kt
+++ b/wear/compose/compose-material3/macrobenchmark-target/src/main/java/androidx/wear/compose/material3/macrobenchmark/target/DatePickerActivity.kt
@@ -16,6 +16,9 @@
package androidx.wear.compose.material3.macrobenchmark.target
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.wear.compose.material3.macrobenchmark.common.DatePickerBenchmark
+@RequiresApi(Build.VERSION_CODES.O)
class DatePickerActivity : BenchmarkBaseActivity(DatePickerBenchmark)
diff --git a/wear/compose/compose-material3/macrobenchmark-target/src/main/java/androidx/wear/compose/material3/macrobenchmark/target/TimePickerActivity.kt b/wear/compose/compose-material3/macrobenchmark-target/src/main/java/androidx/wear/compose/material3/macrobenchmark/target/TimePickerActivity.kt
index 6188ab7..e134065 100644
--- a/wear/compose/compose-material3/macrobenchmark-target/src/main/java/androidx/wear/compose/material3/macrobenchmark/target/TimePickerActivity.kt
+++ b/wear/compose/compose-material3/macrobenchmark-target/src/main/java/androidx/wear/compose/material3/macrobenchmark/target/TimePickerActivity.kt
@@ -16,6 +16,9 @@
package androidx.wear.compose.material3.macrobenchmark.target
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.wear.compose.material3.macrobenchmark.common.TimePickerBenchmark
+@RequiresApi(Build.VERSION_CODES.O)
class TimePickerActivity : BenchmarkBaseActivity(TimePickerBenchmark)
diff --git a/wear/compose/compose-material3/macrobenchmark/build.gradle b/wear/compose/compose-material3/macrobenchmark/build.gradle
index bcab91e..1940fef 100644
--- a/wear/compose/compose-material3/macrobenchmark/build.gradle
+++ b/wear/compose/compose-material3/macrobenchmark/build.gradle
@@ -23,7 +23,7 @@
android {
compileSdk = 35
defaultConfig {
- minSdk = 30
+ minSdk = 29
}
namespace = "androidx.wear.compose.material3.macrobenchmark"
targetProjectPath = ":wear:compose:compose-material3-macrobenchmark-target"
diff --git a/wear/compose/compose-material3/macrobenchmark/src/main/java/androidx/wear/compose/material3/macrobenchmark/DatePickerBenchmarkTest.kt b/wear/compose/compose-material3/macrobenchmark/src/main/java/androidx/wear/compose/material3/macrobenchmark/DatePickerBenchmarkTest.kt
index b9abc42..3316732 100644
--- a/wear/compose/compose-material3/macrobenchmark/src/main/java/androidx/wear/compose/material3/macrobenchmark/DatePickerBenchmarkTest.kt
+++ b/wear/compose/compose-material3/macrobenchmark/src/main/java/androidx/wear/compose/material3/macrobenchmark/DatePickerBenchmarkTest.kt
@@ -16,12 +16,15 @@
package androidx.wear.compose.material3.macrobenchmark
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.benchmark.macro.CompilationMode
import androidx.test.filters.LargeTest
import androidx.wear.compose.material3.macrobenchmark.common.DatePickerBenchmark
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
+@RequiresApi(Build.VERSION_CODES.O)
@LargeTest
@RunWith(Parameterized::class)
class DatePickerBenchmarkTest(compilationMode: CompilationMode) :
diff --git a/wear/compose/compose-material3/macrobenchmark/src/main/java/androidx/wear/compose/material3/macrobenchmark/TimePickerBenchmarkTest.kt b/wear/compose/compose-material3/macrobenchmark/src/main/java/androidx/wear/compose/material3/macrobenchmark/TimePickerBenchmarkTest.kt
index 1533377..52a5a38 100644
--- a/wear/compose/compose-material3/macrobenchmark/src/main/java/androidx/wear/compose/material3/macrobenchmark/TimePickerBenchmarkTest.kt
+++ b/wear/compose/compose-material3/macrobenchmark/src/main/java/androidx/wear/compose/material3/macrobenchmark/TimePickerBenchmarkTest.kt
@@ -16,12 +16,15 @@
package androidx.wear.compose.material3.macrobenchmark
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.benchmark.macro.CompilationMode
import androidx.test.filters.LargeTest
import androidx.wear.compose.material3.macrobenchmark.common.TimePickerBenchmark
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
+@RequiresApi(Build.VERSION_CODES.O)
@LargeTest
@RunWith(Parameterized::class)
class TimePickerBenchmarkTest(compilationMode: CompilationMode) :
diff --git a/wear/compose/compose-material3/samples/build.gradle b/wear/compose/compose-material3/samples/build.gradle
index 814f3e9..02e43d8 100644
--- a/wear/compose/compose-material3/samples/build.gradle
+++ b/wear/compose/compose-material3/samples/build.gradle
@@ -50,7 +50,7 @@
android {
compileSdk = 35
defaultConfig {
- minSdk = 30
+ minSdk = 25
}
namespace = "androidx.wear.compose.material3.samples"
}
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ButtonGroupSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ButtonGroupSample.kt
index a467fe1..9b6d145 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ButtonGroupSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ButtonGroupSample.kt
@@ -34,19 +34,58 @@
@Sampled
@Composable
fun ButtonGroupSample() {
- val interactionSourceLeft = remember { MutableInteractionSource() }
- val interactionSourceRight = remember { MutableInteractionSource() }
+ val interactionSource1 = remember { MutableInteractionSource() }
+ val interactionSource2 = remember { MutableInteractionSource() }
+
Box(Modifier.size(300.dp), contentAlignment = Alignment.Center) {
ButtonGroup(Modifier.fillMaxWidth()) {
- buttonGroupItem(interactionSource = interactionSourceLeft) {
- Button(onClick = {}, interactionSource = interactionSourceLeft) {
- Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text("L") }
- }
+ Button(
+ onClick = {},
+ modifier = Modifier.enlargeOnPress(interactionSource1),
+ interactionSource = interactionSource1
+ ) {
+ Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text("L") }
}
- buttonGroupItem(interactionSource = interactionSourceRight) {
- Button(onClick = {}, interactionSource = interactionSourceRight) {
- Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text("R") }
- }
+ Button(
+ onClick = {},
+ modifier = Modifier.enlargeOnPress(interactionSource2),
+ interactionSource = interactionSource2
+ ) {
+ Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text("R") }
+ }
+ }
+ }
+}
+
+@Sampled
+@Composable
+fun ButtonGroupThreeButtonsSample() {
+ val interactionSource1 = remember { MutableInteractionSource() }
+ val interactionSource2 = remember { MutableInteractionSource() }
+ val interactionSource3 = remember { MutableInteractionSource() }
+
+ Box(Modifier.size(300.dp), contentAlignment = Alignment.Center) {
+ ButtonGroup(Modifier.fillMaxWidth()) {
+ Button(
+ onClick = {},
+ modifier = Modifier.enlargeOnPress(interactionSource1),
+ interactionSource = interactionSource1
+ ) {
+ Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text("A") }
+ }
+ Button(
+ onClick = {},
+ modifier = Modifier.weight(1.5f).enlargeOnPress(interactionSource2),
+ interactionSource = interactionSource2
+ ) {
+ Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text("B") }
+ }
+ Button(
+ onClick = {},
+ modifier = Modifier.enlargeOnPress(interactionSource3),
+ interactionSource = interactionSource3
+ ) {
+ Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text("C") }
}
}
}
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ButtonSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ButtonSample.kt
index a412d5a..5cda3e5 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ButtonSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ButtonSample.kt
@@ -17,13 +17,21 @@
package androidx.wear.compose.material3.samples
import androidx.annotation.Sampled
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowDropDown
+import androidx.compose.material.icons.filled.KeyboardArrowDown
+import androidx.compose.material.icons.filled.KeyboardArrowUp
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
@@ -280,18 +288,39 @@
@Sampled
@Composable
fun OutlinedCompactButtonSample(modifier: Modifier = Modifier) {
- CompactButton(
- onClick = { /* Do something */ },
- colors = ButtonDefaults.outlinedButtonColors(),
- border = ButtonDefaults.outlinedButtonBorder(enabled = true),
- modifier = modifier,
- ) {
- Text("Show More", maxLines = 1, overflow = TextOverflow.Ellipsis)
- Spacer(Modifier.width(ButtonDefaults.IconSpacing))
- Icon(
- Icons.Filled.ArrowDropDown,
- contentDescription = "Expand",
- modifier = Modifier.size(ButtonDefaults.ExtraSmallIconSize)
- )
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ var expanded by remember { mutableStateOf(false) }
+ if (expanded) {
+ Text("A multiline string showing two lines")
+ } else {
+ Text("One line text")
+ }
+ Spacer(Modifier.height(ButtonDefaults.IconSpacing))
+ CompactButton(
+ onClick = { expanded = !expanded },
+ colors = ButtonDefaults.outlinedButtonColors(),
+ border = ButtonDefaults.outlinedButtonBorder(enabled = true),
+ modifier = modifier,
+ ) {
+ if (expanded) {
+ Text("Show Less", maxLines = 1, overflow = TextOverflow.Ellipsis)
+ } else {
+ Text("Show More", maxLines = 1, overflow = TextOverflow.Ellipsis)
+ }
+ Spacer(Modifier.width(ButtonDefaults.IconSpacing))
+ if (expanded) {
+ Icon(
+ Icons.Filled.KeyboardArrowUp,
+ contentDescription = "Collapse",
+ modifier = Modifier.size(ButtonDefaults.ExtraSmallIconSize)
+ )
+ } else {
+ Icon(
+ Icons.Filled.KeyboardArrowDown,
+ contentDescription = "Expand",
+ modifier = Modifier.size(ButtonDefaults.ExtraSmallIconSize)
+ )
+ }
+ }
}
}
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/DatePickerSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/DatePickerSample.kt
index 4c2ae26..6557f67 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/DatePickerSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/DatePickerSample.kt
@@ -17,12 +17,8 @@
package androidx.wear.compose.material3.samples
import androidx.annotation.Sampled
-import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.height
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Edit
import androidx.compose.runtime.Composable
@@ -33,7 +29,6 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.unit.dp
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.DatePicker
import androidx.wear.compose.material3.DatePickerType
@@ -106,14 +101,13 @@
@Sampled
@Composable
-fun DatePickerMinDateMaxDateSample() {
- var showDatePicker by remember { mutableStateOf(false) }
- var datePickerDate by remember { mutableStateOf(LocalDate.of(2024, 9, 2)) }
+fun DatePickerFutureOnlySample() {
+ val currentDate = LocalDate.now()
+ var showDatePicker by remember { mutableStateOf(true) }
+ var datePickerDate by remember { mutableStateOf(LocalDate.now()) }
val formatter =
DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(LocalConfiguration.current.locales[0])
- val minDate = LocalDate.of(2022, 10, 30)
- val maxDate = LocalDate.of(2025, 2, 4)
if (showDatePicker) {
DatePicker(
initialDate = datePickerDate, // Initialize with last picked date on reopen
@@ -121,18 +115,14 @@
datePickerDate = it
showDatePicker = false
},
- minValidDate = minDate,
- maxValidDate = maxDate,
- datePickerType = DatePickerType.YearMonthDay
+ datePickerType = DatePickerType.YearMonthDay,
+ minValidDate = currentDate
)
} else {
- Column(
+ Box(
modifier = Modifier.fillMaxSize(),
- verticalArrangement = Arrangement.Center,
- horizontalAlignment = Alignment.CenterHorizontally,
+ contentAlignment = Alignment.Center,
) {
- Text(text = "${minDate.format(formatter)} ~ ${maxDate.format(formatter)}")
- Spacer(modifier = Modifier.height(6.dp))
Button(
onClick = { showDatePicker = true },
label = { Text("Selected Date") },
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/IconToggleButtonSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/IconToggleButtonSample.kt
index 7009f9e..9158d71 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/IconToggleButtonSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/IconToggleButtonSample.kt
@@ -22,8 +22,6 @@
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.width
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Favorite
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -32,7 +30,6 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
-import androidx.wear.compose.material3.Icon
import androidx.wear.compose.material3.IconToggleButton
import androidx.wear.compose.material3.IconToggleButtonDefaults
import androidx.wear.compose.material3.samples.icons.WifiOffIcon
@@ -54,7 +51,11 @@
onCheckedChange = { firstChecked = !firstChecked },
shapes = IconToggleButtonDefaults.animatedShapes(),
) {
- Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Favorite icon")
+ if (firstChecked) {
+ WifiOnIcon()
+ } else {
+ WifiOffIcon()
+ }
}
Spacer(modifier = Modifier.width(5.dp))
@@ -64,7 +65,11 @@
onCheckedChange = { secondChecked = !secondChecked },
shapes = IconToggleButtonDefaults.animatedShapes(),
) {
- Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Favorite icon")
+ if (secondChecked) {
+ WifiOnIcon()
+ } else {
+ WifiOffIcon()
+ }
}
}
}
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ScrollIndicatorSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ScrollIndicatorSample.kt
index 17cd46b..0fe375e 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ScrollIndicatorSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ScrollIndicatorSample.kt
@@ -17,6 +17,7 @@
package androidx.wear.compose.material3.samples
import androidx.annotation.Sampled
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
@@ -27,12 +28,30 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.tooling.preview.Preview
import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
+import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState
import androidx.wear.compose.material3.ScrollIndicator
import androidx.wear.compose.material3.Text
@Sampled
+@Preview
+@Composable
+fun ScrollIndicatorWithTLCSample() {
+ val scrollState = rememberTransformingLazyColumnState()
+ Box {
+ TransformingLazyColumn(modifier = Modifier.background(Color.Black), state = scrollState) {
+ items(100) { Text(text = "Item $it") }
+ }
+ ScrollIndicator(modifier = Modifier.align(Alignment.CenterEnd), state = scrollState)
+ }
+}
+
+@Sampled
+@Preview
@Composable
fun ScrollIndicatorWithSLCSample() {
val scrollState = rememberScalingLazyListState()
@@ -45,6 +64,7 @@
}
@Sampled
+@Preview
@Composable
fun ScrollIndicatorWithLCSample() {
val scrollState = rememberLazyListState()
@@ -61,6 +81,7 @@
}
@Sampled
+@Preview
@Composable
fun ScrollIndicatorWithColumnSample() {
val scrollState = rememberScrollState()
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/SwipeToRevealSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/SwipeToRevealSample.kt
index 163c461..ca8fd9e 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/SwipeToRevealSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/SwipeToRevealSample.kt
@@ -78,6 +78,10 @@
text = { Text("Delete") },
label = "Delete"
)
+ undoPrimaryAction(
+ onClick = { /* This block is called when the undo primary action is executed. */ },
+ text = { Text("Undo Delete") },
+ )
}
) {
Card(modifier = Modifier.fillMaxWidth(), onClick = {}) {
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AlertDialogTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AlertDialogTest.kt
index cdf1893..cde46d4 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AlertDialogTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AlertDialogTest.kt
@@ -747,7 +747,7 @@
titleTop.assertIsEqualTo(iconBottom + AlertIconBottomSpacing)
textTop.assertIsEqualTo(titleBottom + AlertTextMessageTopSpacing)
- contentTop.assertIsEqualTo(textBottom + AlertTextMessageTopSpacing)
+ contentTop.assertIsEqualTo(textBottom + AlertContentTopSpacing)
confirmButtonTop.assertIsEqualTo(contentBottom + ConfirmDismissButtonsTopSpacing)
}
@@ -782,7 +782,7 @@
rule.onNodeWithTag(ConfirmButtonTestTag).getUnclippedBoundsInRoot().top
titleTop.assertIsEqualTo(iconBottom + AlertIconBottomSpacing)
- contentTop.assertIsEqualTo(titleBottom + AlertTextMessageTopSpacing)
+ contentTop.assertIsEqualTo(titleBottom + AlertContentTopSpacing)
confirmButtonTop.assertIsEqualTo(contentBottom + ConfirmDismissButtonsTopSpacing)
}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonGroupScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonGroupScreenshotTest.kt
index bfd690a..0b84d2b 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonGroupScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonGroupScreenshotTest.kt
@@ -18,8 +18,6 @@
import android.os.Build
import androidx.compose.foundation.background
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.runtime.remember
import androidx.compose.testutils.assertAgainstGolden
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -72,25 +70,25 @@
require(numItems in 1..3)
rule.setContentWithTheme {
ScreenConfiguration(SCREEN_SIZE_SMALL) {
- val interactionSource1 = remember { MutableInteractionSource() }
- val interactionSource2 = remember { MutableInteractionSource() }
- val interactionSource3 = remember { MutableInteractionSource() }
ButtonGroup(
Modifier.testTag(TEST_TAG),
spacing = spacing,
expansionWidth = expansionWidth
) {
- buttonGroupItem(interactionSource1, minWidth1, weight1) {
- Text("A", Modifier.background(Color.Gray))
+ // Modifiers inverted here to check order doesn't matter
+ Text("A", Modifier.background(Color.Gray).weight(weight1).minWidth(minWidth1))
+ if (numItems >= 2) {
+ Text(
+ "B",
+ Modifier.background(Color.Gray).minWidth(minWidth2).weight(weight2)
+ )
}
- if (numItems >= 2)
- buttonGroupItem(interactionSource2, minWidth2, weight2) {
- Text("B", Modifier.background(Color.Gray))
- }
- if (numItems >= 3)
- buttonGroupItem(interactionSource3, minWidth3, weight3) {
- Text("C", Modifier.background(Color.Gray))
- }
+ if (numItems >= 3) {
+ Text(
+ "C",
+ Modifier.background(Color.Gray).minWidth(minWidth3).weight(weight3)
+ )
+ }
}
}
}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonGroupTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonGroupTest.kt
index b24182d..e28002d 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonGroupTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonGroupTest.kt
@@ -16,12 +16,10 @@
package androidx.wear.compose.material3
-import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
-import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.assertWidthIsEqualTo
@@ -38,13 +36,10 @@
@Test
fun supports_testtag() {
rule.setContentWithTheme {
- val interactionSource = remember { MutableInteractionSource() }
ButtonGroup(modifier = Modifier.testTag(TEST_TAG)) {
- buttonGroupItem(interactionSource) {
- Box(
- modifier = Modifier.fillMaxSize(),
- )
- }
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ )
}
}
@@ -53,76 +48,94 @@
@Test
fun two_items_equally_sized_by_default() =
- doTest(
+ verifyWidths(
2,
expectedWidths = { availableSpace -> arrayOf(availableSpace / 2, availableSpace / 2) }
)
@Test
fun two_items_one_double_size() =
- doTest(
+ verifyWidths(
2,
expectedWidths = { availableSpace ->
arrayOf(availableSpace / 3, availableSpace / 3 * 2)
},
- minWidthsAndWeights = arrayOf(50.dp to 1f, 50.dp to 2f)
+ minWidthAndWeights = arrayOf(50.dp to 1f, 50.dp to 2f)
)
@Test
fun respects_min_width() =
- doTest(
+ verifyWidths(
2,
expectedWidths = { availableSpace -> arrayOf(30.dp, availableSpace - 30.dp) },
size = 100.dp,
- minWidthsAndWeights = arrayOf(30.dp to 1f, 30.dp to 10f)
+ minWidthAndWeights = arrayOf(30.dp to 1f, 30.dp to 10f)
)
@Test
fun three_equal_buttons() =
- doTest(3, expectedWidths = { availableSpace -> Array(3) { availableSpace / 3 } })
+ verifyWidths(3, expectedWidths = { availableSpace -> Array(3) { availableSpace / 3 } })
@Test
fun three_buttons_one_two_one() =
- doTest(
+ verifyWidths(
3,
expectedWidths = { availableSpace ->
arrayOf(availableSpace / 4, availableSpace / 2, availableSpace / 4)
},
- minWidthsAndWeights = arrayOf(50.dp to 1f, 50.dp to 2f, 50.dp to 1f)
+ minWidthAndWeights = arrayOf(50.dp to 1f, 50.dp to 2f, 50.dp to 1f)
)
- private fun doTest(
+ @Test
+ fun modifier_order_ignored() {
+ val size = 300.dp
+ rule.setContentWithTheme {
+ ButtonGroup(
+ modifier = Modifier.size(size),
+ contentPadding = PaddingValues(0.dp),
+ spacing = 0.dp
+ ) {
+ Box(Modifier.weight(1f).minWidth(60.dp).testTag("${TEST_TAG}0"))
+ Box(Modifier.minWidth(60.dp).weight(1f).testTag("${TEST_TAG}1"))
+ Box(Modifier.weight(2f).minWidth(60.dp).testTag("${TEST_TAG}2"))
+ Box(Modifier.minWidth(60.dp).weight(2f).testTag("${TEST_TAG}3"))
+ }
+ }
+
+ // Items 0 & 1 should be 60.dp, 2 & 3 should be 90.dp
+ listOf(60.dp, 60.dp, 90.dp, 90.dp).forEachIndexed { index, dp ->
+ rule.onNodeWithTag(TEST_TAG + index.toString()).assertWidthIsEqualTo(dp)
+ }
+ }
+
+ private fun verifyWidths(
numItems: Int,
expectedWidths: (Dp) -> Array<Dp>,
size: Dp = 300.dp,
spacing: Dp = 10.dp,
- minWidthsAndWeights: Array<Pair<Dp, Float>> = Array(numItems) { 48.dp to 1f },
+ minWidthAndWeights: Array<Pair<Dp, Float>> = Array(numItems) { 48.dp to 1f },
) {
val horizontalPadding = 10.dp
val actualExpectedWidths =
expectedWidths(size - horizontalPadding * 2 - spacing * (numItems - 1))
require(numItems == actualExpectedWidths.size)
- require(numItems == minWidthsAndWeights.size)
+ require(numItems == minWidthAndWeights.size)
rule.setContentWithTheme {
- val interactionSources = remember { Array(numItems) { MutableInteractionSource() } }
ButtonGroup(
modifier = Modifier.size(size),
contentPadding = PaddingValues(horizontal = horizontalPadding),
spacing = spacing
) {
repeat(numItems) { ix ->
- buttonGroupItem(
- interactionSources[ix],
- minWidth = minWidthsAndWeights[ix].first,
- weight = minWidthsAndWeights[ix].second
- ) {
- Box(
- modifier =
- Modifier.testTag(TEST_TAG + (ix + 1).toString()).fillMaxSize(),
- )
- }
+ Box(
+ modifier =
+ Modifier.testTag(TEST_TAG + (ix + 1).toString())
+ .fillMaxSize()
+ .weight(minWidthAndWeights[ix].second)
+ .minWidth(minWidthAndWeights[ix].first)
+ )
}
}
}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonTest.kt
index 722675e..a6c2594 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ButtonTest.kt
@@ -16,6 +16,8 @@
package androidx.wear.compose.material3
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@@ -277,6 +279,7 @@
assertEquals(expectedSecondaryTextStyle, actualSecondaryLabelTextStyle)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun default_shape_is_stadium() {
rule.isShape(
@@ -289,6 +292,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun allows_custom_shape_override() {
val shape = CutCornerShape(4.dp)
@@ -376,6 +380,7 @@
.assertTopPositionInRootIsEqualTo((itemBounds.height - iconBounds.height) / 2)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_enabled_base_button_correct_colors() {
rule.verifyButtonColors(
@@ -385,6 +390,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_base_button_correct_colors() {
rule.verifyButtonColors(
@@ -398,6 +404,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_enabled_filled_tonal_base_button_correct_colors() {
rule.verifyButtonColors(
@@ -408,6 +415,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_filled_tonal_base_button_correct_colors() {
rule.verifyButtonColors(
@@ -422,6 +430,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_enabled_button_correct_filled_variant_colors() {
rule.verifyButtonColors(
@@ -432,6 +441,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_button_correct_filled_variant_colors() {
rule.verifyButtonColors(
@@ -446,6 +456,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_enabled_outlined_base_button_correct_colors() {
rule.verifyButtonColors(
@@ -456,6 +467,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_outlined_base_button_correct_colors() {
rule.verifyButtonColors(
@@ -468,6 +480,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_enabled_child_base_button_correct_colors() {
rule.verifyButtonColors(
@@ -478,6 +491,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_child_base_button_correct_colors() {
rule.verifyButtonColors(
@@ -490,6 +504,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_enabled_three_slot_button_correct_colors() {
rule.verifyThreeSlotButtonColors(
@@ -499,6 +514,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_three_slot_button_correct_colors() {
rule.verifyThreeSlotButtonColors(
@@ -508,6 +524,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_enabled_filled_tonal_three_slot_button_correct_colors() {
rule.verifyThreeSlotButtonColors(
@@ -517,6 +534,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_filled_tonal_three_slot_button_correct_colors() {
rule.verifyThreeSlotButtonColors(
@@ -526,6 +544,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_enabled_outlined_three_slot_button_correct_colors() {
rule.verifyThreeSlotButtonColors(
@@ -535,6 +554,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_outlined_three_slot_button_correct_colors() {
rule.verifyThreeSlotButtonColors(
@@ -544,6 +564,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_enabled_child_three_slot_button_correct_colors() {
rule.verifyThreeSlotButtonColors(
@@ -553,6 +574,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_child_three_slot_button_correct_colors() {
rule.verifyThreeSlotButtonColors(
@@ -562,6 +584,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_enabled_outlined_button_correct_border_colors() {
val status = Status.Enabled
@@ -573,6 +596,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_outlined_button_correct_border_colors() {
val status = Status.Disabled
@@ -594,6 +618,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun overrides_enabled_outlined_button_border_color() {
val status = Status.Enabled
@@ -615,6 +640,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun overrides_disabled_outlined_button_border_color() {
val status = Status.Disabled
@@ -752,6 +778,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_enabled_compact_button_correct_colors() {
rule.verifyCompactButtonColors(
@@ -760,6 +787,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_compact_button_correct_colors() {
rule.verifyCompactButtonColors(
@@ -768,6 +796,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_enabled_filled_tonal_compact_button_correct_colors() {
rule.verifyCompactButtonColors(
@@ -776,6 +805,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_filled_tonal_compact_button_correct_colors() {
rule.verifyCompactButtonColors(
@@ -784,6 +814,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_enabled_outlined_compact_button_correct_colors() {
rule.verifyCompactButtonColors(
@@ -792,6 +823,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_outlined_compact_button_correct_colors() {
rule.verifyCompactButtonColors(
@@ -800,6 +832,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_enabled_child_compact_button_correct_colors() {
rule.verifyCompactButtonColors(
@@ -808,6 +841,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_child_compact_button_correct_colors() {
rule.verifyCompactButtonColors(
@@ -1228,6 +1262,7 @@
}
}
+@RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.verifyButtonColors(
status: Status,
expectedContainerColor: @Composable () -> Color,
@@ -1317,6 +1352,7 @@
return actualContentColor
}
+@RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.verifyThreeSlotButtonColors(
status: Status,
expectedColor: @Composable () -> ButtonColors,
@@ -1419,6 +1455,7 @@
return ThreeSlotButtonColors(actualLabelColor, actualSecondaryLabelColor, actualIconColor)
}
+@RequiresApi(Build.VERSION_CODES.O)
internal fun ComposeContentTestRule.verifyButtonBorderColor(
expectedBorderColor: @Composable () -> Color,
content: @Composable (Modifier) -> Unit
@@ -1436,6 +1473,7 @@
onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(finalExpectedBorderColor)
}
+@RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.isShape(
expectedShape: Shape,
colors: @Composable () -> ButtonColors,
@@ -1469,6 +1507,7 @@
)
}
+@RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.verifyCompactButtonColors(
status: Status,
colors: @Composable () -> ButtonColors
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/CardTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/CardTest.kt
index 62c88b3..98b8213 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/CardTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/CardTest.kt
@@ -16,6 +16,8 @@
package androidx.wear.compose.material3
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@@ -540,6 +542,7 @@
assertEquals(expectedTitleColor, actualTitleColor)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun outlined_card_has_outlined_border_and_transparent() {
val outlineColor = Color.Red
@@ -563,6 +566,7 @@
.assertColorInPercentageRange(testBackground, 93f..97f)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun outlined_titlecard_has_outlined_border_and_transparent() {
val outlineColor = Color.Red
@@ -588,6 +592,7 @@
.assertColorInPercentageRange(testBackground, 93f..97f)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun outlined_appcard_has_outlined_border_and_transparent() {
val outlineColor = Color.Red
@@ -691,6 +696,7 @@
assertEquals(expectedContentTextStyle, actuaContentTextStyle)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun outlined_app_card_gives_correct_text_style_base() {
var actualAppTextStyle = TextStyle.Default
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/CheckboxButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/CheckboxButtonTest.kt
index 0c34439..3b4f228 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/CheckboxButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/CheckboxButtonTest.kt
@@ -16,6 +16,8 @@
package androidx.wear.compose.material3
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
@@ -549,6 +551,7 @@
Assert.assertEquals(2, secondaryLabelMaxLines)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun checkbox_button_allows_checked_background_color_override() =
verifyToggleButtonBackgroundColor(
@@ -557,6 +560,7 @@
expectedColor = CHECKED_COLOR
)
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun checkbox_button_allows_unchecked_background_color_override() =
verifyToggleButtonBackgroundColor(
@@ -565,6 +569,7 @@
expectedColor = UNCHECKED_COLOR
)
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun split_checkbox_button_allows_checked_background_color_override() =
verifySplitToggleButtonBackgroundColor(
@@ -573,6 +578,7 @@
expectedColor = CHECKED_COLOR
)
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun split_checkbox_button_allows_unchecked_background_color_override() =
verifySplitToggleButtonBackgroundColor(
@@ -581,46 +587,55 @@
expectedColor = UNCHECKED_COLOR
)
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun verify_checkbox_button_colors_enabled_and_checked() {
rule.verifyCheckboxButtonColors(checked = true, enabled = true)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun verify_checkbox_button_colors_enabled_and_unchecked() {
rule.verifyCheckboxButtonColors(checked = false, enabled = true)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun verify_checkbox_button_colors_disabled_and_checked() {
rule.verifyCheckboxButtonColors(checked = true, enabled = false)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun verify_checkbox_button_colors_disabled_and_unchecked() {
rule.verifyCheckboxButtonColors(checked = false, enabled = false)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun verify_split_checkbox_button_colors_enabled_and_checked() {
rule.verifySplitCheckboxButtonColors(checked = true, enabled = true)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun verify_split_checkbox_button_colors_enabled_and_unchecked() {
rule.verifySplitCheckboxButtonColors(checked = false, enabled = true)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun verify_split_checkbox_button_colors_disabled_and_checked() {
rule.verifySplitCheckboxButtonColors(checked = true, enabled = false)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun verify_split_checkbox_button_colors_disabled_and_unchecked() {
rule.verifySplitCheckboxButtonColors(checked = false, enabled = false)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun checkbox_checked_colors_are_customisable() {
val boxColor = Color.Green
@@ -643,6 +658,7 @@
checkboxImage.assertContainsColor(checkmarkColor)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun checkbox_unchecked_colors_are_customisable() {
// NB checkmark is erased during animation, so we don't test uncheckedCheckmarkColor
@@ -664,6 +680,7 @@
checkboxImage.assertContainsColor(boxColor)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun disabled_checkbox_checked_colors_are_customisable() {
val boxColor = Color.Green
@@ -686,6 +703,7 @@
checkboxImage.assertContainsColor(checkmarkColor)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun disabled_checkbox_unchecked_colors_are_customisable() {
// NB checkmark is erased during animation, so we don't test uncheckedCheckmarkColor
@@ -707,6 +725,7 @@
checkboxImage.assertContainsColor(boxColor)
}
+ @RequiresApi(Build.VERSION_CODES.O)
private fun verifyToggleButtonBackgroundColor(
checked: Boolean,
enabled: Boolean,
@@ -729,6 +748,7 @@
rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(expectedColor)
}
+ @RequiresApi(Build.VERSION_CODES.O)
private fun verifySplitToggleButtonBackgroundColor(
checked: Boolean,
enabled: Boolean,
@@ -797,6 +817,7 @@
toggleContentDescription = "description",
)
+@RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.verifyCheckboxButtonColors(enabled: Boolean, checked: Boolean) {
val testBackgroundColor = Color.White
var expectedContainerColor = Color.Transparent
@@ -836,6 +857,7 @@
)
}
+@RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.verifySplitCheckboxButtonColors(
enabled: Boolean,
checked: Boolean
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/CurvedTextTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/CurvedTextTest.kt
index 2d16628..f42a497 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/CurvedTextTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/CurvedTextTest.kt
@@ -16,6 +16,8 @@
package androidx.wear.compose.material3
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.testutils.assertContainsColor
import androidx.compose.testutils.assertDoesNotContainColor
@@ -35,6 +37,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@RequiresApi(Build.VERSION_CODES.O)
class CurvedTextTest {
@get:Rule val rule = createComposeRule()
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/DatePickerScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/DatePickerScreenshotTest.kt
index fba9409..057cf19 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/DatePickerScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/DatePickerScreenshotTest.kt
@@ -17,6 +17,7 @@
package androidx.wear.compose.material3
import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.testutils.assertAgainstGolden
@@ -307,6 +308,7 @@
)
.onFirst()
+ @RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.verifyDatePickerScreenshot(
testName: TestName,
screenshotRule: AndroidXScreenshotTestRule,
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/DatePickerTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/DatePickerTest.kt
index 0f074a7..e92bce3 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/DatePickerTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/DatePickerTest.kt
@@ -35,7 +35,7 @@
import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import androidx.wear.compose.material3.internal.Strings
-import androidx.wear.compose.material3.samples.DatePickerMinDateMaxDateSample
+import androidx.wear.compose.material3.samples.DatePickerFutureOnlySample
import androidx.wear.compose.material3.samples.DatePickerSample
import androidx.wear.compose.material3.samples.DatePickerYearMonthDaySample
import com.google.common.truth.Truth.assertThat
@@ -70,7 +70,7 @@
rule.setContentWithTheme {
DatePickerSample()
DatePickerYearMonthDaySample()
- DatePickerMinDateMaxDateSample()
+ DatePickerFutureOnlySample()
}
}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt
index 0d321cd..374863e 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt
@@ -16,6 +16,8 @@
package androidx.wear.compose.material3
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@@ -239,6 +241,7 @@
rule.onNodeWithTag(TEST_TAG).assertIsOff().performClick().assertIsOff()
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun is_circular_under_ltr() =
rule.isShape(
@@ -255,6 +258,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun is_circular_under_rtl() =
rule.isShape(
@@ -271,6 +275,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun allows_shape_overrides() =
rule.isShape(
@@ -384,6 +389,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_checked_primary_colors() =
rule.verifyIconToggleButtonColors(
@@ -394,6 +400,7 @@
contentColor = { MaterialTheme.colorScheme.onPrimary }
)
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_unchecked_surface_colors() =
rule.verifyIconToggleButtonColors(
@@ -404,6 +411,7 @@
contentColor = { MaterialTheme.colorScheme.onSurfaceVariant }
)
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_unchecked_surface_colors_with_alpha() =
rule.verifyIconToggleButtonColors(
@@ -416,6 +424,7 @@
contentColor = { MaterialTheme.colorScheme.onSurface.toDisabledColor() }
)
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_primary_checked_contrasting_content_color() =
rule.verifyIconToggleButtonColors(
@@ -428,6 +437,7 @@
contentColor = { MaterialTheme.colorScheme.onSurface.toDisabledColor() },
)
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun allows_custom_checked_background_override() {
val overrideColor = Color.Yellow
@@ -445,6 +455,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun allows_custom_checked_content_override() {
val overrideColor = Color.Green
@@ -460,6 +471,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun allows_custom_unchecked_background_override() {
val overrideColor = Color.Red
@@ -477,6 +489,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun allows_custom_unchecked_content_override() {
val overrideColor = Color.Green
@@ -494,6 +507,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun allows_custom_checked_disabled_background_override() {
val overrideColor = Color.Yellow
@@ -512,6 +526,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun allows_custom_checked_disabled_content_override() {
val overrideColor = Color.Green
@@ -532,6 +547,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun allows_custom_unchecked_disabled_background_override() {
val overrideColor = Color.Red
@@ -550,6 +566,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun allows_custom_unchecked_disabled_content_override() {
val overrideColor = Color.Green
@@ -606,6 +623,7 @@
.assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, overrideRole))
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun animates_corners_to_75_percent_on_click() {
val uncheckedShape = RoundedCornerShape(20.dp)
@@ -641,6 +659,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun changes_unchecked_to_checked_shape_on_click() {
val uncheckedShape = RoundedCornerShape(20.dp)
@@ -664,6 +683,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun changes_checked_to_unchecked_shape_on_click() {
val uncheckedShape = RoundedCornerShape(10.dp)
@@ -688,6 +708,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun changes_to_unchecked_pressed_shape_when_pressed_on_unchecked() {
val uncheckedShape = RoundedCornerShape(20.dp)
@@ -720,6 +741,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun changes_to_checked_pressed_shape_when_pressed_on_checked() {
val uncheckedShape = RoundedCornerShape(10.dp)
@@ -752,6 +774,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.verifyIconToggleButtonColors(
status: Status,
checked: Boolean,
@@ -778,6 +801,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun changes_unchecked_to_checked_shape_when_checked_changed() {
val uncheckedShape = RoundedCornerShape(20.dp)
@@ -802,6 +826,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun changes_checked_to_unchecked_shape_when_checked_changed() {
val uncheckedShape = RoundedCornerShape(20.dp)
@@ -833,6 +858,7 @@
.value
}
+ @RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.isShape(
shape: Shape = CircleShape,
layoutDirection: LayoutDirection,
@@ -863,6 +889,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.verifyColors(
expectedContainerColor: @Composable () -> Color,
expectedContentColor: @Composable () -> Color,
@@ -884,6 +911,7 @@
onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(finalExpectedContainerColor)
}
+ @RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.verifyCheckedStateChange(
updateState: () -> Unit,
startShape: Shape,
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/LevelIndicatorTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/LevelIndicatorTest.kt
index bda423d..4645acf 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/LevelIndicatorTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/LevelIndicatorTest.kt
@@ -16,6 +16,8 @@
package androidx.wear.compose.material3
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@@ -46,6 +48,7 @@
rule.onNodeWithTag(TEST_TAG).assertExists()
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_indicator_correct_color() {
var expectedColor: Color = Color.Unspecified
@@ -58,6 +61,7 @@
rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(expectedColor)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_track_correct_color() {
var expectedColor: Color = Color.Unspecified
@@ -70,6 +74,7 @@
rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(expectedColor)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_indicator_custom_color() {
val customColor = Color.Red
@@ -84,6 +89,7 @@
rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(customColor)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_track_custom_color() {
val customColor = Color.Red
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
index d990dc1a..d0333da 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
@@ -17,6 +17,8 @@
package androidx.wear.compose.material3
import android.content.res.Configuration
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
@@ -242,6 +244,7 @@
onNodeWithTag(TEST_TAG).assertHeightIsEqualTo(expectedSize).assertWidthIsEqualTo(expectedSize)
}
+@RequiresApi(Build.VERSION_CODES.O)
internal fun ComposeContentTestRule.verifyColors(
status: Status,
expectedContainerColor: @Composable () -> Color,
@@ -376,6 +379,7 @@
}
}
+@RequiresApi(Build.VERSION_CODES.O)
internal fun ComposeContentTestRule.verifyScreenshot(
methodName: String,
screenshotRule: AndroidXScreenshotTestRule,
@@ -396,6 +400,7 @@
onNodeWithTag(testTag).captureToImage().assertAgainstGolden(screenshotRule, methodName)
}
+@RequiresApi(Build.VERSION_CODES.O)
fun ComposeContentTestRule.verifyRoundedButtonTapAnimationEnd(
baseShape: RoundedCornerShape,
pressedShape: RoundedCornerShape,
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PageIndicatorTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PageIndicatorTest.kt
index 84d54f5..203c279 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PageIndicatorTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PageIndicatorTest.kt
@@ -16,6 +16,8 @@
package androidx.wear.compose.material3
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.compose.testutils.assertContainsColor
import androidx.compose.testutils.assertDoesNotContainColor
import androidx.compose.ui.Modifier
@@ -33,6 +35,7 @@
import org.junit.Rule
import org.junit.Test
+@RequiresApi(Build.VERSION_CODES.O)
class PageIndicatorTest {
@get:Rule val rule = createComposeRule()
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PlaceholderTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PlaceholderTest.kt
index e98e268..3234616 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PlaceholderTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PlaceholderTest.kt
@@ -16,6 +16,8 @@
package androidx.wear.compose.material3
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -41,6 +43,7 @@
import org.junit.Rule
import org.junit.Test
+@RequiresApi(Build.VERSION_CODES.O)
class PlaceholderTest {
@get:Rule val rule = createComposeRule()
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/RadioButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/RadioButtonTest.kt
index 44d9c68..4b6a973 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/RadioButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/RadioButtonTest.kt
@@ -16,6 +16,8 @@
package androidx.wear.compose.material3
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope
@@ -583,6 +585,7 @@
Assert.assertEquals(2, secondaryLabelMaxLines)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun radio_button_allows_checked_background_color_override() =
verifyRadioButtonBackgroundColor(
@@ -591,6 +594,7 @@
expectedColor = SELECTED_COLOR
)
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun radio_button_allows_unchecked_background_color_override() =
verifyRadioButtonBackgroundColor(
@@ -599,6 +603,7 @@
expectedColor = UNSELECTED_COLOR
)
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun split_radio_button_allows_checked_background_color_override() =
verifySplitRadioButtonBackgroundColor(
@@ -607,6 +612,7 @@
expectedColor = SELECTED_COLOR
)
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun split_radio_button_allows_unchecked_background_color_override() =
verifySplitRadioButtonBackgroundColor(
@@ -615,6 +621,7 @@
expectedColor = UNSELECTED_COLOR
)
+ @RequiresApi(Build.VERSION_CODES.O)
private fun verifyRadioButtonBackgroundColor(
selected: Boolean,
enabled: Boolean,
@@ -637,6 +644,7 @@
rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(expectedColor)
}
+ @RequiresApi(Build.VERSION_CODES.O)
private fun verifySplitRadioButtonBackgroundColor(
selected: Boolean,
enabled: Boolean,
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ScrollAwayTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ScrollAwayTest.kt
index 9bcedf1..90674cb 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ScrollAwayTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ScrollAwayTest.kt
@@ -16,6 +16,8 @@
package androidx.wear.compose.material3
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
@@ -59,6 +61,7 @@
class ScrollAwayTest {
@get:Rule val rule = createComposeRule()
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun showsTimeTextWithScalingLazyColumnInitially() {
val timeTextColor = Color.Red
@@ -89,6 +92,7 @@
rule.onNodeWithTag(TIME_TEXT_TAG).assertIsDisplayed()
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun showsTimeTextWithLazyColumnInitially() {
val timeTextColor = Color.Red
@@ -101,6 +105,7 @@
rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(timeTextColor)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun showsTimeTextWithColumnInitially() {
val timeTextColor = Color.Red
@@ -113,6 +118,7 @@
rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(timeTextColor)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun hidesTimeTextAfterScrollingScalingLazyColumn() {
val timeTextColor = Color.Red
@@ -130,6 +136,7 @@
rule.onNodeWithTag(TEST_TAG).captureToImage().assertDoesNotContainColor(timeTextColor)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun hidesTimeTextWithLazyColumn() {
val timeTextColor = Color.Red
@@ -152,6 +159,7 @@
rule.onNodeWithTag(TEST_TAG).captureToImage().assertDoesNotContainColor(timeTextColor)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun hidesTimeTextWithColumn() {
val timeTextColor = Color.Red
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SliderTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SliderTest.kt
index 20a08e9..3b86bf5 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SliderTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SliderTest.kt
@@ -342,7 +342,7 @@
.onChildAt(0)
.onChild()
.assertContentDescriptionContains(
- getString(Strings.SliderDecreaseButtonContentDescription)
+ getString(Strings.SliderDecreaseIconContentDescription)
)
}
@@ -364,7 +364,7 @@
.onChildAt(1)
.onChild()
.assertContentDescriptionContains(
- getString(Strings.SliderIncreaseButtonContentDescription)
+ getString(Strings.SliderIncreaseIconContentDescription)
)
}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SwitchButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SwitchButtonTest.kt
index f9dd312..ec60445 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SwitchButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SwitchButtonTest.kt
@@ -16,6 +16,8 @@
package androidx.wear.compose.material3
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
@@ -545,6 +547,7 @@
Assert.assertEquals(2, secondaryLabelMaxLines)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun switch_button_allows_checked_background_color_override() =
verifySwitchButtonBackgroundColor(
@@ -553,6 +556,7 @@
expectedColor = CHECKED_COLOR
)
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun switch_button_allows_unchecked_background_color_override() =
verifySwitchButtonBackgroundColor(
@@ -561,6 +565,7 @@
expectedColor = UNCHECKED_COLOR
)
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun split_switch_button_allows_checked_background_color_override() =
verifySplitSwitchButtonBackgroundColor(
@@ -569,6 +574,7 @@
expectedColor = CHECKED_COLOR
)
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun split_switch_button_allows_unchecked_background_color_override() =
verifySplitSwitchButtonBackgroundColor(
@@ -577,46 +583,55 @@
expectedColor = UNCHECKED_COLOR
)
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun verify_switch_button_colors_enabled_and_checked() {
rule.verifySwitchButtonColors(checked = true, enabled = true)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun verify_switch_button_colors_enabled_and_unchecked() {
rule.verifySwitchButtonColors(checked = false, enabled = true)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun verify_switch_button_colors_disabled_and_checked() {
rule.verifySwitchButtonColors(checked = true, enabled = false)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun verify_switch_button_colors_disabled_and_unchecked() {
rule.verifySwitchButtonColors(checked = false, enabled = false)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun verify_split_switch_button_colors_enabled_and_checked() {
rule.verifySplitToggleButtonColors(checked = true, enabled = true)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun verify_split_switch_button_colors_enabled_and_unchecked() {
rule.verifySplitToggleButtonColors(checked = false, enabled = true)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun verify_split_switch_button_colors_disabled_and_checked() {
rule.verifySplitToggleButtonColors(checked = true, enabled = false)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun verify_split_toggle_button_colors_disabled_and_unchecked() {
rule.verifySplitToggleButtonColors(checked = false, enabled = false)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun switch_checked_colors_are_customisable() {
val thumbColor = Color.Green
@@ -645,6 +660,7 @@
image.assertContainsColor(trackBorderColor)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun switch_unchecked_colors_are_customisable() {
val thumbColor = Color.Green
@@ -672,6 +688,7 @@
image.assertContainsColor(trackBorderColor)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun disabled_switch_checked_colors_are_customisable() {
val thumbColor = Color.Green
@@ -700,6 +717,7 @@
image.assertContainsColor(trackBorderColor)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun disabled_switch_unchecked_colors_are_customisable() {
val thumbColor = Color.Green
@@ -724,6 +742,7 @@
image.assertContainsColor(trackBorderColor)
}
+ @RequiresApi(Build.VERSION_CODES.O)
private fun verifySwitchButtonBackgroundColor(
checked: Boolean,
enabled: Boolean,
@@ -746,6 +765,7 @@
rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(expectedColor)
}
+ @RequiresApi(Build.VERSION_CODES.O)
private fun verifySplitSwitchButtonBackgroundColor(
checked: Boolean,
enabled: Boolean,
@@ -814,6 +834,7 @@
toggleContentDescription = "description",
)
+@RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.verifySwitchButtonColors(enabled: Boolean, checked: Boolean) {
val testBackgroundColor = Color.White
var expectedContainerColor = Color.Transparent
@@ -853,6 +874,7 @@
)
}
+@RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.verifySplitToggleButtonColors(
enabled: Boolean,
checked: Boolean
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt
index ddfc1b0..884643a 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt
@@ -16,6 +16,8 @@
package androidx.wear.compose.material3
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
@@ -335,6 +337,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun default_shape_is_circular() {
rule.isShape(
@@ -347,6 +350,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun allows_custom_shape_override() {
val shape = CutCornerShape(4.dp)
@@ -363,6 +367,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_enabled_text_button_colors() {
rule.verifyTextButtonColors(
@@ -373,6 +378,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_text_button_colors() {
rule.verifyTextButtonColors(
@@ -385,6 +391,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_enabled_filled_text_button_colors() {
rule.verifyTextButtonColors(
@@ -395,6 +402,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_filled_text_button_colors() {
rule.verifyTextButtonColors(
@@ -409,6 +417,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_enabled_filled_variant_text_button_colors() {
rule.verifyTextButtonColors(
@@ -419,6 +428,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_filled_variant_text_button_colors() {
rule.verifyTextButtonColors(
@@ -433,6 +443,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_enabled_filled_tonal_text_button_colors() {
rule.verifyTextButtonColors(
@@ -443,6 +454,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_filled_tonal_text_button_colors() {
rule.verifyTextButtonColors(
@@ -457,6 +469,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_enabled_outlined_text_button_colors() {
rule.verifyTextButtonColors(
@@ -467,6 +480,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_outlined_text_button_colors() {
rule.verifyTextButtonColors(
@@ -479,6 +493,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_enabled_outlined_text_button_correct_border_colors() {
val status = Status.Enabled
@@ -496,6 +511,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_outlined_text_button_correct_border_colors() {
val status = Status.Disabled
@@ -515,6 +531,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun overrides_outlined_text_button_border_color() {
val status = Status.Enabled
@@ -536,6 +553,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun animates_corners_to_75_percent_on_click() {
val baseShape = RoundedCornerShape(20.dp)
@@ -557,6 +575,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.verifyTextButtonColors(
status: Status,
colors: @Composable () -> TextButtonColors,
@@ -584,6 +603,7 @@
}
}
+@RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.isShape(
expectedShape: Shape,
colors: @Composable () -> TextButtonColors,
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextToggleButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextToggleButtonTest.kt
index 0d1b67e..fae1c050 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextToggleButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextToggleButtonTest.kt
@@ -16,6 +16,8 @@
package androidx.wear.compose.material3
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@@ -241,6 +243,7 @@
rule.onNodeWithTag(TEST_TAG).assertIsOff().performClick().assertIsOff()
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun is_circular_under_ltr() =
rule.isShape(
@@ -257,6 +260,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun is_circular_under_rtl() =
rule.isShape(
@@ -273,6 +277,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun allows_shape_overrides() =
rule.isShape(
@@ -374,6 +379,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_checked_primary_colors() =
rule.verifyTextToggleButtonColors(
@@ -384,6 +390,7 @@
contentColor = { MaterialTheme.colorScheme.onPrimary }
)
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_unchecked_surface_colors() =
rule.verifyTextToggleButtonColors(
@@ -394,6 +401,7 @@
contentColor = { MaterialTheme.colorScheme.onSurfaceVariant }
)
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_unchecked_surface_colors_with_alpha() =
rule.verifyTextToggleButtonColors(
@@ -406,6 +414,7 @@
contentColor = { MaterialTheme.colorScheme.onSurface.toDisabledColor() }
)
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun gives_disabled_primary_checked_contrasting_content_color() =
rule.verifyTextToggleButtonColors(
@@ -418,6 +427,7 @@
contentColor = { MaterialTheme.colorScheme.onSurface.toDisabledColor() },
)
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun allows_custom_checked_background_override() {
val override = Color.Yellow
@@ -433,6 +443,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun allows_custom_checked_content_override() {
val override = Color.Green
@@ -448,6 +459,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun allows_custom_unchecked_background_override() {
val override = Color.Red
@@ -463,6 +475,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun allows_custom_unchecked_content_override() {
val override = Color.Green
@@ -478,6 +491,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun allows_custom_checked_disabled_background_override() {
val override = Color.Yellow
@@ -495,6 +509,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun allows_custom_checked_disabled_content_override() {
val override = Color.Green
@@ -515,6 +530,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun allows_custom_unchecked_disabled_background_override() {
val override = Color.Red
@@ -533,6 +549,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun allows_custom_unchecked_disabled_content_override() {
val override = Color.Green
@@ -590,6 +607,7 @@
.assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, overrideRole))
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun animates_corners_to_75_percent_on_click() {
val uncheckedShape = RoundedCornerShape(20.dp)
@@ -625,6 +643,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun changes_unchecked_to_checked_shape_on_click() {
val uncheckedShape = RoundedCornerShape(20.dp)
@@ -648,6 +667,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun changes_checked_to_unchecked_shape_on_click() {
val uncheckedShape = RoundedCornerShape(10.dp)
@@ -671,6 +691,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun changes_to_unchecked_pressed_shape_when_pressed_on_unchecked() {
val uncheckedShape = RoundedCornerShape(20.dp)
@@ -703,6 +724,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun changes_to_checked_pressed_shape_when_pressed_on_checked() {
val uncheckedShape = RoundedCornerShape(10.dp)
@@ -735,6 +757,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun changes_unchecked_to_checked_shape_when_checked_changed() {
val uncheckedShape = RoundedCornerShape(20.dp)
@@ -759,6 +782,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
@Test
fun changes_checked_to_unchecked_shape_when_checked_changed() {
val uncheckedShape = RoundedCornerShape(20.dp)
@@ -790,6 +814,7 @@
.value
}
+ @RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.verifyTextToggleButtonColors(
status: Status,
checked: Boolean,
@@ -816,6 +841,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.isShape(
shape: Shape = CircleShape,
layoutDirection: LayoutDirection,
@@ -846,6 +872,7 @@
)
}
+ @RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.verifyColors(
expectedContainerColor: @Composable () -> Color,
expectedContentColor: @Composable () -> Color,
@@ -879,6 +906,7 @@
.assertWidthIsEqualTo(expectedSize)
}
+ @RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.verifyCheckedStateChange(
updateState: () -> Unit,
startShape: Shape,
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimePickerScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimePickerScreenshotTest.kt
index 1e80a24..3511aa3 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimePickerScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimePickerScreenshotTest.kt
@@ -17,6 +17,7 @@
package androidx.wear.compose.material3
import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.compose.runtime.Composable
import androidx.compose.testutils.assertAgainstGolden
import androidx.compose.ui.Modifier
@@ -138,6 +139,7 @@
}
)
+ @RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.verifyTimePickerScreenshot(
methodName: String,
screenshotRule: AndroidXScreenshotTestRule,
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AlertDialog.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AlertDialog.kt
index c12cee9..b7e5fcf 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AlertDialog.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AlertDialog.kt
@@ -30,9 +30,6 @@
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Check
-import androidx.compose.material.icons.outlined.Close
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
@@ -48,6 +45,7 @@
import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
import androidx.wear.compose.material3.PaddingDefaults.horizontalContentPadding
import androidx.wear.compose.material3.PaddingDefaults.verticalContentPadding
+import androidx.wear.compose.material3.internal.Icons
import androidx.wear.compose.material3.internal.Strings
import androidx.wear.compose.material3.internal.getString
import androidx.wear.compose.materialcore.screenHeightDp
@@ -412,12 +410,15 @@
) {
val state = rememberScalingLazyListState(initialCenterItemIndex = 0)
+ val noTextAndContent = text == null && content == null
ScreenScaffold(
scrollState = state,
edgeButton = edgeButton,
modifier = modifier,
contentPadding = contentPadding,
- edgeButtonSpacing = AlertEdgeButtonSpacing,
+ edgeButtonSpacing =
+ if (noTextAndContent) AlertEdgeButtonSpacingWithoutTextAndContent
+ else AlertEdgeButtonSpacing,
) {
ScalingLazyColumn(
state = state,
@@ -568,18 +569,18 @@
/** Default icon for the confirm button. */
public val ConfirmIcon: @Composable RowScope.() -> Unit = {
Icon(
- imageVector = Icons.Filled.Check,
+ imageVector = Icons.Check,
contentDescription = getString(Strings.AlertDialogContentDescriptionConfirmButton),
- modifier = Modifier.size(36.dp).align(Alignment.CenterVertically)
+ modifier = Modifier.size(28.dp).align(Alignment.CenterVertically)
)
}
/** Default icon for the dismiss button. */
public val DismissIcon: @Composable RowScope.() -> Unit = {
Icon(
- imageVector = Icons.Outlined.Close,
+ imageVector = Icons.Close,
contentDescription = getString(Strings.AlertDialogContentDescriptionDismissButton),
- modifier = Modifier.size(36.dp).align(Alignment.CenterVertically)
+ modifier = Modifier.size(28.dp).align(Alignment.CenterVertically)
)
}
@@ -603,7 +604,7 @@
item { TextMessage(text) }
}
if (content != null) {
- item { Spacer(Modifier.height(ContentTopSpacing)) }
+ item { Spacer(Modifier.height(AlertContentTopSpacing)) }
content()
}
}
@@ -681,14 +682,14 @@
}
internal val AlertIconBottomSpacing = 4.dp
+internal val AlertTextMessageTopSpacing = 4.dp
internal val AlertEdgeButtonSpacing = 4.dp
-internal val AlertTextMessageTopSpacing = 8.dp
+internal val AlertEdgeButtonSpacingWithoutTextAndContent = 16.dp
internal val ConfirmDismissButtonsTopSpacing = 12.dp
+internal val AlertContentTopSpacing = 8.dp
internal const val ConfirmDismissButtonsBottomSpacingFraction = 0.045f
internal const val AlertTitleMaxLines = 3
-private val ContentTopSpacing = 8.dp
-private val BottomButtonSpacing = 8.dp
private const val TextPaddingFraction = 0.0416f
private const val TitlePaddingFraction = 0.12f
private const val ConfirmDismissBetweenButtonsPaddingFraction = 0.03f
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt
index 5a3f11e..b3a9562 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt
@@ -915,7 +915,8 @@
* @sample androidx.wear.compose.material3.samples.FilledTonalCompactButtonSample
*
* Example of a [CompactButton] with an icon and label and with
- * [ButtonDefaults.outlinedButtonBorder] and [ButtonDefaults.outlinedButtonColors]
+ * [ButtonDefaults.outlinedButtonBorder] and [ButtonDefaults.outlinedButtonColors]. The example
+ * includes a [Text] that expands and collapses when the [CompactButton] is clicked.
*
* @sample androidx.wear.compose.material3.samples.OutlinedCompactButtonSample
*
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ButtonGroup.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ButtonGroup.kt
index 56b0729..ebfc393f 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ButtonGroup.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ButtonGroup.kt
@@ -16,67 +16,53 @@
package androidx.wear.compose.material3
+import androidx.annotation.FloatRange
import androidx.annotation.VisibleForTesting
import androidx.compose.animation.core.Animatable
-import androidx.compose.foundation.interaction.InteractionSource
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.ParentDataModifierNode
+import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.unit.takeOrElse
import androidx.compose.ui.util.fastForEachIndexed
+import androidx.compose.ui.util.fastIsFinite
import androidx.compose.ui.util.fastMap
import androidx.compose.ui.util.fastMapIndexed
import androidx.wear.compose.materialcore.screenHeightDp
import kotlin.math.abs
import kotlin.math.roundToInt
+import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch
-/** Scope for the children of a [ButtonGroup] */
-public class ButtonGroupScope {
- internal val items = mutableListOf<ButtonGroupItem>()
-
- /**
- * Adds an item to a [ButtonGroup]
- *
- * @param interactionSource the interactionSource used to detect press/release events. Should be
- * the same one used in the content in this slot, which is typically a [Button].
- * @param minWidth the minimum width this item can be. This will only be used if distributing
- * the available space results on a item falling below it's minimum width.
- * @param weight the main way of distributing available space. In most cases, items will have a
- * width assigned proportional to their width (and available space). The exception is if that
- * will make some item(s) width fall below it's minWidth.
- * @param content the content to use for this item. Usually, this will be one of the [Button]
- * variants.
- */
- public fun buttonGroupItem(
- interactionSource: InteractionSource,
- minWidth: Dp = minimumInteractiveComponentSize,
- weight: Float = 1f,
- content: @Composable () -> Unit
- ): Boolean = items.add(ButtonGroupItem(interactionSource, minWidth, weight, content))
-}
-
/**
- * Layout component to implement an expressive group of buttons, that react to touch by growing the
- * touched button, (while the neighbor(s) shrink to accommodate and keep the group width constant).
+ * Layout component to implement an expressive group of buttons in a row, that react to touch by
+ * growing the touched button, (while the neighbor(s) shrink to accommodate and keep the group width
+ * constant).
*
* Example of a [ButtonGroup]:
*
* @sample androidx.wear.compose.material3.samples.ButtonGroupSample
+ *
+ * Example of 3 buttons, the middle one bigger [ButtonGroup]:
+ *
+ * @sample androidx.wear.compose.material3.samples.ButtonGroupThreeButtonsSample
* @param modifier Modifier to be applied to the button group
* @param spacing the amount of spacing between buttons
* @param expansionWidth how much buttons grow when pressed
@@ -84,7 +70,8 @@
* content
* @param verticalAlignment the vertical alignment of the button group's children.
* @param content the content and properties of each button. The Ux guidance is to use no more than
- * 3 buttons within a ButtonGroup.
+ * 3 buttons within a ButtonGroup. Note that this content is on the [ButtonGroupScope], to provide
+ * access to 3 new modifiers to configure the buttons.
*/
@Composable
public fun ButtonGroup(
@@ -93,98 +80,77 @@
expansionWidth: Dp = ButtonGroupDefaults.ExpansionWidth,
contentPadding: PaddingValues = ButtonGroupDefaults.fullWidthPaddings(),
verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
- content: ButtonGroupScope.() -> Unit
+ content: @Composable ButtonGroupScope.() -> Unit
) {
- val actualContent = ButtonGroupScope().apply(block = content)
-
- val pressedStates = remember { Array(actualContent.items.size) { mutableStateOf(false) } }
-
- val animatedSizes = remember { Array(actualContent.items.size) { Animatable(0f) } }
-
val expandAmountPx = with(LocalDensity.current) { expansionWidth.toPx() }
val downAnimSpec = MaterialTheme.motionScheme.fastSpatialSpec<Float>().faster(100f)
val upAnimSpec = MaterialTheme.motionScheme.slowSpatialSpec<Float>()
- LaunchedEffect(actualContent.items) {
- launch {
- val pressInteractions =
- Array(actualContent.items.size) { mutableListOf<PressInteraction.Press>() }
-
- merge(
- flows =
- Array(actualContent.items.size) { index ->
- // Annotate each flow with the item index it is related to.
- actualContent.items[index].interactionSource.interactions.map {
- interaction ->
- index to interaction
- }
- }
- )
- .collect { (index, interaction) ->
- when (interaction) {
- is PressInteraction.Press -> pressInteractions[index].add(interaction)
- is PressInteraction.Release ->
- pressInteractions[index].remove(interaction.press)
- is PressInteraction.Cancel ->
- pressInteractions[index].remove(interaction.press)
- }
- pressedStates[index].value = pressInteractions[index].isNotEmpty()
+ val scope = remember {
+ object : ButtonGroupScope {
+ override fun Modifier.weight(weight: Float): Modifier {
+ require(weight >= 0.0) {
+ "invalid weight $weight; must be greater or equal to zero"
}
- }
-
- actualContent.items.indices.forEach { index ->
- launch {
- snapshotFlow { pressedStates[index].value }
- .collectLatest { value ->
- if (value) {
- animatedSizes[index].animateTo(expandAmountPx, downAnimSpec)
- } else {
- animatedSizes[index].animateTo(0f, upAnimSpec)
- }
- }
+ return this.then(ButtonGroupElement(weight = weight))
}
+
+ override fun Modifier.minWidth(minWidth: Dp): Modifier {
+ require(minWidth > 0.dp) { "invalid minWidth $minWidth; must be greater than zero" }
+ return this.then(ButtonGroupElement(minWidth = minWidth))
+ }
+
+ override fun Modifier.enlargeOnPress(interactionSource: MutableInteractionSource) =
+ this.then(
+ EnlargeOnPressElement(
+ interactionSource = interactionSource,
+ downAnimSpec,
+ upAnimSpec
+ )
+ )
}
}
- Layout(
- modifier = modifier.padding(contentPadding),
- content = { actualContent.items.fastForEach { it.content() } }
- ) { measurables, constraints ->
+ Layout(modifier = modifier.padding(contentPadding), content = { scope.content() }) {
+ measurables,
+ constraints ->
require(constraints.hasBoundedWidth) { "ButtonGroup width cannot be unbounded." }
- require(measurables.size == actualContent.items.size) {
- "ButtonGroup's items have to produce exactly one composable each."
- }
val width = constraints.maxWidth
val spacingPx = spacing.roundToPx()
+ val configs =
+ Array(measurables.size) {
+ measurables[it].parentData as? ButtonGroupParentData
+ ?: ButtonGroupParentData.DEFAULT
+ }
+
+ val animatedSizes = Array(measurables.size) { configs[it].pressedState.value }
+
// TODO: Cache this if it proves to be computationally intensive.
val widths =
- computeWidths(
- actualContent.items.fastMap { it.minWidth.toPx() to it.weight },
- spacingPx,
- width
- )
+ computeWidths(configs.map { it.minWidth.toPx() to it.weight }, spacingPx, width)
// Add animated grow/shrink
- if (actualContent.items.size > 1) {
- animatedSizes.forEachIndexed { index, value ->
+ if (measurables.size > 1) {
+ for (index in measurables.indices) {
+ val value = animatedSizes[index] * expandAmountPx
// How much we need to grow the pressed item.
val growth: Int
- if (index in 1 until animatedSizes.lastIndex) {
- // index is in the middle. Ensure we keep the size of the middle element with
+ if (index in 1 until measurables.lastIndex) {
+ // index is in the middle. Ensure we keep the size of the middle item with
// the same parity, so its content remains in place.
- growth = (value.value / 2).roundToInt() * 2
+ growth = (value / 2).roundToInt() * 2
widths[index - 1] -= growth / 2
widths[index + 1] -= growth / 2
} else {
- growth = value.value.roundToInt()
+ growth = value.roundToInt()
if (index == 0) {
// index == 0, and we know there are at least 2 items.
widths[1] -= growth
} else {
- // index == animatedSizes.lastIndex, and we know there are at least 2 items.
+ // index == measurables.lastIndex, and we know there are at least 2 items.
widths[index - 1] -= growth
}
}
@@ -218,6 +184,37 @@
}
}
+public interface ButtonGroupScope {
+ /**
+ * [ButtonGroup] uses a ratio of all sibling item [weight]s to assign a width to each item. The
+ * horizontal space is distributed using [weight] first, and this will only be changed if any
+ * item would be smaller than its [minWidth]. See also [Modifier.minWidth].
+ *
+ * @param weight The main way of distributing available space. This is a relative measure, and
+ * items with no weight specified will have a default of 1f.
+ */
+ public fun Modifier.weight(
+ @FloatRange(from = 0.0, fromInclusive = false) weight: Float
+ ): Modifier
+
+ /**
+ * Specifies the minimum width this item can be, in Dp. This will only be used if distributing
+ * the available space results in a item falling below its minimum width. Note that this is only
+ * used before animations, pressing a button may result on neighbor button(s) going below their
+ * minWidth. See also [Modifier.weight]
+ *
+ * @param minWidth the minimum width. If none is specified, minimumInteractiveComponentSize is
+ * used.
+ */
+ public fun Modifier.minWidth(minWidth: Dp = minimumInteractiveComponentSize): Modifier
+
+ /**
+ * Specifies the interaction source to use with this item. This is used to listen to events and
+ * grow/shrink the buttons in reaction.
+ */
+ public fun Modifier.enlargeOnPress(interactionSource: MutableInteractionSource): Modifier
+}
+
/** Contains the default values used by [ButtonGroup] */
public object ButtonGroupDefaults {
/**
@@ -246,22 +243,160 @@
/**
* Data class to configure one item in a [ButtonGroup]
*
- * @param interactionSource the interactionSource used to detect press/release events. Should be the
- * same one used in the content in this slot, which is typically a [Button].
- * @param minWidth the minimum width this item can be. This will only be used if distributing the
- * available space results on a item falling below it's minimum width.
* @param weight the main way of distributing available space. In most cases, items will have a
- * width assigned proportional to their width (and available space). The exception is if that will
- * make some item(s) width fall below it's minWidth.
- * @param content the content to use for this item. Usually, this will be one of the [Button]
- * variants.
+ * width assigned proportional to their weight (and available space). The exception is if that
+ * will make some item(s) width fall below its minWidth.
+ * @param minWidth the minimum width this item can be. This will only be used if distributing the
+ * available space results on a item falling below its minimum width.
+ * @param pressedState an animated float between 0f and 1f that captures an animated, continuous
+ * version of the item's interaction source pressed state.
*/
-internal data class ButtonGroupItem(
- val interactionSource: InteractionSource,
- val minWidth: Dp = minimumInteractiveComponentSize,
- val weight: Float = 1f,
- val content: @Composable () -> Unit
-)
+internal data class ButtonGroupParentData(
+ val weight: Float,
+ val minWidth: Dp,
+ val pressedState: Animatable<Float, AnimationVector1D>,
+) {
+ companion object {
+ val DEFAULT = ButtonGroupParentData(1f, minimumInteractiveComponentSize, Animatable(0f))
+ }
+}
+
+internal class ButtonGroupElement(
+ val weight: Float = Float.NaN,
+ val minWidth: Dp = Dp.Unspecified,
+) : ModifierNodeElement<ButtonGroupNode>() {
+
+ override fun create(): ButtonGroupNode {
+ return ButtonGroupNode(weight, minWidth)
+ }
+
+ override fun update(node: ButtonGroupNode) {
+ node.weight = weight
+ node.minWidth = minWidth
+ }
+
+ override fun InspectorInfo.inspectableProperties() {
+ name = "ButtonGroupElement"
+ properties["weight"] = weight
+ properties["minWidth"] = minWidth
+ }
+
+ override fun hashCode() = weight.hashCode() * 31 + minWidth.hashCode()
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ val otherModifier = other as? ButtonGroupNode ?: return false
+ return weight == otherModifier.weight && minWidth == otherModifier.minWidth
+ }
+}
+
+internal class ButtonGroupNode(var weight: Float, var minWidth: Dp) :
+ ParentDataModifierNode, Modifier.Node() {
+ override fun Density.modifyParentData(parentData: Any?) =
+ (parentData as? ButtonGroupParentData ?: ButtonGroupParentData.DEFAULT).let { prev ->
+ ButtonGroupParentData(
+ if (weight.fastIsFinite()) weight else prev.weight,
+ minWidth.takeOrElse { prev.minWidth },
+ prev.pressedState
+ )
+ }
+}
+
+internal class EnlargeOnPressElement(
+ val interactionSource: MutableInteractionSource,
+ val downAnimSpec: AnimationSpec<Float>,
+ val upAnimSpec: AnimationSpec<Float>,
+) : ModifierNodeElement<EnlargeOnPressNode>() {
+
+ override fun create(): EnlargeOnPressNode {
+ return EnlargeOnPressNode(interactionSource, downAnimSpec, upAnimSpec)
+ }
+
+ override fun update(node: EnlargeOnPressNode) {
+ if (node.interactionSource != interactionSource) {
+ node.interactionSource = interactionSource
+ node.launchCollectionJob()
+ }
+ node.downAnimSpec = downAnimSpec
+ node.upAnimSpec = upAnimSpec
+ }
+
+ override fun InspectorInfo.inspectableProperties() {
+ name = "EnlargeOnPressElement"
+ properties["interactionSource"] = interactionSource
+ properties["downAnimSpec"] = downAnimSpec
+ properties["upAnimSpec"] = upAnimSpec
+ }
+
+ override fun hashCode() =
+ (interactionSource.hashCode() * 31 + downAnimSpec.hashCode()) * 31 + upAnimSpec.hashCode()
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ val otherModifier = other as? EnlargeOnPressNode ?: return false
+ return interactionSource == otherModifier.interactionSource &&
+ downAnimSpec == otherModifier.downAnimSpec &&
+ upAnimSpec == otherModifier.upAnimSpec
+ }
+}
+
+internal class EnlargeOnPressNode(
+ var interactionSource: MutableInteractionSource,
+ var downAnimSpec: AnimationSpec<Float>,
+ var upAnimSpec: AnimationSpec<Float>,
+) : ParentDataModifierNode, Modifier.Node() {
+ private val pressedAnimatable: Animatable<Float, AnimationVector1D> = Animatable(0f)
+
+ private var collectionJob: Job? = null
+
+ override fun onAttach() {
+ super.onAttach()
+
+ launchCollectionJob()
+ }
+
+ override fun onDetach() {
+ super.onDetach()
+ collectionJob = null
+ }
+
+ internal fun launchCollectionJob() {
+ collectionJob?.cancel()
+ collectionJob =
+ coroutineScope.launch {
+ val pressInteractions = mutableListOf<PressInteraction.Press>()
+
+ launch {
+ // Use collect here to ensure we don't lose any events.
+ interactionSource.interactions
+ .map { interaction ->
+ when (interaction) {
+ is PressInteraction.Press -> pressInteractions.add(interaction)
+ is PressInteraction.Release ->
+ pressInteractions.remove(interaction.press)
+ is PressInteraction.Cancel ->
+ pressInteractions.remove(interaction.press)
+ }
+ pressInteractions.isNotEmpty()
+ }
+ .distinctUntilChanged()
+ .collectLatest { pressed ->
+ if (pressed) {
+ launch { pressedAnimatable.animateTo(1f, downAnimSpec) }
+ } else {
+ waitUntil { pressedAnimatable.value > 0.75f }
+ pressedAnimatable.animateTo(0f, upAnimSpec)
+ }
+ }
+ }
+ }
+ }
+
+ override fun Density.modifyParentData(parentData: Any?) =
+ (parentData as? ButtonGroupParentData ?: ButtonGroupParentData.DEFAULT).let { prev ->
+ ButtonGroupParentData(prev.weight, prev.minWidth, pressedAnimatable)
+ }
+}
// TODO: Does it make sense to unify these 2 classes?
private data class ComputeHelper(
@@ -295,8 +430,8 @@
val totalWeight = helper.map { it.weight }.sum()
val extraSpace = availableWidth - minSpaceNeeded
- // TODO: should we really handle the totalWeight <= 0 case? If so, we need to leave items at
- // their minWidth and center the whole thing?
+ // TODO: should we really handle the totalWeight <= 0 case? If so, we need to leave items
+ // at their minWidth and center the whole thing?
if (totalWeight > 0) {
for (ix in helper.indices) {
// Initial distribution ignores minWidth.
@@ -312,9 +447,11 @@
// Sort them. We will have:
// * Items with weight == 0 and less width required (usually 0)
// * Items with weight > 0 and less width required
- // * Items with weight > 0, sorted for the order in which they may get below their minimum width
+ // * Items with weight > 0, sorted for the order in which they may get below their
+ // minimum width
// as we take away space.
- // * Items with weight == 0 and enough width (This can only happen if totalWeight == 0)
+ // * Items with weight == 0 and enough width (This can only happen if totalWeight
+ // == 0)
helper.sortBy {
if (it.weight == 0f) {
if (it.width < it.minWidth) Float.MIN_VALUE else Float.MAX_VALUE
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/DatePicker.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/DatePicker.kt
index af2130d..6d981e3 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/DatePicker.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/DatePicker.kt
@@ -16,7 +16,9 @@
package androidx.wear.compose.material3
+import android.os.Build
import android.text.format.DateFormat
+import androidx.annotation.RequiresApi
import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Arrangement
@@ -84,9 +86,9 @@
*
* @sample androidx.wear.compose.material3.samples.DatePickerYearMonthDaySample
*
- * Example of a [DatePicker] with minDate and maxDate:
+ * Example of a [DatePicker] with a minDate:
*
- * @sample androidx.wear.compose.material3.samples.DatePickerMinDateMaxDateSample
+ * @sample androidx.wear.compose.material3.samples.DatePickerFutureOnlySample
* @param initialDate The initial value to be displayed in the DatePicker.
* @param onDatePicked The callback that is called when the user confirms the date selection. It
* provides the selected date as [LocalDate]
@@ -96,6 +98,7 @@
* @param datePickerType The different [DatePickerType] supported by this [DatePicker].
* @param colors [DatePickerColors] to be applied to the DatePicker.
*/
+@RequiresApi(Build.VERSION_CODES.O)
@Composable
public fun DatePicker(
initialDate: LocalDate,
@@ -696,6 +699,7 @@
else -> arrayOf(DatePickerOption.Day, DatePickerOption.Month, DatePickerOption.Year)
}
+@RequiresApi(Build.VERSION_CODES.O)
private fun verifyDates(
date: LocalDate,
minDate: LocalDate,
@@ -705,6 +709,7 @@
require(date in minDate..maxDate) { "date should lie between minDate and maxDate" }
}
+@RequiresApi(Build.VERSION_CODES.O)
private fun getMonthNames(pattern: String): List<String> {
val monthFormatter = DateTimeFormatter.ofPattern(pattern)
val months = 1..12
@@ -730,6 +735,7 @@
}
}
+@RequiresApi(Build.VERSION_CODES.O)
private class DatePickerState(
initialDate: LocalDate,
initialDateMinYear: LocalDate?,
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ListHeader.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ListHeader.kt
index d8b5fbf..8e8c181 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ListHeader.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ListHeader.kt
@@ -62,7 +62,7 @@
public fun ListHeader(
modifier: Modifier = Modifier,
backgroundColor: Color = Color.Transparent,
- contentColor: Color = ListHeaderDefaults.ContentColor,
+ contentColor: Color = ListHeaderDefaults.contentColor,
contentPadding: PaddingValues = ListHeaderDefaults.ContentPadding,
content: @Composable RowScope.() -> Unit
) {
@@ -157,7 +157,7 @@
PaddingValues(HorizontalPadding, TopPadding, HorizontalPadding, SubHeaderBottomPadding)
/** The default color for ListHeader */
- public val ContentColor: Color
+ public val contentColor: Color
@Composable get() = ListHeaderTokens.ContentColor.value
/** The default color for ListSubHeader */
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/PickerGroup.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/PickerGroup.kt
index ece1094..78277fc 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/PickerGroup.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/PickerGroup.kt
@@ -24,6 +24,7 @@
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RoundButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RoundButton.kt
index 92016f0..13726ac 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RoundButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RoundButton.kt
@@ -65,7 +65,6 @@
content: @Composable BoxScope.() -> Unit,
) {
val borderStroke = border(enabled)
-
Box(
contentAlignment = Alignment.Center,
modifier =
@@ -158,11 +157,10 @@
onPressAnimationSpec = onPressAnimationSpec,
onReleaseAnimationSpec = onReleaseAnimationSpec
)
-
- Pair(finalShape, finalInteractionSource)
+ finalShape to finalInteractionSource
} else {
// Fallback to static uncheckedShape if no other shapes, or not animatable
- Pair(defaultShape, interactionSource)
+ defaultShape to interactionSource
}
@Composable
@@ -195,22 +193,19 @@
val finalInteractionSource = interactionSource ?: remember { MutableInteractionSource() }
- val finalShape =
- rememberAnimatedToggleRoundedCornerShape(
- interactionSource = finalInteractionSource,
- uncheckedCornerSize = uncheckedShape.topEnd,
- checkedCornerSize = checkedShape.topEnd,
- uncheckedPressedCornerSize = uncheckedPressedShape.topEnd,
- checkedPressedCornerSize = checkedPressedShape.topEnd,
- checked = checked,
- onPressAnimationSpec = onPressAnimationSpec,
- onReleaseAnimationSpec = onReleaseAnimationSpec,
- )
-
- Pair(finalShape, finalInteractionSource)
+ rememberAnimatedToggleRoundedCornerShape(
+ interactionSource = finalInteractionSource,
+ uncheckedCornerSize = uncheckedShape.topEnd,
+ checkedCornerSize = checkedShape.topEnd,
+ uncheckedPressedCornerSize = uncheckedPressedShape.topEnd,
+ checkedPressedCornerSize = checkedPressedShape.topEnd,
+ checked = checked,
+ onPressAnimationSpec = onPressAnimationSpec,
+ onReleaseAnimationSpec = onReleaseAnimationSpec,
+ ) to finalInteractionSource
} else {
// Fallback to static uncheckedShape if no other shapes, or not animatable
- Pair(uncheckedShape, interactionSource)
+ uncheckedShape to interactionSource
}
}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ScrollIndicator.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ScrollIndicator.kt
index 816d82c..76e591d 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ScrollIndicator.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ScrollIndicator.kt
@@ -145,7 +145,7 @@
* For more information, see the
* [Scroll indicators](https://developer.android.com/training/wearables/components/scroll) guide.
*
- * Example of a sample ScrollIndicator with LazyColumn:
+ * Example of a sample ScrollIndicator with ScalingLazyColumn:
*
* @sample androidx.wear.compose.material3.samples.ScrollIndicatorWithSLCSample
* @param state the [ScalingLazyListState] to use as the basis for the ScrollIndicatorState.
@@ -172,6 +172,34 @@
positionAnimationSpec = positionAnimationSpec
)
+/**
+ * A composable that displays a visual indicator of scrolling progress within a scrollable
+ * container.
+ *
+ * Creates an [ScrollIndicator] based on the values in a [TransformingLazyColumnState] object that a
+ * [TransformingLazyColumn] uses.
+ *
+ * Typically used with the [ScreenScaffold] but can be used to decorate any full screen situation.
+ *
+ * To comply with Wear Material Design guidelines, this composable should be aligned to the center
+ * end of the screen using `Alignment.CenterEnd`. It will appear on the right in Ltr orientation and
+ * on the left in Rtl orientation.
+ *
+ * It detects if the screen is round or square and draws itself as a curve or line.
+ *
+ * For more information, see the
+ * [Scroll indicators](https://developer.android.com/training/wearables/components/scroll) guide.
+ *
+ * Example of a sample ScrollIndicator with TransformingLazyColumn:
+ *
+ * @sample androidx.wear.compose.material3.samples.ScrollIndicatorWithTLCSample
+ * @param state the [TransformingLazyColumnState] to use as the basis for the ScrollIndicatorState.
+ * @param modifier The modifier to be applied to the component
+ * @param reverseDirection Reverses direction of ScrollIndicator if true
+ * @param positionAnimationSpec [AnimationSpec] for position animation. The Position animation is
+ * used for animating changes to the scroll size and position. To disable this animation [snap]
+ * AnimationSpec should be passed instead.
+ */
@Composable
public fun ScrollIndicator(
state: TransformingLazyColumnState,
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
index 1b64b43..b127853 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
@@ -52,8 +52,8 @@
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material3.SliderDefaults.MaxSegmentSteps
import androidx.wear.compose.material3.internal.Icons
-import androidx.wear.compose.material3.internal.Strings.Companion.SliderDecreaseButtonContentDescription
-import androidx.wear.compose.material3.internal.Strings.Companion.SliderIncreaseButtonContentDescription
+import androidx.wear.compose.material3.internal.Strings.Companion.SliderDecreaseIconContentDescription
+import androidx.wear.compose.material3.internal.Strings.Companion.SliderIncreaseIconContentDescription
import androidx.wear.compose.material3.internal.getString
import androidx.wear.compose.material3.tokens.MotionTokens
import androidx.wear.compose.material3.tokens.SliderTokens
@@ -292,7 +292,7 @@
/** Defaults used by slider. */
public object SliderDefaults {
- /** The recommended size for Slider [DecreaseIcon] and [IncreaseIcon] button icons. */
+ /** The recommended size for Slider button icons. */
public val IconSize: Dp = 24.dp
/** The maximum recommended number of steps for a segmented [Slider]. */
@@ -302,19 +302,37 @@
public val shape: Shape
@Composable get() = SliderTokens.ContainerShape.value
- /** Decrease Icon. */
+ /**
+ * The recommended decrease icon.
+ *
+ * @param modifier Modifier to be applied to the decrease icon.
+ * @param contentDescription The content description for the decrease icon.
+ */
@Composable
- public fun DecreaseIcon(modifier: Modifier = Modifier): Unit =
- Icon(
- Icons.Remove,
- getString(SliderDecreaseButtonContentDescription),
- modifier.size(IconSize)
- )
+ public fun DecreaseIcon(
+ modifier: Modifier = Modifier,
+ contentDescription: String = decreaseIconContentDescription
+ ): Unit = Icon(Icons.Remove, contentDescription, modifier.size(IconSize))
- /** Increase Icon. */
+ /**
+ * The recommended increase icon.
+ *
+ * @param modifier Modifier to be applied to the increase icon.
+ * @param contentDescription The content description for the increase icon.
+ */
@Composable
- public fun IncreaseIcon(modifier: Modifier = Modifier): Unit =
- Icon(Icons.Add, getString(SliderIncreaseButtonContentDescription), modifier.size(IconSize))
+ public fun IncreaseIcon(
+ modifier: Modifier = Modifier,
+ contentDescription: String = increaseIconContentDescription
+ ): Unit = Icon(Icons.Add, contentDescription, modifier.size(IconSize))
+
+ /** The default content description for the increase icon */
+ public val increaseIconContentDescription: String
+ @Composable get() = getString(SliderIncreaseIconContentDescription)
+
+ /** The default content description for the decrease button */
+ public val decreaseIconContentDescription: String
+ @Composable get() = getString(SliderDecreaseIconContentDescription)
/**
* Creates a [SliderColors] that represents the default background and content colors used in an
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TextToggleButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TextToggleButton.kt
index 2ae6941..acd3223 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TextToggleButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TextToggleButton.kt
@@ -39,6 +39,7 @@
import androidx.wear.compose.material3.tokens.MotionTokens
import androidx.wear.compose.material3.tokens.ShapeTokens
import androidx.wear.compose.material3.tokens.TextToggleButtonTokens
+import androidx.wear.compose.materialcore.ToggleButton
import androidx.wear.compose.materialcore.animateSelectionColor
/**
@@ -110,7 +111,7 @@
interactionSource = interactionSource
)
- androidx.wear.compose.materialcore.ToggleButton(
+ ToggleButton(
checked = checked,
onCheckedChange = onCheckedChange,
modifier = modifier.minimumInteractiveComponentSize(),
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt
index 01b7923..4f2571d 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt
@@ -16,6 +16,8 @@
package androidx.wear.compose.material3
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Arrangement
@@ -95,6 +97,7 @@
* whether to show seconds or AM/PM selector as well as hours and minutes.
* @param colors [TimePickerColors] be applied to the TimePicker.
*/
+@RequiresApi(Build.VERSION_CODES.O)
@Composable
public fun TimePicker(
initialTime: LocalTime,
@@ -593,6 +596,7 @@
}
/* Returns the picker data for the third column (AM/PM or seconds) based on the time picker type. */
+@RequiresApi(Build.VERSION_CODES.O)
@Composable
private fun getOptionalThirdPicker(
timePickerType: TimePickerType,
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Icons.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Icons.kt
index e23e152..96fee10 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Icons.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Icons.kt
@@ -123,6 +123,47 @@
private var _check: ImageVector? = null
+ internal val Close: ImageVector
+ get() {
+ if (_close != null) {
+ return _close!!
+ }
+ _close =
+ materialIcon(name = "Close") {
+ materialPath {
+ moveTo(480.0f, 416.35f)
+ lineTo(287.83f, 224.17f)
+ quadTo(275.15f, 211.5f, 256.0f, 211.5f)
+ quadTo(236.85f, 211.5f, 224.17f, 224.17f)
+ quadTo(211.5f, 236.85f, 211.5f, 256.0f)
+ quadTo(211.5f, 275.15f, 224.17f, 287.83f)
+ lineTo(416.35f, 480.0f)
+ lineTo(224.17f, 672.17f)
+ quadTo(211.5f, 684.85f, 211.5f, 704.0f)
+ quadTo(211.5f, 723.15f, 224.17f, 735.83f)
+ quadTo(236.85f, 748.5f, 256.0f, 748.5f)
+ quadTo(275.15f, 748.5f, 287.83f, 735.83f)
+ lineTo(480.0f, 543.65f)
+ lineTo(672.17f, 735.83f)
+ quadTo(684.85f, 748.5f, 704.0f, 748.5f)
+ quadTo(723.15f, 748.5f, 735.83f, 735.83f)
+ quadTo(748.5f, 723.15f, 748.5f, 704.0f)
+ quadTo(748.5f, 684.85f, 735.83f, 672.17f)
+ lineTo(543.65f, 480.0f)
+ lineTo(735.83f, 287.83f)
+ quadTo(748.5f, 275.15f, 748.5f, 256.0f)
+ quadTo(748.5f, 236.85f, 735.83f, 224.17f)
+ quadTo(723.15f, 211.5f, 704.0f, 211.5f)
+ quadTo(684.85f, 211.5f, 672.17f, 224.17f)
+ lineTo(480.0f, 416.35f)
+ close()
+ }
+ }
+ return _close!!
+ }
+
+ private var _close: ImageVector? = null
+
internal object AutoMirrored {
internal val KeyboardArrowRight: ImageVector
get() {
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Strings.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Strings.kt
index 1d89c64..a618cff 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Strings.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Strings.kt
@@ -80,10 +80,10 @@
inline val PickerNextButtonContentDescription
get() = Strings(R.string.wear_m3c_picker_next_button_content_description)
- inline val SliderDecreaseButtonContentDescription
+ inline val SliderDecreaseIconContentDescription
get() = Strings(R.string.wear_m3c_slider_decrease_content_description)
- inline val SliderIncreaseButtonContentDescription
+ inline val SliderIncreaseIconContentDescription
get() = Strings(R.string.wear_m3c_slider_increase_content_description)
inline val AlertDialogContentDescriptionConfirmButton
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypeScaleTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypeScaleTokens.kt
index c23d721..873d680 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypeScaleTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypeScaleTokens.kt
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-// VERSION: v0_103
+// VERSION: v0_108
// GENERATED CODE - DO NOT MODIFY BY HAND
-
package androidx.wear.compose.material3.tokens
import androidx.compose.ui.unit.sp
@@ -143,8 +142,8 @@
val NumeralSmallWeightProminent = 750.0f
val NumeralSmallWidth = 100.0f
val TitleLargeFont = TypefaceTokens.Brand
- val TitleLargeLineHeight = 22.0.sp
- val TitleLargeSize = 20.sp
+ val TitleLargeSize = 18.sp
+ val TitleLargeLineHeight = 20.0.sp
val TitleLargeTracking = 0.2.sp
val TitleLargeWeight = 500.0f
val TitleLargeWidth = 110.0f
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypefaceTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypefaceTokens.kt
index 260235c..7178603 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypefaceTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypefaceTokens.kt
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-// VERSION: v0_103
+// VERSION: v0_108
// GENERATED CODE - DO NOT MODIFY BY HAND
-
package androidx.wear.compose.material3.tokens
import androidx.compose.ui.text.font.DeviceFontFamilyName
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypographyKeyTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypographyKeyTokens.kt
index 531e688..35c2841 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypographyKeyTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypographyKeyTokens.kt
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-// VERSION: v0_103
+// VERSION: v0_108
// GENERATED CODE - DO NOT MODIFY BY HAND
-
package androidx.wear.compose.material3.tokens
internal enum class TypographyKeyTokens {
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypographyTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypographyTokens.kt
index f9626c2..cf49281 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypographyTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypographyTokens.kt
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-// VERSION: v0_103
+// VERSION: v0_108
// GENERATED CODE - DO NOT MODIFY BY HAND
-
package androidx.wear.compose.material3.tokens
import androidx.compose.ui.text.font.DeviceFontFamilyName
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypographyVariableFontsTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypographyVariableFontsTokens.kt
index 9f13848..c4f99af 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypographyVariableFontsTokens.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypographyVariableFontsTokens.kt
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-// VERSION: v0_103
+// VERSION: v0_108
// GENERATED CODE - DO NOT MODIFY BY HAND
-
package androidx.wear.compose.material3.tokens
import androidx.compose.ui.text.font.FontVariation
@@ -24,8 +23,8 @@
internal object TypographyVariableFontsTokens {
val ArcLargeVariationSettings =
FontVariation.Settings(
- FontVariation.Setting("wght", TypeScaleTokens.ArcLargeWeight),
FontVariation.Setting("wdth", TypeScaleTokens.ArcLargeWidth),
+ FontVariation.Setting("wght", TypeScaleTokens.ArcLargeWeight),
)
val ArcMediumVariationSettings =
FontVariation.Settings(
@@ -34,8 +33,8 @@
)
val ArcSmallVariationSettings =
FontVariation.Settings(
- FontVariation.Setting("wdth", TypeScaleTokens.ArcSmallWidth),
FontVariation.Setting("wght", TypeScaleTokens.ArcSmallWeight),
+ FontVariation.Setting("wdth", TypeScaleTokens.ArcSmallWidth),
)
val BodyExtraSmallVariationSettings =
FontVariation.Settings(
@@ -44,13 +43,13 @@
)
val BodyLargeVariationSettings =
FontVariation.Settings(
- FontVariation.Setting("wght", TypeScaleTokens.BodyLargeWeight),
FontVariation.Setting("wdth", TypeScaleTokens.BodyLargeWidth),
+ FontVariation.Setting("wght", TypeScaleTokens.BodyLargeWeight),
)
val BodyMediumVariationSettings =
FontVariation.Settings(
- FontVariation.Setting("wdth", TypeScaleTokens.BodyMediumWidth),
FontVariation.Setting("wght", TypeScaleTokens.BodyMediumWeight),
+ FontVariation.Setting("wdth", TypeScaleTokens.BodyMediumWidth),
)
val BodySmallVariationSettings =
FontVariation.Settings(
@@ -64,18 +63,18 @@
)
val DisplayMediumVariationSettings =
FontVariation.Settings(
- FontVariation.Setting("wght", TypeScaleTokens.DisplayMediumWeight),
FontVariation.Setting("wdth", TypeScaleTokens.DisplayMediumWidth),
+ FontVariation.Setting("wght", TypeScaleTokens.DisplayMediumWeight),
)
val DisplaySmallVariationSettings =
FontVariation.Settings(
- FontVariation.Setting("wght", TypeScaleTokens.DisplaySmallWeight),
FontVariation.Setting("wdth", TypeScaleTokens.DisplaySmallWidth),
+ FontVariation.Setting("wght", TypeScaleTokens.DisplaySmallWeight),
)
val LabelLargeVariationSettings =
FontVariation.Settings(
- FontVariation.Setting("wdth", TypeScaleTokens.LabelLargeWidth),
FontVariation.Setting("wght", TypeScaleTokens.LabelLargeWeight),
+ FontVariation.Setting("wdth", TypeScaleTokens.LabelLargeWidth),
)
val LabelMediumVariationSettings =
FontVariation.Settings(
@@ -99,23 +98,23 @@
)
val NumeralLargeVariationSettings =
FontVariation.Settings(
- FontVariation.Setting("wdth", TypeScaleTokens.NumeralLargeWidth),
FontVariation.Setting("wght", TypeScaleTokens.NumeralLargeWeight),
+ FontVariation.Setting("wdth", TypeScaleTokens.NumeralLargeWidth),
)
val NumeralMediumVariationSettings =
FontVariation.Settings(
- FontVariation.Setting("wdth", TypeScaleTokens.NumeralMediumWidth),
FontVariation.Setting("wght", TypeScaleTokens.NumeralMediumWeight),
+ FontVariation.Setting("wdth", TypeScaleTokens.NumeralMediumWidth),
)
val NumeralSmallVariationSettings =
FontVariation.Settings(
- FontVariation.Setting("wght", TypeScaleTokens.NumeralSmallWeight),
FontVariation.Setting("wdth", TypeScaleTokens.NumeralSmallWidth),
+ FontVariation.Setting("wght", TypeScaleTokens.NumeralSmallWeight),
)
val TitleLargeVariationSettings =
FontVariation.Settings(
- FontVariation.Setting("wght", TypeScaleTokens.TitleLargeWeight),
FontVariation.Setting("wdth", TypeScaleTokens.TitleLargeWidth),
+ FontVariation.Setting("wght", TypeScaleTokens.TitleLargeWeight),
)
val TitleMediumVariationSettings =
FontVariation.Settings(
@@ -124,7 +123,7 @@
)
val TitleSmallVariationSettings =
FontVariation.Settings(
- FontVariation.Setting("wdth", TypeScaleTokens.TitleSmallWidth),
FontVariation.Setting("wght", TypeScaleTokens.TitleSmallWeight),
+ FontVariation.Setting("wdth", TypeScaleTokens.TitleSmallWidth),
)
}
diff --git a/wear/compose/integration-tests/demos/build.gradle b/wear/compose/integration-tests/demos/build.gradle
index a38ede8..8426c60 100644
--- a/wear/compose/integration-tests/demos/build.gradle
+++ b/wear/compose/integration-tests/demos/build.gradle
@@ -25,9 +25,9 @@
compileSdk = 35
defaultConfig {
applicationId = "androidx.wear.compose.integration.demos"
- minSdk = 30
- versionCode = 62
- versionName = "1.62"
+ minSdk = 25
+ versionCode = 63
+ versionName = "1.63"
}
buildTypes {
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoActivity.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoActivity.kt
index 883f184..938f23f 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoActivity.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoActivity.kt
@@ -19,11 +19,13 @@
import android.app.Activity
import android.content.Context
import android.content.Intent
+import android.os.Build
import android.os.Bundle
import android.view.View
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedCallback
import androidx.activity.OnBackPressedDispatcher
+import androidx.annotation.RequiresApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
@@ -53,6 +55,7 @@
lateinit var hostView: View
lateinit var focusManager: FocusManager
+ @RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
index 169ba5bc..1aa5314 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
@@ -16,6 +16,7 @@
package androidx.wear.compose.integration.demos
+import android.os.Build
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -146,65 +147,71 @@
),
DemoCategory(
"Picker",
- listOf(
- ComposableDemo("Time HH:MM:SS") { params ->
- var timePickerTime by remember { mutableStateOf(LocalTime.now()) }
- TimePicker(
- onTimeConfirm = {
- timePickerTime = it
- params.navigateBack()
- },
- time = timePickerTime,
- )
- },
- ComposableDemo("Time 12 Hour") { params ->
- var timePickerTime by remember { mutableStateOf(LocalTime.now()) }
- TimePickerWith12HourClock(
- onTimeConfirm = {
- timePickerTime = it
- params.navigateBack()
- },
- time = timePickerTime,
- )
- },
- ComposableDemo("Date Picker") { params ->
- var datePickerDate by remember { mutableStateOf(LocalDate.now()) }
- DatePicker(
- onDateConfirm = {
- datePickerDate = it
- params.navigateBack()
- },
- date = datePickerDate
- )
- },
- ComposableDemo("From Date Picker") { params ->
- var datePickerDate by remember { mutableStateOf(LocalDate.now()) }
- DatePicker(
- onDateConfirm = {
- datePickerDate = it
- params.navigateBack()
- },
- date = datePickerDate,
- fromDate = datePickerDate
- )
- },
- ComposableDemo("To Date Picker") { params ->
- var datePickerDate by remember { mutableStateOf(LocalDate.now()) }
- DatePicker(
- onDateConfirm = {
- datePickerDate = it
- params.navigateBack()
- },
- date = datePickerDate,
- toDate = datePickerDate
- )
- },
- ComposableDemo("Simple Picker") { SimplePicker() },
- ComposableDemo("No gradient") { PickerWithoutGradient() },
- ComposableDemo("Animate picker change") { AnimateOptionChangePicker() },
- ComposableDemo("Sample Picker Group") { PickerGroup24Hours() },
- ComposableDemo("Autocentering Picker Group") { AutoCenteringPickerGroup() }
- )
+ if (Build.VERSION.SDK_INT > 25) {
+ listOf(
+ ComposableDemo("Time HH:MM:SS") { params ->
+ var timePickerTime by remember { mutableStateOf(LocalTime.now()) }
+ TimePicker(
+ onTimeConfirm = {
+ timePickerTime = it
+ params.navigateBack()
+ },
+ time = timePickerTime,
+ )
+ },
+ ComposableDemo("Time 12 Hour") { params ->
+ var timePickerTime by remember { mutableStateOf(LocalTime.now()) }
+ TimePickerWith12HourClock(
+ onTimeConfirm = {
+ timePickerTime = it
+ params.navigateBack()
+ },
+ time = timePickerTime,
+ )
+ },
+ ComposableDemo("Date Picker") { params ->
+ var datePickerDate by remember { mutableStateOf(LocalDate.now()) }
+ DatePicker(
+ onDateConfirm = {
+ datePickerDate = it
+ params.navigateBack()
+ },
+ date = datePickerDate
+ )
+ },
+ ComposableDemo("From Date Picker") { params ->
+ var datePickerDate by remember { mutableStateOf(LocalDate.now()) }
+ DatePicker(
+ onDateConfirm = {
+ datePickerDate = it
+ params.navigateBack()
+ },
+ date = datePickerDate,
+ fromDate = datePickerDate
+ )
+ },
+ ComposableDemo("To Date Picker") { params ->
+ var datePickerDate by remember { mutableStateOf(LocalDate.now()) }
+ DatePicker(
+ onDateConfirm = {
+ datePickerDate = it
+ params.navigateBack()
+ },
+ date = datePickerDate,
+ toDate = datePickerDate
+ )
+ },
+ ComposableDemo("Simple Picker") { SimplePicker() },
+ ComposableDemo("No gradient") { PickerWithoutGradient() },
+ ComposableDemo("Animate picker change") { AnimateOptionChangePicker() },
+ ComposableDemo("Sample Picker Group") { PickerGroup24Hours() },
+ ComposableDemo("Autocentering Picker Group") { AutoCenteringPickerGroup() }
+ )
+ } else {
+ listOf(
+ ComposableDemo("Simple Picker") { SimplePicker() },
+ )
+ }
),
DemoCategory(
"Slider",
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PickerDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PickerDemo.kt
index 310a4b7..223bcbe 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PickerDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PickerDemo.kt
@@ -17,9 +17,11 @@
package androidx.wear.compose.integration.demos
import android.content.Context
+import android.os.Build
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener
+import androidx.annotation.RequiresApi
import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Arrangement
@@ -93,6 +95,7 @@
* @param modifier the modifiers for the `Box` containing the UI elements.
* @param time the initial value to seed the picker with.
*/
+@RequiresApi(Build.VERSION_CODES.O)
@Composable
public fun TimePicker(
onTimeConfirm: (LocalTime) -> Unit,
@@ -265,6 +268,7 @@
* @param modifier the modifiers for the `Column` containing the UI elements.
* @param time the initial value to seed the picker with.
*/
+@RequiresApi(Build.VERSION_CODES.O)
@Composable
public fun TimePickerWith12HourClock(
onTimeConfirm: (LocalTime) -> Unit,
@@ -464,6 +468,7 @@
* @param fromDate the minimum date to be selected in picker
* @param toDate the maximum date to be selected in picker
*/
+@RequiresApi(Build.VERSION_CODES.O)
@Composable
public fun DatePicker(
onDateConfirm: (LocalDate) -> Unit,
@@ -856,17 +861,20 @@
}
}
+@RequiresApi(Build.VERSION_CODES.O)
private fun verifyDates(date: LocalDate, fromDate: LocalDate, toDate: LocalDate) {
require(toDate >= fromDate) { "toDate should be greater than or equal to fromDate" }
require(date in fromDate..toDate) { "date should lie between fromDate and toDate" }
}
+@RequiresApi(Build.VERSION_CODES.O)
private fun getMonthNames(pattern: String): List<String> {
val monthFormatter = DateTimeFormatter.ofPattern(pattern)
val months = 1..12
return months.map { LocalDate.of(2022, it, 1).format(monthFormatter) }
}
+@RequiresApi(Build.VERSION_CODES.O)
internal class DatePickerState
constructor(
private val date: LocalDate,
diff --git a/wear/compose/integration-tests/macrobenchmark-target/build.gradle b/wear/compose/integration-tests/macrobenchmark-target/build.gradle
index d10859a..dbbe105 100644
--- a/wear/compose/integration-tests/macrobenchmark-target/build.gradle
+++ b/wear/compose/integration-tests/macrobenchmark-target/build.gradle
@@ -53,4 +53,4 @@
implementation(project(":tracing:tracing-perfetto-binary"))
}
-android.defaultConfig.minSdk = 30
+android.defaultConfig.minSdk = 25
diff --git a/wear/compose/integration-tests/macrobenchmark/build.gradle b/wear/compose/integration-tests/macrobenchmark/build.gradle
index e124928..6526e1b 100644
--- a/wear/compose/integration-tests/macrobenchmark/build.gradle
+++ b/wear/compose/integration-tests/macrobenchmark/build.gradle
@@ -23,7 +23,7 @@
android {
compileSdk = 35
defaultConfig {
- minSdk = 30
+ minSdk = 29
}
namespace = "androidx.wear.compose.integration.macrobenchmark"
targetProjectPath = ":wear:compose:integration-tests:macrobenchmark-target"
diff --git a/wear/protolayout/protolayout-material3/api/current.txt b/wear/protolayout/protolayout-material3/api/current.txt
index b45c9c7..8942afc 100644
--- a/wear/protolayout/protolayout-material3/api/current.txt
+++ b/wear/protolayout/protolayout-material3/api/current.txt
@@ -224,10 +224,6 @@
method public androidx.wear.protolayout.material3.GraphicDataCardStyle largeGraphicDataCardStyle();
}
- public final class HelpersKt {
- method public static androidx.wear.protolayout.types.LayoutColor withOpacity(androidx.wear.protolayout.types.LayoutColor, @FloatRange(from=0.0, to=1.0) float ratio);
- }
-
public final class IconButtonStyle {
field public static final androidx.wear.protolayout.material3.IconButtonStyle.Companion Companion;
}
@@ -336,8 +332,6 @@
}
public final class Typography {
- property public static final int ARC_MEDIUM;
- property public static final int ARC_SMALL;
property public static final int BODY_EXTRA_SMALL;
property public static final int BODY_LARGE;
property public static final int BODY_MEDIUM;
@@ -356,8 +350,6 @@
property public static final int TITLE_LARGE;
property public static final int TITLE_MEDIUM;
property public static final int TITLE_SMALL;
- field public static final int ARC_MEDIUM = 0; // 0x0
- field public static final int ARC_SMALL = 1; // 0x1
field public static final int BODY_EXTRA_SMALL = 2; // 0x2
field public static final int BODY_LARGE = 3; // 0x3
field public static final int BODY_MEDIUM = 4; // 0x4
diff --git a/wear/protolayout/protolayout-material3/api/restricted_current.txt b/wear/protolayout/protolayout-material3/api/restricted_current.txt
index b45c9c7..8942afc 100644
--- a/wear/protolayout/protolayout-material3/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-material3/api/restricted_current.txt
@@ -224,10 +224,6 @@
method public androidx.wear.protolayout.material3.GraphicDataCardStyle largeGraphicDataCardStyle();
}
- public final class HelpersKt {
- method public static androidx.wear.protolayout.types.LayoutColor withOpacity(androidx.wear.protolayout.types.LayoutColor, @FloatRange(from=0.0, to=1.0) float ratio);
- }
-
public final class IconButtonStyle {
field public static final androidx.wear.protolayout.material3.IconButtonStyle.Companion Companion;
}
@@ -336,8 +332,6 @@
}
public final class Typography {
- property public static final int ARC_MEDIUM;
- property public static final int ARC_SMALL;
property public static final int BODY_EXTRA_SMALL;
property public static final int BODY_LARGE;
property public static final int BODY_MEDIUM;
@@ -356,8 +350,6 @@
property public static final int TITLE_LARGE;
property public static final int TITLE_MEDIUM;
property public static final int TITLE_SMALL;
- field public static final int ARC_MEDIUM = 0; // 0x0
- field public static final int ARC_SMALL = 1; // 0x1
field public static final int BODY_EXTRA_SMALL = 2; // 0x2
field public static final int BODY_LARGE = 3; // 0x3
field public static final int BODY_MEDIUM = 4; // 0x4
diff --git a/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/MaterialGoldenTest.kt b/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/MaterialGoldenTest.kt
index 12a274b..f05c1b6 100644
--- a/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/MaterialGoldenTest.kt
+++ b/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/MaterialGoldenTest.kt
@@ -33,8 +33,27 @@
AndroidXScreenshotTestRule("wear/protolayout/protolayout-material3")
@Test
- fun test() {
- RunnerUtils.runSingleScreenshotTest(mScreenshotRule, testCase, expected)
+ fun testLtr() {
+ // Skip test if it's not meant for LTR
+ if (!testCase.isForLtr) return
+ RunnerUtils.runSingleScreenshotTest(
+ rule = mScreenshotRule,
+ layout = testCase.layout,
+ expected = expected,
+ isRtlDirection = false
+ )
+ }
+
+ @Test
+ fun testRtl() {
+ // Skip test if it's not meant for RTL
+ if (!testCase.isForRtl) return
+ RunnerUtils.runSingleScreenshotTest(
+ rule = mScreenshotRule,
+ layout = testCase.layout,
+ expected = expected + "_rtl",
+ isRtlDirection = true
+ )
}
companion object {
diff --git a/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/RunnerUtils.kt b/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/RunnerUtils.kt
index 6bf8296..5ee111f 100644
--- a/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/RunnerUtils.kt
+++ b/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/RunnerUtils.kt
@@ -33,27 +33,9 @@
// watch dimensions here.
const val SCREEN_SIZE_SMALL: Int = 525 // ~199dp
- fun runSingleScreenshotTest(
- rule: AndroidXScreenshotTestRule,
- testCase: TestCase,
- expected: String
- ) {
- if (testCase.isForLtr) {
- runSingleScreenshotTest(rule, testCase.mLayout, expected, /* isRtlDirection= */ false)
- }
- if (testCase.isForRtl) {
- runSingleScreenshotTest(
- rule,
- testCase.mLayout,
- expected + "_rtl",
- /* isRtlDirection= */ true
- )
- }
- }
-
@SuppressLint("BanThreadSleep")
// TODO: b/355417923 - Avoid calling sleep.
- private fun runSingleScreenshotTest(
+ fun runSingleScreenshotTest(
rule: AndroidXScreenshotTestRule,
layout: LayoutElementBuilders.Layout,
expected: String,
@@ -61,7 +43,7 @@
) {
val layoutPayload = layout.toByteArray()
- val startIntent: Intent =
+ val startIntent =
Intent(
androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
.targetContext,
@@ -134,7 +116,7 @@
/** Holds testcase parameters. */
class TestCase(
- val mLayout: LayoutElementBuilders.Layout,
+ val layout: LayoutElementBuilders.Layout,
val isForRtl: Boolean,
val isForLtr: Boolean
)
diff --git a/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/TestCasesGenerator.kt b/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/TestCasesGenerator.kt
index 2aa2b52..6b02da5 100644
--- a/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/TestCasesGenerator.kt
+++ b/wear/protolayout/protolayout-material3/src/androidTest/java/androidx/wear/protolayout/material3/TestCasesGenerator.kt
@@ -19,20 +19,19 @@
import androidx.test.core.app.ApplicationProvider
import androidx.test.platform.app.InstrumentationRegistry
import androidx.wear.protolayout.DeviceParametersBuilders
+import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters
import androidx.wear.protolayout.DimensionBuilders.dp
import androidx.wear.protolayout.DimensionBuilders.expand
import androidx.wear.protolayout.LayoutElementBuilders
-import androidx.wear.protolayout.LayoutElementBuilders.Box
import androidx.wear.protolayout.LayoutElementBuilders.Column
-import androidx.wear.protolayout.ModifiersBuilders.Background
-import androidx.wear.protolayout.ModifiersBuilders.Corner
-import androidx.wear.protolayout.ModifiersBuilders.Modifiers
+import androidx.wear.protolayout.expression.VersionBuilders.VersionInfo
import androidx.wear.protolayout.material3.AppCardStyle.Companion.largeAppCardStyle
import androidx.wear.protolayout.material3.ButtonDefaults.filledButtonColors
import androidx.wear.protolayout.material3.ButtonDefaults.filledTonalButtonColors
import androidx.wear.protolayout.material3.ButtonDefaults.filledVariantButtonColors
import androidx.wear.protolayout.material3.ButtonGroupDefaults.DEFAULT_SPACER_BETWEEN_BUTTON_GROUPS
import androidx.wear.protolayout.material3.CardDefaults.filledVariantCardColors
+import androidx.wear.protolayout.material3.CircularProgressIndicatorDefaults.filledTonalProgressIndicatorColors
import androidx.wear.protolayout.material3.CircularProgressIndicatorDefaults.filledVariantProgressIndicatorColors
import androidx.wear.protolayout.material3.DataCardStyle.Companion.smallCompactDataCardStyle
import androidx.wear.protolayout.material3.IconButtonStyle.Companion.largeIconButtonStyle
@@ -45,7 +44,6 @@
import androidx.wear.protolayout.modifiers.clickable
import androidx.wear.protolayout.modifiers.clip
import androidx.wear.protolayout.modifiers.contentDescription
-import androidx.wear.protolayout.types.LayoutColor
import androidx.wear.protolayout.types.layoutString
import com.google.common.collect.ImmutableMap
@@ -70,12 +68,14 @@
val scale = displayMetrics.density
val deviceParameters =
- DeviceParametersBuilders.DeviceParameters.Builder()
+ DeviceParameters.Builder()
.setScreenWidthDp(pxToDp(RunnerUtils.SCREEN_SIZE_SMALL, scale))
.setScreenHeightDp(pxToDp(RunnerUtils.SCREEN_SIZE_SMALL, scale))
.setScreenDensity(displayMetrics.density)
.setFontScale(1f)
.setScreenShape(DeviceParametersBuilders.SCREEN_SHAPE_RECT)
+ // testing with the latest renderer version
+ .setRendererSchemaVersion(VersionInfo.Builder().setMajor(99).setMinor(999).build())
.build()
val clickable = clickable(id = "action_id")
val testCases: HashMap<String, LayoutElementBuilders.LayoutElement> = HashMap()
@@ -470,45 +470,22 @@
deviceParameters,
allowDynamicTheme = false
) {
- primaryLayout(
- mainSlot = {
- buttonGroup(height = dp(50F)) {
- buttonGroupItem { circularProgressIndicator() }
- buttonGroupItem {
- circularProgressIndicator(
- staticProgress = 0.75F,
- colors = filledVariantProgressIndicatorColors()
- )
- }
- buttonGroupItem {
- circularProgressIndicator(
- staticProgress = 1.5F,
- startAngleDegrees = 200F,
- endAngleDegrees = 520F,
- colors = filledVariantProgressIndicatorColors()
- )
- }
- }
- }
- )
+ primaryLayout(mainSlot = { progressIndicatorGroup() })
+ }
+
+ testCases["primarylayout_circularprogressindicators_fallback__golden$NORMAL_SCALE_SUFFIX"] =
+ materialScope(
+ ApplicationProvider.getApplicationContext(),
+ deviceConfiguration =
+ deviceParameters.copy(VersionInfo.Builder().setMajor(1).setMinor(402).build()),
+ allowDynamicTheme = false
+ ) {
+ primaryLayout(mainSlot = { progressIndicatorGroup() })
}
return collectTestCases(testCases)
}
- private fun coloredBox(color: LayoutColor, shape: Corner) =
- Box.Builder()
- .setWidth(expand())
- .setHeight(expand())
- .setModifiers(
- Modifiers.Builder()
- .setBackground(
- Background.Builder().setColor(color.prop).setCorner(shape).build()
- )
- .build()
- )
- .build()
-
private fun collectTestCases(
testCases: Map<String, LayoutElementBuilders.LayoutElement>
): ImmutableMap<String, LayoutElementBuilders.Layout> {
@@ -523,4 +500,72 @@
)
)
}
+
+ private fun MaterialScope.progressIndicatorGroup(): Column =
+ Column.Builder()
+ .setWidth(expand())
+ .setHeight(expand())
+ .addContent(
+ buttonGroup(height = dp(52F)) {
+ buttonGroupItem {
+ circularProgressIndicator(colors = filledTonalProgressIndicatorColors())
+ }
+ buttonGroupItem {
+ circularProgressIndicator(
+ staticProgress = 0.75F,
+ colors = filledVariantProgressIndicatorColors()
+ )
+ }
+ buttonGroupItem {
+ circularProgressIndicator(
+ staticProgress = 1.5F,
+ startAngleDegrees = 200F,
+ endAngleDegrees = 520F,
+ colors = filledVariantProgressIndicatorColors()
+ )
+ }
+ }
+ )
+ .addContent(DEFAULT_SPACER_BETWEEN_BUTTON_GROUPS)
+ .addContent(
+ buttonGroup(height = dp(52F)) {
+ buttonGroupItem {
+ segmentedCircularProgressIndicator(
+ segmentCount = 1,
+ colors = filledTonalProgressIndicatorColors()
+ )
+ }
+ buttonGroupItem {
+ segmentedCircularProgressIndicator(
+ segmentCount = 5,
+ staticProgress = 0.75F,
+ colors = filledVariantProgressIndicatorColors()
+ )
+ }
+ buttonGroupItem {
+ segmentedCircularProgressIndicator(
+ segmentCount = 5,
+ staticProgress = 1.5F,
+ startAngleDegrees = 200F,
+ endAngleDegrees = 520F,
+ colors = filledVariantProgressIndicatorColors()
+ )
+ }
+ }
+ )
+ .build()
+
+ /**
+ * Make a copy of a [DeviceParameters], and update it with the provided renderer version for
+ * testing fallback features on older renderer.
+ */
+ private fun DeviceParameters.copy(rendererVersion: VersionInfo): DeviceParameters =
+ DeviceParameters.Builder()
+ .setScreenWidthDp(screenWidthDp)
+ .setScreenHeightDp(screenHeightDp)
+ .setScreenDensity(screenDensity)
+ .setFontScale(fontScale)
+ .setScreenShape(screenShape)
+ .setRendererSchemaVersion(rendererVersion)
+ .build()
}
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Card.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Card.kt
index c5fce5f..951a192 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Card.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Card.kt
@@ -62,7 +62,7 @@
* @param time An optional slot for displaying the time relevant to the contents of the card,
* expected to be a short piece of text. Uses [CardColors.timeColor] color by default.
* @param height The height of this card. It's highly recommended to set this to [expand] or
- * [weight]
+ * [weight].
* @param shape Defines the card's shape, in other words the corner radius for this card.
* @param colors The colors to be used for a background and inner content of this card. If the
* background image is also specified, the image will be laid out on top of the background color.
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CircularProgressIndicator.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CircularProgressIndicator.kt
index c8a3022..7bfd428 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CircularProgressIndicator.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CircularProgressIndicator.kt
@@ -23,13 +23,14 @@
import androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint
import androidx.wear.protolayout.DimensionBuilders.ContainerDimension
import androidx.wear.protolayout.DimensionBuilders.DegreesProp
+import androidx.wear.protolayout.DimensionBuilders.DpProp
import androidx.wear.protolayout.DimensionBuilders.ExpandedDimensionProp
import androidx.wear.protolayout.DimensionBuilders.WrappedDimensionProp
import androidx.wear.protolayout.DimensionBuilders.degrees
-import androidx.wear.protolayout.DimensionBuilders.dp
import androidx.wear.protolayout.DimensionBuilders.expand
import androidx.wear.protolayout.LayoutElementBuilders
import androidx.wear.protolayout.LayoutElementBuilders.Arc
+import androidx.wear.protolayout.LayoutElementBuilders.ArcLine
import androidx.wear.protolayout.LayoutElementBuilders.ArcSpacer
import androidx.wear.protolayout.LayoutElementBuilders.Box
import androidx.wear.protolayout.LayoutElementBuilders.DashedArcLine
@@ -38,6 +39,8 @@
import androidx.wear.protolayout.ModifiersBuilders.Modifiers
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat
+import androidx.wear.protolayout.expression.VersionBuilders.VersionInfo
+import androidx.wear.protolayout.material3.CircularProgressIndicatorDefaults.CPI_DEFAULT_DP_SIZE
import androidx.wear.protolayout.material3.CircularProgressIndicatorDefaults.INDICATOR_STROKE_WIDTH_INCREMENT_PX
import androidx.wear.protolayout.material3.CircularProgressIndicatorDefaults.LARGE_STROKE_WIDTH
import androidx.wear.protolayout.material3.CircularProgressIndicatorDefaults.METADATA_TAG
@@ -49,11 +52,17 @@
import androidx.wear.protolayout.modifiers.padding
import androidx.wear.protolayout.modifiers.tag
import androidx.wear.protolayout.modifiers.toProtoLayoutModifiers
+import androidx.wear.protolayout.types.dp
import kotlin.math.min
/**
* Protolayout Material3 design circular progress indicator.
*
+ * Note that, the proper implementation of this component requires a ProtoLayout renderer with
+ * version equal to or above 1.403. When the renderer is lower than 1.403, this component will
+ * automatically fallback to an implementation with reduced features, without support for expandable
+ * size, overflow, and start/end transition.
+ *
* @param staticProgress The static progress of this progress indicator where 0 represent no
* progress and 1 represents completion. Progress above 1 is also allowed. If [dynamicProgress] is
* also set, this static value will only be used when the dynamic value cannot be evaluated. By
@@ -91,8 +100,8 @@
modifier: LayoutModifier = LayoutModifier,
startAngleDegrees: Float = 0F,
endAngleDegrees: Float = startAngleDegrees + 360F,
- @Dimension(unit = DP) strokeWidth: Float = LARGE_STROKE_WIDTH,
- @Dimension(unit = DP) gapSize: Float = calculateRecommendedGapSize(strokeWidth),
+ @Dimension(DP) strokeWidth: Float = LARGE_STROKE_WIDTH,
+ @Dimension(DP) gapSize: Float = calculateRecommendedGapSize(strokeWidth),
colors: ProgressIndicatorColors = filledProgressIndicatorColors(),
size: ContainerDimension = expand(),
): LayoutElement {
@@ -100,19 +109,39 @@
verifySize(size)
val modifiers = (LayoutModifier.tag(METADATA_TAG) then modifier).toProtoLayoutModifiers()
+ val hasDashedArcLineSupport =
+ deviceConfiguration.rendererSchemaVersion.hasDashedArcLineSupport()
+ // With the fallback implementation, expandable size is not supported, fallback to dp size.
+ val containerSize =
+ if (hasDashedArcLineSupport || size is DpProp) size else CPI_DEFAULT_DP_SIZE.dp
+ val boxBuilder =
+ if (hasDashedArcLineSupport)
+ singleSegmentImpl(
+ startAngleDegrees = startAngleDegrees,
+ endAngleDegrees = checkAndAdjustEndAngle(startAngleDegrees, endAngleDegrees),
+ staticProgress = staticProgress,
+ dynamicProgress = dynamicProgress,
+ strokeWidth = strokeWidth,
+ gapSize = gapSize,
+ colors = colors
+ )
+ else
+ circularProgressIndicatorFallbackImpl(
+ // Without DashedArcLine support, container size fell back to dp size.
+ arcContainerSize = (containerSize as DpProp).value,
+ startAngleDegrees = startAngleDegrees,
+ endAngleDegrees = checkAndAdjustEndAngle(startAngleDegrees, endAngleDegrees),
+ staticProgress = staticProgress,
+ dynamicProgress = dynamicProgress,
+ strokeWidth = strokeWidth,
+ gapSize = gapSize,
+ colors = colors
+ )
- return singleSegmentImpl(
- startAngleDegrees = startAngleDegrees,
- endAngleDegrees = checkAndAdjustEndAngle(startAngleDegrees, endAngleDegrees),
- staticProgress = staticProgress,
- dynamicProgress = dynamicProgress,
- strokeWidth = strokeWidth,
- gapSize = gapSize,
- colors = colors
- )
+ return boxBuilder
.setModifiers(modifiers)
- .setWidth(size)
- .setHeight(size)
+ .setWidth(containerSize)
+ .setHeight(containerSize)
.build()
}
@@ -121,6 +150,11 @@
*
* A segmented variant of [circularProgressIndicator] that is divided into equally sized segments.
*
+ * Note that, the proper implementation of this component requires a ProtoLayout renderer with
+ * version equal to or above 1.403. When the renderer is lower than 1.403, this component will
+ * automatically fallback to an implementation with reduced features, without support for multiple
+ * segments, expandable size, overflow, and start/end transition.
+ *
* @param segmentCount Number of equal segments that the progress indicator should be divided into.
* Has to be a number greater than or equal to 1.
* @param staticProgress The static progress of this progress indicator where 0 represent no
@@ -161,8 +195,8 @@
modifier: LayoutModifier = LayoutModifier,
startAngleDegrees: Float = 0F,
endAngleDegrees: Float = startAngleDegrees + 360F,
- @Dimension(unit = DP) strokeWidth: Float = LARGE_STROKE_WIDTH,
- @Dimension(unit = DP) gapSize: Float = calculateRecommendedGapSize(strokeWidth),
+ @Dimension(DP) strokeWidth: Float = LARGE_STROKE_WIDTH,
+ @Dimension(DP) gapSize: Float = calculateRecommendedGapSize(strokeWidth),
colors: ProgressIndicatorColors = filledProgressIndicatorColors(),
size: ContainerDimension = expand(),
): LayoutElement {
@@ -170,20 +204,40 @@
verifySize(size)
val modifiers = (LayoutModifier.tag(METADATA_TAG) then modifier).toProtoLayoutModifiers()
+ val hasDashedArcLineSupport =
+ deviceConfiguration.rendererSchemaVersion.hasDashedArcLineSupport()
+ // Without using DashedArcLine, expandable size is not supported, fallback to dp size.
+ val containerSize =
+ if (hasDashedArcLineSupport || size is DpProp) size else CPI_DEFAULT_DP_SIZE.dp
+ val boxBuilder =
+ if (hasDashedArcLineSupport)
+ multipleSegmentsImpl(
+ segmentCount = segmentCount,
+ startAngleDegrees = startAngleDegrees,
+ endAngleDegrees = checkAndAdjustEndAngle(startAngleDegrees, endAngleDegrees),
+ staticProgress = staticProgress,
+ dynamicProgress = dynamicProgress,
+ strokeWidth = strokeWidth,
+ gapSize = gapSize,
+ colors = colors
+ )
+ else
+ circularProgressIndicatorFallbackImpl(
+ // Without DashedArcLine support, container size fell back to dp size.
+ arcContainerSize = (containerSize as DpProp).value,
+ startAngleDegrees = startAngleDegrees,
+ endAngleDegrees = checkAndAdjustEndAngle(startAngleDegrees, endAngleDegrees),
+ staticProgress = staticProgress,
+ dynamicProgress = dynamicProgress,
+ strokeWidth = strokeWidth,
+ gapSize = gapSize,
+ colors = colors
+ )
- return multipleSegmentsImpl(
- segmentCount = segmentCount,
- startAngleDegrees = startAngleDegrees,
- endAngleDegrees = checkAndAdjustEndAngle(startAngleDegrees, endAngleDegrees),
- staticProgress = staticProgress,
- dynamicProgress = dynamicProgress,
- strokeWidth = strokeWidth,
- gapSize = gapSize,
- colors = colors
- )
+ return boxBuilder
.setModifiers(modifiers)
- .setWidth(size)
- .setHeight(size)
+ .setWidth(containerSize)
+ .setHeight(containerSize)
.build()
}
@@ -197,8 +251,8 @@
endAngleDegrees: Float,
staticProgress: Float,
dynamicProgress: DynamicFloat?,
- @Dimension(unit = DP) strokeWidth: Float,
- @Dimension(unit = DP) gapSize: Float,
+ @Dimension(DP) strokeWidth: Float,
+ @Dimension(DP) gapSize: Float,
colors: ProgressIndicatorColors
): Box.Builder {
val sweepAngle = endAngleDegrees - startAngleDegrees
@@ -225,7 +279,7 @@
.setGapLocations(0f)
.build()
val spacer =
- ArcSpacer.Builder().setAngularLength(dp(gapSize / 2)).setThickness(dp(strokeWidth)).build()
+ ArcSpacer.Builder().setAngularLength((gapSize / 2F).dp).setThickness(strokeWidth.dp).build()
return Box.Builder()
.addContent( // the track
createArc(
@@ -266,8 +320,8 @@
endAngleDegrees: Float,
staticProgress: Float,
dynamicProgress: DynamicFloat?,
- @Dimension(unit = DP) strokeWidth: Float,
- @Dimension(unit = DP) gapSize: Float,
+ @Dimension(DP) strokeWidth: Float,
+ @Dimension(DP) gapSize: Float,
colors: ProgressIndicatorColors
): Box.Builder {
val sweepAngle = endAngleDegrees - startAngleDegrees
@@ -333,6 +387,12 @@
}
}
+/**
+ * For renderer with version lower than 1.403, there is no [DashedArcLine] support. In this case,
+ * the progress indicator will fallback to use [ArcLine]
+ */
+private fun VersionInfo.hasDashedArcLineSupport() = major > 1 || (major == 1 && minor >= 403)
+
/*
* Check the endAngle is valid with the provided startAngle, and adjust the sweep angle to be 360
* maximum.
@@ -352,7 +412,8 @@
staticProgress: Float,
dynamicProgress: DynamicFloat?,
): DegreesProp =
- DegreesProp.Builder((staticProgress * sweepAngle - TRIVIAL_ARC_OFFSET).mod(sweepAngle))
+ // Note that (-0.05) % 360 = -0.05; while (-0.05).mod(360) = 359.95, we need to use % here.
+ DegreesProp.Builder((staticProgress * sweepAngle - TRIVIAL_ARC_OFFSET) % sweepAngle)
.apply {
dynamicProgress?.let {
setDynamicValue(it.times(sweepAngle).minus(TRIVIAL_ARC_OFFSET).rem(sweepAngle))
@@ -394,11 +455,11 @@
anchorType: Int,
arcLength: DegreesProp,
arcColor: ColorProp,
- @Dimension(unit = DP) strokeWidth: Float,
+ @Dimension(DP) strokeWidth: Float,
linePattern: DashedLinePattern,
arcDirection: Int
-): Arc.Builder {
- return Arc.Builder()
+): Arc.Builder =
+ Arc.Builder()
.setAnchorAngle(anchorAngle)
.setAnchorType(anchorType)
.setArcDirection(arcDirection)
@@ -415,4 +476,3 @@
)
.build()
)
-}
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CircularProgressIndicatorDefaults.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CircularProgressIndicatorDefaults.kt
index 7ca7208..bd9c49c 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CircularProgressIndicatorDefaults.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CircularProgressIndicatorDefaults.kt
@@ -84,18 +84,18 @@
.build()
/** Large stroke width for circular progress indicator. */
- @Dimension(unit = DP) public const val LARGE_STROKE_WIDTH: Float = 8F
+ @Dimension(DP) public const val LARGE_STROKE_WIDTH: Float = 8F
/** Small stroke width for circular progress indicator. */
- @Dimension(unit = DP) public const val SMALL_STROKE_WIDTH: Float = 4F
+ @Dimension(DP) public const val SMALL_STROKE_WIDTH: Float = 4F
/**
* Returns recommended size of the gap based on [strokeWidth].
*
* The absolute value can be customized with `gapSize` parameter on [circularProgressIndicator].
*/
- @Dimension(unit = DP)
- public fun calculateRecommendedGapSize(@Dimension(unit = DP) strokeWidth: Float): Float =
+ @Dimension(DP)
+ public fun calculateRecommendedGapSize(@Dimension(DP) strokeWidth: Float): Float =
strokeWidth / 3F
internal const val METADATA_TAG: String = "M3CPI"
@@ -110,4 +110,7 @@
* top of the track arc where there are multiple segments.
*/
internal const val INDICATOR_STROKE_WIDTH_INCREMENT_PX: Float = 1.5f
+
+ /** Default size for the fallback implementation. */
+ @Dimension(DP) internal const val CPI_DEFAULT_DP_SIZE: Float = 52F
}
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CircularProgressIndicatorFallback.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CircularProgressIndicatorFallback.kt
new file mode 100644
index 0000000..00dc0e2
--- /dev/null
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CircularProgressIndicatorFallback.kt
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2025 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.protolayout.material3
+
+import androidx.annotation.Dimension
+import androidx.annotation.Dimension.Companion.DP
+import androidx.wear.protolayout.ColorBuilders.ColorProp
+import androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint
+import androidx.wear.protolayout.DimensionBuilders.DegreesProp
+import androidx.wear.protolayout.DimensionBuilders.degrees
+import androidx.wear.protolayout.LayoutElementBuilders
+import androidx.wear.protolayout.LayoutElementBuilders.Arc
+import androidx.wear.protolayout.LayoutElementBuilders.ArcLine
+import androidx.wear.protolayout.LayoutElementBuilders.Box
+import androidx.wear.protolayout.LayoutElementBuilders.DashedArcLine
+import androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat
+import androidx.wear.protolayout.types.dp
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * This method provides the fallback content layout for [circularProgressIndicator] and
+ * [segmentedCircularProgressIndicator] using [ArcLine] when the renderer version is lower than
+ * 1.403 where [DashedArcLine] is not available.
+ *
+ * Note that we require valid start and end angles for calling this method.
+ */
+internal fun MaterialScope.circularProgressIndicatorFallbackImpl(
+ @Dimension(unit = DP) arcContainerSize: Float,
+ startAngleDegrees: Float,
+ endAngleDegrees: Float,
+ staticProgress: Float,
+ dynamicProgress: DynamicFloat?,
+ @Dimension(unit = DP) strokeWidth: Float,
+ @Dimension(unit = DP) gapSize: Float,
+ colors: ProgressIndicatorColors
+): Box.Builder {
+ // Offset the anchor to make space for the cap and half gap.
+ val anchorOffsetDegrees =
+ (strokeWidth + gapSize).dpToDegree(radius = (arcContainerSize - strokeWidth) / 2.0) / 2
+
+ // Visually, the progress arcline and track arcline never overlaps. But from the layout point
+ // of view, their layout size are both from start angle to end angle, and overlaps completely.
+ // Thus, we put each arcline in an arc container, and stacks these two arc containers inside a
+ // box.
+ return Box.Builder()
+ .addContent(
+ createArc(
+ anchorDegree = endAngleDegrees - anchorOffsetDegrees,
+ anchorType = LayoutElementBuilders.ARC_ANCHOR_END,
+ arcLength =
+ trackInDegrees(
+ sweepAngle = endAngleDegrees - startAngleDegrees,
+ staticProgress = staticProgress,
+ dynamicProgress = dynamicProgress,
+ lengthAdjustment = anchorOffsetDegrees * 2F
+ ),
+ color = colors.trackColor.prop,
+ strokeWidth = strokeWidth
+ )
+ )
+ .addContent(
+ createArc(
+ anchorDegree = startAngleDegrees + anchorOffsetDegrees,
+ anchorType = LayoutElementBuilders.ARC_ANCHOR_START,
+ arcLength =
+ progressInDegrees(
+ sweepAngle = endAngleDegrees - startAngleDegrees,
+ staticProgress = staticProgress,
+ dynamicProgress = dynamicProgress,
+ lengthAdjustment = anchorOffsetDegrees * 2F
+ ),
+ color = colors.indicatorColor.prop,
+ strokeWidth = strokeWidth
+ )
+ )
+}
+
+/**
+ * A small offset to make the progress dot remaining when the progress is 0, and the track dot
+ * remaining when the progress is 1.
+ */
+private const val ARC_OFFSET_IN_DEGREES = 0.05f
+
+private fun createArc(
+ anchorDegree: Float,
+ anchorType: Int,
+ arcLength: DegreesProp,
+ color: ColorProp,
+ @Dimension(unit = DP) strokeWidth: Float
+): Arc =
+ Arc.Builder()
+ .setAnchorAngle(degrees(anchorDegree))
+ .setAnchorType(anchorType)
+ .setArcDirection(LayoutElementBuilders.ARC_DIRECTION_CLOCKWISE)
+ .addContent(
+ ArcLine.Builder()
+ .setColor(color)
+ .setArcDirection(LayoutElementBuilders.ARC_DIRECTION_CLOCKWISE)
+ .setLength(arcLength)
+ .setLayoutConstraintsForDynamicLength(
+ // We use one Arc container to put one arcline, so it is fine to put 360 here
+ // as layout constraint.
+ AngularLayoutConstraint.Builder(360F).setAngularAlignment(anchorType).build()
+ )
+ .setThickness(strokeWidth.dp)
+ .build()
+ )
+ .build()
+
+private fun Float.dpToDegree(radius: Double): Float =
+ // radianAngle = arcLength / radius
+ Math.toDegrees(this / radius).toFloat()
+
+private fun progressInDegrees(
+ sweepAngle: Float,
+ staticProgress: Float,
+ dynamicProgress: DynamicFloat?,
+ lengthAdjustment: Float
+): DegreesProp =
+ arcInDegrees(
+ sweepAngle = sweepAngle,
+ staticRatio = staticProgress,
+ dynamicRatio = dynamicProgress,
+ lengthAdjustment = lengthAdjustment
+ )
+
+private fun trackInDegrees(
+ sweepAngle: Float,
+ staticProgress: Float,
+ dynamicProgress: DynamicFloat?,
+ lengthAdjustment: Float
+): DegreesProp =
+ arcInDegrees(
+ sweepAngle = sweepAngle,
+ staticRatio = 1F - staticProgress,
+ dynamicRatio =
+ if (dynamicProgress == null) null else DynamicFloat.constant(1F).minus(dynamicProgress),
+ lengthAdjustment = lengthAdjustment
+ )
+
+private fun arcInDegrees(
+ sweepAngle: Float,
+ staticRatio: Float,
+ dynamicRatio: DynamicFloat?,
+ lengthAdjustment: Float
+): DegreesProp {
+ val staticValue =
+ getCorrectStaticArcLength(
+ sweepAngle = sweepAngle,
+ ratio = staticRatio,
+ lengthAdjustment = lengthAdjustment
+ )
+
+ if (dynamicRatio == null) { // static value
+ return degrees(staticValue)
+ }
+
+ return DegreesProp.Builder(staticValue)
+ .setDynamicValue(
+ getApproximateDynamicArcLength(
+ sweepAngle = sweepAngle,
+ ratio = dynamicRatio,
+ lengthAdjustment = lengthAdjustment
+ )
+ )
+ .build()
+}
+
+/**
+ * When drawing the progress arc line, we need to adjust its Length to make space for two caps and
+ * gap. Note that, event the progress arc is 0, we still leave the above space, for a good
+ * transition to a non-zero progress. Similar for track arc line. The arc length calculation is as
+ * follows:
+ * ```
+ * sweepAngle = endAngle-startAngle
+ * perArcLengthAdjustment = cap * 2 - gap
+ * maxTotalLength = endAngle - startAngle- 2 * perArcLengthAdjustment
+ * ProgressArcLength = clamp(sweepAngle * progress - perArcLengthAdjustment, 0, maxTotalLength)
+ * trackArcLength = clamp(sweepAngle*(1-progress) - perArcLengthAdjustment, 0, maxTotalLength)
+ * ```
+ */
+private fun getCorrectStaticArcLength(
+ sweepAngle: Float,
+ ratio: Float,
+ lengthAdjustment: Float
+): Float {
+ val length = (sweepAngle) * ratio - lengthAdjustment
+ val maxLength = sweepAngle - 2F * lengthAdjustment
+ return (max(min(length, maxLength), 0F) + ARC_OFFSET_IN_DEGREES)
+}
+
+/**
+ * When the progress is static, we calculate the arc lengths as {@link #getCorrectStaticArcLength}.
+ * However, the clamp operation adds two extra animation quota per arc with animated dynamic
+ * progress which is not acceptable. We thus use an approximation calculation for dynamic values,
+ * Which is not very precise, but a good approximation. As follows:
+ * ```
+ * sweepAngle = endAngle-startAngle
+ * perArcLengthAdjustment = cap * 2 - gap
+ * maxTotalLength = endAngle-startAngle - 2 * perArcLengthAdjustment
+ * progressArcLength = maxTotalLength * progress
+ * trackArcLength = maxTotalLength * (1-progress)
+ * ```
+ */
+private fun getApproximateDynamicArcLength(
+ sweepAngle: Float,
+ ratio: DynamicFloat,
+ lengthAdjustment: Float
+) = ratio.times(sweepAngle - lengthAdjustment * 2).plus(ARC_OFFSET_IN_DEGREES)
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Helpers.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Helpers.kt
index a95093c..8f080b0 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Helpers.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Helpers.kt
@@ -116,7 +116,7 @@
* Note that this only looks at the static value of the [LayoutColor], any dynamic value will be
* ignored.
*/
-public fun LayoutColor.withOpacity(@FloatRange(from = 0.0, to = 1.0) ratio: Float): LayoutColor {
+internal fun LayoutColor.withOpacity(@FloatRange(from = 0.0, to = 1.0) ratio: Float): LayoutColor {
// From androidx.core.graphics.ColorUtils
require(!(ratio < 0 || ratio > 1)) { "setOpacityForColor ratio must be between 0 and 1." }
val fullyOpaque = 255
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Typography.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Typography.kt
index d4b560e..3863970f 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Typography.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Typography.kt
@@ -27,8 +27,6 @@
/** Returns the [TextStyle] from the typography tokens for the given token name. */
internal fun fromToken(@TypographyToken typographyToken: Int): TextStyle {
return when (typographyToken) {
- ARC_MEDIUM -> TypographyTokens.ARC_MEDIUM
- ARC_SMALL -> TypographyTokens.ARC_SMALL
BODY_EXTRA_SMALL -> TypographyTokens.BODY_EXTRA_SMALL
BODY_LARGE -> TypographyTokens.BODY_LARGE
BODY_MEDIUM -> TypographyTokens.BODY_MEDIUM
@@ -52,19 +50,6 @@
}
/**
- * ArcMedium is for arc headers and titles. Arc is for text along a curved path on the screen,
- * reserved for short header text strings at the very top or bottom of the screen like page
- * titles.
- */
- public const val ARC_MEDIUM: Int = 0
-
- /**
- * ArcSmall is for limited arc strings of text. Arc is for text along a curved path on the
- * screen, reserved for short curved text strings at the bottom of the screen.
- */
- public const val ARC_SMALL: Int = 1
-
- /**
* BodyExtraSmall is the smallest body. Body texts are typically used for long-form writing as
* it works well for small text sizes. For longer sections of text, a serif or sans serif
* typeface is recommended.
@@ -187,8 +172,6 @@
@RestrictTo(RestrictTo.Scope.LIBRARY)
@Retention(AnnotationRetention.SOURCE)
@IntDef(
- ARC_MEDIUM,
- ARC_SMALL,
BODY_EXTRA_SMALL,
BODY_LARGE,
BODY_MEDIUM,
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/TypographyFontSelection.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/TypographyFontSelection.kt
index 870a18d..e1d6767 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/TypographyFontSelection.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/TypographyFontSelection.kt
@@ -21,8 +21,6 @@
import androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp
import androidx.wear.protolayout.LayoutElementBuilders.FontWeightProp
import androidx.wear.protolayout.expression.ProtoLayoutExperimental
-import androidx.wear.protolayout.material3.Typography.ARC_MEDIUM
-import androidx.wear.protolayout.material3.Typography.ARC_SMALL
import androidx.wear.protolayout.material3.Typography.BODY_EXTRA_SMALL
import androidx.wear.protolayout.material3.Typography.BODY_LARGE
import androidx.wear.protolayout.material3.Typography.BODY_MEDIUM
@@ -57,8 +55,6 @@
FontVariantProp.Builder()
.setValue(
when (typographyToken) {
- ARC_MEDIUM,
- ARC_SMALL,
BODY_EXTRA_SMALL,
BODY_LARGE,
BODY_MEDIUM,
@@ -97,8 +93,6 @@
LABEL_SMALL,
BODY_EXTRA_SMALL,
BODY_SMALL,
- ARC_MEDIUM,
- ARC_SMALL,
NUMERAL_EXTRA_LARGE,
NUMERAL_LARGE,
NUMERAL_MEDIUM,
@@ -125,9 +119,7 @@
BODY_EXTRA_SMALL,
BODY_LARGE,
BODY_MEDIUM,
- BODY_SMALL,
- ARC_MEDIUM,
- ARC_SMALL -> true
+ BODY_SMALL -> true
DISPLAY_LARGE,
DISPLAY_MEDIUM,
DISPLAY_SMALL,
diff --git a/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/CircularProgressIndicatorTest.kt b/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/CircularProgressIndicatorTest.kt
index 07973b83..6d7e763 100644
--- a/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/CircularProgressIndicatorTest.kt
+++ b/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/CircularProgressIndicatorTest.kt
@@ -21,6 +21,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.wear.protolayout.DeviceParametersBuilders
import androidx.wear.protolayout.DimensionBuilders.expand
+import androidx.wear.protolayout.expression.VersionBuilders.VersionInfo
import androidx.wear.protolayout.testing.LayoutElementAssertionsProvider
import androidx.wear.protolayout.testing.hasHeight
import androidx.wear.protolayout.testing.hasWidth
@@ -61,6 +62,7 @@
DeviceParametersBuilders.DeviceParameters.Builder()
.setScreenWidthDp(192)
.setScreenHeightDp(192)
+ .setRendererSchemaVersion(VersionInfo.Builder().setMajor(1).setMinor(403).build())
.build()
private const val STATIC_PROGRESS = 0.5F
diff --git a/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/MaterialThemeTest.kt b/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/MaterialThemeTest.kt
index 9902024..1751085 100644
--- a/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/MaterialThemeTest.kt
+++ b/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/MaterialThemeTest.kt
@@ -34,7 +34,7 @@
fun defaultMaterialTheme_returnsTokenDefaults() {
val defaultTheme = MaterialTheme()
- for (i in 0 until Typography.TOKEN_COUNT) {
+ for (i in 2 until Typography.TOKEN_COUNT) {
val fontStyle: LayoutElementBuilders.FontStyle =
defaultTheme.getFontStyleBuilder(i).build()
val textStyle = Typography.fromToken(i)
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/OneOffLayoutChangeListener.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/OneOffLayoutChangeListener.java
new file mode 100644
index 0000000..be54029
--- /dev/null
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/OneOffLayoutChangeListener.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2025 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.protolayout.renderer.inflater;
+
+import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.View.OnLayoutChangeListener;
+
+import org.jspecify.annotations.NonNull;
+
+/**
+ * The listener that calls {@link OnLayoutChangeListener} only once, after the when View is
+ * attached.
+ *
+ * <p>Similar to the {@link androidx.core.view.OneShotPreDrawListener}, but calls {@code
+ * onLayoutChange} once. See b/381497935.
+ */
+class OneOffLayoutChangeListener implements OnLayoutChangeListener, OnAttachStateChangeListener {
+ private final @NonNull View mView;
+ private final @NonNull Runnable mRunnable;
+ private boolean mIsAttached = false;
+
+ private OneOffLayoutChangeListener(@NonNull View view, @NonNull Runnable runnable) {
+ this.mView = view;
+ this.mRunnable = runnable;
+ }
+
+ /**
+ * Creates and adds an instance of {@link OneOffLayoutChangeListener} to the given {@link View}.
+ *
+ * @param view The view whose OnLayoutChangeListener should listen.
+ * <p>Note that the returned listener is already attached to the view and it will be
+ * detached when it's called.
+ * @param runnable The Runnable to execute in the onLayoutChange (once). It is this Runnable's
+ * responsibility to invalidate or update any layout in the view.
+ */
+ public static @NonNull OneOffLayoutChangeListener add(
+ @NonNull View view, @NonNull Runnable runnable) {
+ OneOffLayoutChangeListener listener = new OneOffLayoutChangeListener(view, runnable);
+ // onLayoutChange listener will be added only after this View was attached.
+ view.addOnAttachStateChangeListener(listener);
+ return listener;
+ }
+
+ @Override
+ public void onViewAttachedToWindow(@NonNull View v) {
+ // We need this flag in case detach was called before onLayoutChange
+ mIsAttached = true;
+ mView.addOnLayoutChangeListener(this);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(@NonNull View v) {
+ removeOnLayoutChangeListener();
+ mIsAttached = false;
+ }
+
+ @Override
+ public void onLayoutChange(
+ View view,
+ int left,
+ int top,
+ int right,
+ int bottom,
+ int oldLeft,
+ int oldTop,
+ int oldRight,
+ int oldBottom) {
+ // Only call runnable.run() if View is attached.
+ if (!mIsAttached) {
+ return;
+ }
+
+ // Since we called the runnable after the View was attached, we can remove that listener too
+ mView.removeOnAttachStateChangeListener(this);
+ removeOnLayoutChangeListener();
+ mRunnable.run();
+ }
+
+ private void removeOnLayoutChangeListener() {
+ mView.removeOnLayoutChangeListener(this);
+ }
+}
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/OneOffPreDrawListener.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/OneOffPreDrawListener.java
deleted file mode 100644
index 5ea9abb..0000000
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/OneOffPreDrawListener.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright 2024 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.protolayout.renderer.inflater;
-
-import android.view.View;
-import android.view.View.OnAttachStateChangeListener;
-import android.view.ViewTreeObserver;
-import android.view.ViewTreeObserver.OnPreDrawListener;
-
-import org.jspecify.annotations.NonNull;
-import org.jspecify.annotations.Nullable;
-
-import java.util.function.Supplier;
-
-/**
- * The listener that calls {@link ViewTreeObserver.OnPreDrawListener} only once, after the when View
- * is attached.
- *
- * <p>Similar to the {@link androidx.core.view.OneShotPreDrawListener}, but {@code onPreDraw} method
- * can return false, meaning that the current draw is cancelled.
- */
-class OneOffPreDrawListener implements OnPreDrawListener, OnAttachStateChangeListener {
-
- private final @NonNull View mView;
- private @Nullable ViewTreeObserver mViewTreeObserver;
- private final @NonNull Supplier<Boolean> mSupplier;
- private boolean mIsAttached = false;
-
- private OneOffPreDrawListener(@NonNull View view, @NonNull Supplier<Boolean> supplier) {
- this.mView = view;
- this.mSupplier = supplier;
- }
-
- /**
- * Creates and adds an instance of {@link OneOffPreDrawListener} to the given {@link View} by
- *
- * @param view The view whose ViewTreeObserver the OnPreDrawListener should listen.
- * <p>Note that the returned listener is already attached.
- * @param supplier The Supplier to execute in the OnPreDraw (once). This Supplier should return
- * false if the current drawing pass that was called for needs to be cancelled.
- */
- public static @NonNull OneOffPreDrawListener add(
- @NonNull View view, @NonNull Supplier<Boolean> supplier) {
- OneOffPreDrawListener listener = new OneOffPreDrawListener(view, supplier);
- // OnPreDraw listener will be added only after this View was attached.
- view.addOnAttachStateChangeListener(listener);
- return listener;
- }
-
- @Override
- public void onViewAttachedToWindow(@NonNull View v) {
- // We need this flag in case detach was called before onPreDraw
- mIsAttached = true;
- mViewTreeObserver = v.getViewTreeObserver();
- mViewTreeObserver.addOnPreDrawListener(this);
- }
-
- @Override
- public void onViewDetachedFromWindow(@NonNull View v) {
- removeOnPreDrawListener();
- mIsAttached = false;
- }
-
- @Override
- public boolean onPreDraw() {
- // Call onPreDraw only once so removing the listener.
- removeOnPreDrawListener();
-
- // Only call supplier.get() if View is attached.
- if (!mIsAttached) {
- return true;
- }
-
- // Since we called the supplier after the View was attached, we can remove that listener
- // too.
- mView.removeOnAttachStateChangeListener(this);
- return mSupplier.get();
- }
-
- private void removeOnPreDrawListener() {
- if (mViewTreeObserver == null) {
- return;
- }
-
- if (mViewTreeObserver.isAlive()) {
- mViewTreeObserver.removeOnPreDrawListener(this);
- } else {
- mView.getViewTreeObserver().removeOnPreDrawListener(this);
- }
- mViewTreeObserver = null;
- }
-}
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
index 1262b78..012a095 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
@@ -2162,7 +2162,15 @@
return view;
}
+
private static int textAlignToAndroidGravity(TextAlignment alignment) {
+ // Vertical center alignment is usually a default and text will be centered vertically.
+ // However, we need it explicitly for cases when max lines are adjusted and shrank, so there
+ // could be a bit of extra space and text would be anchored to the top.
+ return textAlignToAndroidGravityHorizontal(alignment) | Gravity.CENTER_VERTICAL;
+ }
+
+ private static int textAlignToAndroidGravityHorizontal(TextAlignment alignment) {
switch (alignment) {
case TEXT_ALIGN_START:
return Gravity.START;
@@ -2861,7 +2869,7 @@
&& !text.getText().hasDynamicValue()
// There's no need for any optimizations if max lines is already 1.
&& textView.getMaxLines() != 1) {
- OneOffPreDrawListener.add(textView, () -> adjustMaxLinesForEllipsize(textView));
+ OneOffLayoutChangeListener.add(textView, () -> adjustMaxLinesForEllipsize(textView));
}
// Text auto size is not supported for dynamic text.
@@ -2960,14 +2968,14 @@
* different than what TEXT_OVERFLOW_ELLIPSIZE_END does, as that option just ellipsizes the last
* line of text.
*/
- private static boolean adjustMaxLinesForEllipsize(@NonNull TextView textView) {
+ private static void adjustMaxLinesForEllipsize(@NonNull TextView textView) {
ViewParent maybeParent = textView.getParent();
if (!(maybeParent instanceof View)) {
Log.d(
TAG,
"Couldn't adjust max lines for ellipsizing as there's no View/ViewGroup"
+ " parent.");
- return true;
+ return;
}
View parent = (View) maybeParent;
@@ -2978,14 +2986,16 @@
// Avoid having maxLines as 0 in case the space is really tight.
int availableLines = max(availableHeight / oneLineHeight, 1);
- // Update only if changed.
- if (availableLines < maxMaxLines) {
- textView.setMaxLines(availableLines);
- // Cancel the current drawing pass.
- return false;
+ // Update only if maxLines are changed.
+ if (availableLines >= maxMaxLines) {
+ return;
}
- return true;
+ textView.setMaxLines(availableLines);
+ // We need to trigger TextView to re-measure its content in order to place the ellipsis
+ // correctly at the and of {@code availableLines}th line. Using only {@code requestLayout}
+ // or {@code invalidate} isn't enough as TextView wouldn't remeasure itself.
+ textView.setText(textView.getText());
}
/**
@@ -4923,8 +4933,6 @@
return "";
}
- // getObsoleteContentDescription is used for backward compatibility
- @SuppressWarnings("deprecation")
private void applySemantics(
View view,
Semantics semantics,
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/OneOffPreDrawListenerTest.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/OneOffLayoutChangeListenerTest.java
similarity index 70%
rename from wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/OneOffPreDrawListenerTest.java
rename to wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/OneOffLayoutChangeListenerTest.java
index 27f9d28..202d836 100644
--- a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/OneOffPreDrawListenerTest.java
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/OneOffLayoutChangeListenerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 The Android Open Source Project
+ * Copyright 2025 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.
@@ -30,52 +30,47 @@
// This class tests the logic for calling listeners. The actual logic for specific listener is
// tested separately.
@RunWith(AndroidJUnit4.class)
-public class OneOffPreDrawListenerTest {
+public class OneOffLayoutChangeListenerTest {
@Test
- public void test_onPreDrawLogic_calledOnlyOnce() {
+ public void test_onLayoutChangeLogic_calledOnlyOnce() {
TestView view = new TestView();
- TestSupplier supplier = new TestSupplier();
+ TestRunnable runnable = new TestRunnable();
- OneOffPreDrawListener listener = OneOffPreDrawListener.add(view, supplier::run);
+ OneOffLayoutChangeListener listener = OneOffLayoutChangeListener.add(view, runnable::run);
assertThat(view.hasOnAttachStateListener).isTrue();
assertThat(view.isOnAttachStateListenerRemoved).isFalse();
- // Supplier hasn't been called yet.
- assertThat(supplier.runCnt).isEqualTo(0);
+ // Runnable hasn't been called yet.
+ assertThat(runnable.runCnt).isEqualTo(0);
- // Supplier hasn't been called yet, but we attached listener, state listener stayed
+ // Runnable hasn't been called yet, but we attached listener, state listener stayed
// attached.
listener.onViewAttachedToWindow(view);
assertThat(view.isOnAttachStateListenerRemoved).isFalse();
- assertThat(supplier.runCnt).isEqualTo(0);
+ assertThat(runnable.runCnt).isEqualTo(0);
- // Now preDraw is called so should the logic.
- supplier.preDrawReturnValue = false;
- assertThat(listener.onPreDraw()).isFalse();
+ // Now onLayoutChange is called so should the logic.
+ listener.onLayoutChange(view, 0, 0, 0, 0, 0, 0, 0, 0);
+ assertThat(runnable.runCnt).isEqualTo(1);
// And state listener should be removed.
assertThat(view.isOnAttachStateListenerRemoved).isTrue();
- assertThat(supplier.runCnt).isEqualTo(1);
-
// Now it shouldn't after detach.
listener.onViewDetachedFromWindow(view);
- assertThat(listener.onPreDraw()).isTrue();
- assertThat(supplier.runCnt).isEqualTo(1);
+ listener.onLayoutChange(view, 0, 0, 0, 0, 0, 0, 0, 0);
+ assertThat(runnable.runCnt).isEqualTo(1);
// But with new attach it should be called.
listener.onViewAttachedToWindow(view);
- supplier.preDrawReturnValue = true;
- assertThat(listener.onPreDraw()).isTrue();
- assertThat(supplier.runCnt).isEqualTo(2);
+ listener.onLayoutChange(view, 0, 0, 0, 0, 0, 0, 0, 0);
+ assertThat(runnable.runCnt).isEqualTo(2);
}
- private static final class TestSupplier {
- public boolean preDrawReturnValue = true;
+ private static final class TestRunnable {
public int runCnt = 0;
- boolean run() {
+ void run() {
runCnt++;
- return preDrawReturnValue;
}
}
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/Semantics.kt b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/Semantics.kt
index d35b071..d77f5e3 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/Semantics.kt
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/modifiers/Semantics.kt
@@ -26,7 +26,7 @@
import java.util.Objects
/**
- * Adds content description to be read by Talkback.
+ * Adds content description to be read by accessibility services.
*
* @param staticValue The static content description. This value will be used if [dynamicValue] is
* null, or if can't be resolved.
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/types/Helpers.kt b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/types/Helpers.kt
index ef30cd7..cd0f098 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/types/Helpers.kt
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/types/Helpers.kt
@@ -16,6 +16,8 @@
package androidx.wear.protolayout.types
+import androidx.annotation.RestrictTo
+import androidx.annotation.RestrictTo.Scope
import androidx.wear.protolayout.DimensionBuilders.DpProp
import androidx.wear.protolayout.DimensionBuilders.EmProp
import androidx.wear.protolayout.DimensionBuilders.SpProp
@@ -32,7 +34,8 @@
internal val Boolean.prop: BoolProp
get() = BoolProp.Builder(this).build()
-internal val Float.dp: DpProp
+@get:RestrictTo(Scope.LIBRARY_GROUP)
+val Float.dp: DpProp
get() = DpProp.Builder(this).build()
@RequiresSchemaVersion(major = 1, minor = 400)
diff --git a/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt b/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt
index f3f97b9..183279b 100644
--- a/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt
+++ b/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt
@@ -17,6 +17,8 @@
package androidx.wear.tiles.samples.tile
import android.content.Context
+import androidx.wear.protolayout.DeviceParametersBuilders
+import androidx.wear.protolayout.DimensionBuilders.dp
import androidx.wear.protolayout.DimensionBuilders.expand
import androidx.wear.protolayout.DimensionBuilders.weight
import androidx.wear.protolayout.LayoutElementBuilders
@@ -25,6 +27,7 @@
import androidx.wear.protolayout.ResourceBuilders.ImageResource
import androidx.wear.protolayout.TimelineBuilders
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat
+import androidx.wear.protolayout.expression.VersionBuilders.VersionInfo
import androidx.wear.protolayout.material3.ButtonDefaults.filledVariantButtonColors
import androidx.wear.protolayout.material3.CardColors
import androidx.wear.protolayout.material3.CardDefaults.filledTonalCardColors
@@ -250,6 +253,43 @@
}
)
+private fun MaterialScope.graphicDataCardSampleWithFallbackProgressIndicator(context: Context) =
+ graphicDataCard(
+ onClick = clickable(),
+ modifier = LayoutModifier.contentDescription("Graphic Data Card"),
+ height = expand(),
+ horizontalAlignment = LayoutElementBuilders.HORIZONTAL_ALIGN_END,
+ title = {
+ text(
+ "1,234!".layoutString,
+ )
+ },
+ content = {
+ text(
+ "steps".layoutString,
+ )
+ },
+ graphic = {
+ materialScope(
+ context = context,
+ deviceConfiguration =
+ DeviceParametersBuilders.DeviceParameters.Builder()
+ .setRendererSchemaVersion(
+ VersionInfo.Builder().setMajor(1).setMinor(402).build()
+ )
+ .build()
+ ) {
+ segmentedCircularProgressIndicator(
+ segmentCount = 6,
+ startAngleDegrees = 200F,
+ endAngleDegrees = 520F,
+ dynamicProgress = DynamicFloat.animate(0.0F, 1.0F, recommendedAnimationSpec),
+ size = dp(75F)
+ )
+ }
+ }
+ )
+
private fun MaterialScope.dataCards() = buttonGroup {
buttonGroupItem {
textDataCard(
diff --git a/webkit/integration-tests/common/src/main/java/androidx/webkit/test/common/PollingCheck.java b/webkit/integration-tests/common/src/main/java/androidx/webkit/test/common/PollingCheck.java
index b60e7a8..d5d7853 100644
--- a/webkit/integration-tests/common/src/main/java/androidx/webkit/test/common/PollingCheck.java
+++ b/webkit/integration-tests/common/src/main/java/androidx/webkit/test/common/PollingCheck.java
@@ -33,7 +33,7 @@
*/
public abstract class PollingCheck {
private static final long TIME_SLICE = 50;
- private long mTimeout;
+ private final long mTimeout;
public PollingCheck(long timeout) {
mTimeout = timeout;
diff --git a/webkit/integration-tests/common/src/main/java/androidx/webkit/test/common/TestWebMessageListener.java b/webkit/integration-tests/common/src/main/java/androidx/webkit/test/common/TestWebMessageListener.java
index 9543319..21c912b 100644
--- a/webkit/integration-tests/common/src/main/java/androidx/webkit/test/common/TestWebMessageListener.java
+++ b/webkit/integration-tests/common/src/main/java/androidx/webkit/test/common/TestWebMessageListener.java
@@ -16,7 +16,6 @@
package androidx.webkit.test.common;
-import android.annotation.SuppressLint;
import android.net.Uri;
import android.webkit.WebView;
@@ -24,12 +23,12 @@
import androidx.webkit.WebMessageCompat;
import androidx.webkit.WebViewCompat;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
public class TestWebMessageListener implements WebViewCompat.WebMessageListener {
private final BlockingQueue<Data> mQueue = new LinkedBlockingQueue<>();
@@ -38,6 +37,7 @@
Uri mSourceOrigin;
boolean mIsMainFrame;
public @Nullable JavaScriptReplyProxy mReplyProxy;
+
Data(WebMessageCompat message, Uri sourceOrigin, boolean isMainFrame,
JavaScriptReplyProxy replyProxy) {
mMessage = message;
@@ -55,14 +55,14 @@
}
/**
- * Blocks and waits for onPostMessage to queue up data from JavaScript.
+ * Blocks and waits for onPostMessage to queue up data from JavaScript.
*/
public @NonNull Data waitForOnPostMessage() throws Exception {
return WebkitUtils.waitForNextQueueElement(mQueue);
}
/**
- * Indicates whether the queue has any elements enqueued.
+ * Indicates whether the queue has any elements enqueued.
*/
public boolean hasNoMoreOnPostMessage() {
return mQueue.isEmpty();
diff --git a/webkit/integration-tests/common/src/main/java/androidx/webkit/test/common/WebkitUtils.java b/webkit/integration-tests/common/src/main/java/androidx/webkit/test/common/WebkitUtils.java
index 3caed8f..8f4e30c 100644
--- a/webkit/integration-tests/common/src/main/java/androidx/webkit/test/common/WebkitUtils.java
+++ b/webkit/integration-tests/common/src/main/java/androidx/webkit/test/common/WebkitUtils.java
@@ -16,7 +16,6 @@
package androidx.webkit.test.common;
-import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/AsyncStartUpTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/AsyncStartUpTest.java
index cb62196..f828c7e 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/AsyncStartUpTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/AsyncStartUpTest.java
@@ -23,8 +23,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.webkit.internal.WebViewGlueCommunicator;
-import androidx.webkit.test.common.WebkitUtils;
import androidx.webkit.test.common.WebViewOnUiThread;
+import androidx.webkit.test.common.WebkitUtils;
import org.junit.Assert;
import org.junit.Assume;
@@ -141,7 +141,7 @@
// Triggers provider init.
new Handler(Looper.getMainLooper()).post(WebViewGlueCommunicator::getWebViewClassLoader);
WebViewCompat.startUpWebView(config,
- startUpFinishedFuture::set);
+ startUpFinishedFuture::set);
// Wait until the callback has triggered.
WebViewStartUpResult result = WebkitUtils.waitForFuture(startUpFinishedFuture);
Assert.assertNotNull(result);
@@ -271,7 +271,7 @@
@MediumTest
@Ignore("b/376656739")
public void
- testAsyncStartUp_withoutRunningUiThreadStartUpLoadsWebViewWithoutStartingChromium()
+ testAsyncStartUp_withoutRunningUiThreadStartUpLoadsWebViewWithoutStartingChromium()
throws Throwable {
Assume.assumeFalse(webViewCurrentlyLoaded());
WebViewStartUpConfig config = new WebViewStartUpConfig.Builder(
@@ -301,7 +301,7 @@
@MediumTest
@Ignore("b/376656739")
public void
- testAsyncStartUp_withoutRunningUiThreadStartUpReturnsBlockingLocationWithProviderInit()
+ testAsyncStartUp_withoutRunningUiThreadStartUpReturnsBlockingLocationWithProviderInit()
throws Throwable {
Assume.assumeFalse(webViewCurrentlyLoaded());
WebViewStartUpConfig config = new WebViewStartUpConfig.Builder(
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/MultiProfileTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/MultiProfileTest.java
index 47b9693..a272a31 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/MultiProfileTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/MultiProfileTest.java
@@ -26,8 +26,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
-import androidx.webkit.test.common.WebkitUtils;
import androidx.webkit.test.common.WebViewOnUiThread;
+import androidx.webkit.test.common.WebkitUtils;
import org.junit.Assert;
import org.junit.Before;
@@ -49,6 +49,7 @@
// We are unifying the name as there's no way at the moment to delete the loaded profiles, we
// should be able to use different test profiles once b/304456333 is fixed.
private static final String PROFILE_TEST_NAME = "Test";
+
@Before
public void setUp() {
WebkitUtils.checkFeature(WebViewFeature.MULTI_PROFILE);
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/PostMessageTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/PostMessageTest.java
index fbd186a..068eeae 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/PostMessageTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/PostMessageTest.java
@@ -26,8 +26,8 @@
import androidx.test.filters.LargeTest;
import androidx.webkit.WebMessagePortCompat.WebMessageCallbackCompat;
import androidx.webkit.test.common.PollingCheck;
-import androidx.webkit.test.common.WebkitUtils;
import androidx.webkit.test.common.WebViewOnUiThread;
+import androidx.webkit.test.common.WebkitUtils;
import org.jspecify.annotations.NonNull;
import org.junit.After;
@@ -197,7 +197,7 @@
for (int i = 0; i < messageCount; i++) {
// The JavaScript code simply appends an integer counter to the end of the message it
// receives, which is why we have a second i on the end.
- String expectedMessageFromJavascript = WEBVIEW_MESSAGE + i + "" + i;
+ String expectedMessageFromJavascript = WEBVIEW_MESSAGE + i + i;
Assert.assertEquals(expectedMessageFromJavascript,
WebkitUtils.waitForNextQueueElement(queue));
}
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/ProxyControllerTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/ProxyControllerTest.java
index 25eda66..72d4843e4 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/ProxyControllerTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/ProxyControllerTest.java
@@ -27,8 +27,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.webkit.internal.ProxyControllerImpl;
-import androidx.webkit.test.common.WebkitUtils;
import androidx.webkit.test.common.WebViewOnUiThread;
+import androidx.webkit.test.common.WebkitUtils;
import org.junit.After;
import org.junit.Assert;
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/ServiceWorkerClientCompatTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/ServiceWorkerClientCompatTest.java
index 144c44c..33ddef9 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/ServiceWorkerClientCompatTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/ServiceWorkerClientCompatTest.java
@@ -27,8 +27,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.webkit.test.common.PollingCheck;
-import androidx.webkit.test.common.WebkitUtils;
import androidx.webkit.test.common.WebViewOnUiThread;
+import androidx.webkit.test.common.WebkitUtils;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
@@ -39,7 +39,7 @@
import org.junit.runner.RunWith;
import java.io.ByteArrayInputStream;
-import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
@@ -104,12 +104,10 @@
public WebResourceResponse shouldInterceptRequest(WebView view,
WebResourceRequest request) {
// Only return content for INDEX_URL, deny all other requests.
- try {
- if (request.getUrl().toString().equals(INDEX_URL)) {
- return new WebResourceResponse("text/html", "utf-8",
- new ByteArrayInputStream(INDEX_RAW_HTML.getBytes("UTF-8")));
- }
- } catch (UnsupportedEncodingException e) { }
+ if (request.getUrl().toString().equals(INDEX_URL)) {
+ return new WebResourceResponse("text/html", "utf-8",
+ new ByteArrayInputStream(INDEX_RAW_HTML.getBytes(StandardCharsets.UTF_8)));
+ }
return new WebResourceResponse("text/html", "UTF-8", null);
}
}
@@ -122,12 +120,10 @@
@NonNull WebResourceRequest request) {
// Records intercepted requests and only return content for SW_URL.
mInterceptedRequests.add(request);
- try {
- if (request.getUrl().toString().equals(SW_URL)) {
- return new WebResourceResponse("application/javascript", "utf-8",
- new ByteArrayInputStream(SW_RAW_HTML.getBytes("UTF-8")));
- }
- } catch (java.io.UnsupportedEncodingException e) { }
+ if (request.getUrl().toString().equals(SW_URL)) {
+ return new WebResourceResponse("application/javascript", "utf-8",
+ new ByteArrayInputStream(SW_RAW_HTML.getBytes(StandardCharsets.UTF_8)));
+ }
return new WebResourceResponse("text/html", "UTF-8", null);
}
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/ServiceWorkerWebSettingsCompatTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/ServiceWorkerWebSettingsCompatTest.java
index 55c8160..9f01dc0 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/ServiceWorkerWebSettingsCompatTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/ServiceWorkerWebSettingsCompatTest.java
@@ -24,8 +24,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.webkit.test.common.WebkitUtils;
import androidx.webkit.test.common.WebViewOnUiThread;
+import androidx.webkit.test.common.WebkitUtils;
import org.junit.After;
import org.junit.Assert;
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/TracingControllerTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/TracingControllerTest.java
index 84be87d..57496d7 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/TracingControllerTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/TracingControllerTest.java
@@ -22,8 +22,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.webkit.test.common.PollingCheck;
-import androidx.webkit.test.common.WebkitUtils;
import androidx.webkit.test.common.WebViewOnUiThread;
+import androidx.webkit.test.common.WebkitUtils;
import org.junit.After;
import org.junit.Assert;
@@ -232,6 +232,7 @@
private ThreadFactory getCustomThreadFactory() {
return new ThreadFactory() {
private final AtomicInteger mThreadCount = new AtomicInteger(0);
+
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
@@ -296,6 +297,7 @@
return mChunkCount;
}
+
boolean getComplete() {
return mComplete;
}
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatDarkModeTestBase.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatDarkModeTestBase.java
index 7a79081..bb7c299 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatDarkModeTestBase.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatDarkModeTestBase.java
@@ -25,8 +25,8 @@
import android.webkit.WebSettings;
import android.webkit.WebView;
-import androidx.webkit.test.common.WebkitUtils;
import androidx.webkit.test.common.WebViewOnUiThread;
+import androidx.webkit.test.common.WebkitUtils;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
@@ -76,7 +76,7 @@
@Rule
public final androidx.test.rule.ActivityTestRule<T> mActivityRule;
- /** @noinspection deprecation*/
+ /** @noinspection deprecation */
@SuppressWarnings("deprecation")
public WebSettingsCompatDarkModeTestBase(@NonNull Class<T> activityClass) {
mActivityRule = new androidx.test.rule.ActivityTestRule<>(activityClass);
@@ -132,8 +132,8 @@
double major = 1.0 * maxEntry.getValue() / (sideLength * sideLength);
assertTrue(
"The majority color should be at least 85% of the pixels,"
- + " the actual value " + major,
- major > 0.85);
+ + " the actual value " + major,
+ major > 0.85);
return maxEntry.getKey();
}
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
index 6a097be..10e671d 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
@@ -28,8 +28,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.webkit.test.common.WebkitUtils;
import androidx.webkit.test.common.WebViewOnUiThread;
+import androidx.webkit.test.common.WebkitUtils;
import org.junit.After;
import org.junit.Assert;
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatUserAgentMetadataTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatUserAgentMetadataTest.java
index 55ac1fd..ecd8961 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatUserAgentMetadataTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatUserAgentMetadataTest.java
@@ -28,8 +28,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.webkit.test.common.WebkitUtils;
import androidx.webkit.test.common.WebViewOnUiThread;
+import androidx.webkit.test.common.WebkitUtils;
import org.junit.After;
import org.junit.Assert;
@@ -53,8 +53,8 @@
"sec-ch-ua-platform-version", "sec-ch-ua-bitness", "sec-ch-ua-full-version-list",
"sec-ch-ua-wow64"};
- private static final String FIRST_URL = "/first.html";
- private static final String SECOND_URL = "/second.html";
+ private static final String FIRST_URL = "/first.html";
+ private static final String SECOND_URL = "/second.html";
private static final String FIRST_RAW_HTML =
"<!DOCTYPE html>\n"
@@ -95,6 +95,7 @@
private static final class TestHttpsWebViewClient extends
WebViewOnUiThread.WaitForLoadedClient {
private final List<WebResourceRequest> mInterceptedRequests = new ArrayList<>();
+
TestHttpsWebViewClient(WebViewOnUiThread webViewOnUiThread) throws Exception {
super(webViewOnUiThread);
}
@@ -195,7 +196,7 @@
// client hints cache impacts other tests.
String baseUrl = "https://example1.com";
mWebViewOnUiThread.loadUrlAndWaitForCompletion(baseUrl + FIRST_URL);
- List<WebResourceRequest> requests = mTestHttpsWebViewClient.getInterceptedRequests();
+ List<WebResourceRequest> requests = mTestHttpsWebViewClient.getInterceptedRequests();
Assert.assertEquals(2, requests.size());
// Make sure the first request has low-entropy client hints.
@@ -277,7 +278,7 @@
// client hints cache impacts other tests.
String baseUrl = "https://example2.com";
mWebViewOnUiThread.loadUrlAndWaitForCompletion(baseUrl + FIRST_URL);
- List<WebResourceRequest> requests = mTestHttpsWebViewClient.getInterceptedRequests();
+ List<WebResourceRequest> requests = mTestHttpsWebViewClient.getInterceptedRequests();
Assert.assertEquals(2, requests.size());
// Make sure the first request has low-entropy client hints.
@@ -365,7 +366,7 @@
String baseUrl = "https://example3.com";
mWebViewOnUiThread.loadUrlAndWaitForCompletion(baseUrl + FIRST_URL);
mWebViewOnUiThread.loadUrlAndWaitForCompletion(FIRST_URL);
- List<WebResourceRequest> requests = mTestHttpsWebViewClient.getInterceptedRequests();
+ List<WebResourceRequest> requests = mTestHttpsWebViewClient.getInterceptedRequests();
Assert.assertEquals(2, requests.size());
// Make sure the first request has low-entropy client hints.
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebStorageTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebStorageTest.java
index 2a745dfc..7586996 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebStorageTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebStorageTest.java
@@ -25,8 +25,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SdkSuppress;
-import androidx.webkit.test.common.WebkitUtils;
import androidx.webkit.test.common.WebViewOnUiThread;
+import androidx.webkit.test.common.WebkitUtils;
import org.junit.After;
import org.junit.Assert;
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewClientCompatTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewClientCompatTest.java
index b3c98ff..28da65b 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewClientCompatTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewClientCompatTest.java
@@ -26,8 +26,8 @@
import androidx.concurrent.futures.ResolvableFuture;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.webkit.test.common.WebkitUtils;
import androidx.webkit.test.common.WebViewOnUiThread;
+import androidx.webkit.test.common.WebkitUtils;
import org.jspecify.annotations.NonNull;
import org.junit.After;
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewCompatTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewCompatTest.java
index 93083d9..fdd5337 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewCompatTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewCompatTest.java
@@ -40,8 +40,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SdkSuppress;
-import androidx.webkit.test.common.WebkitUtils;
import androidx.webkit.test.common.WebViewOnUiThread;
+import androidx.webkit.test.common.WebkitUtils;
import org.jspecify.annotations.NonNull;
import org.junit.After;
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewDocumentStartJavaScriptTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewDocumentStartJavaScriptTest.java
index d3533d4..d46aef4 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewDocumentStartJavaScriptTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewDocumentStartJavaScriptTest.java
@@ -16,27 +16,21 @@
package androidx.webkit;
-import android.net.Uri;
-import android.webkit.WebView;
-
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.webkit.test.common.TestWebMessageListener;
-import androidx.webkit.test.common.WebkitUtils;
import androidx.webkit.test.common.WebViewOnUiThread;
+import androidx.webkit.test.common.WebkitUtils;
-import org.jspecify.annotations.NonNull;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
/**
* Test
@@ -51,10 +45,11 @@
private static final String JS_OBJECT_NAME = "myObject";
private static final String BASIC_USAGE = "<!DOCTYPE html><html><body></body></html>";
private static final String BASIC_SCRIPT = "myObject.postMessage('hello');";
- private static final Set<String> MATCH_EXAMPLE_COM = new HashSet<>(Arrays.asList(BASE_URI));
+ private static final Set<String> MATCH_EXAMPLE_COM = new HashSet<>(
+ Collections.singletonList(BASE_URI));
private WebViewOnUiThread mWebViewOnUiThread;
- private TestWebMessageListener mListener = new TestWebMessageListener();
+ private final TestWebMessageListener mListener = new TestWebMessageListener();
@Before
public void setUp() {
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewRenderProcessClientTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewRenderProcessClientTest.java
index 2976981..96fa4c8 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewRenderProcessClientTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewRenderProcessClientTest.java
@@ -23,8 +23,8 @@
import androidx.concurrent.futures.ResolvableFuture;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.webkit.test.common.WebkitUtils;
import androidx.webkit.test.common.WebViewOnUiThread;
+import androidx.webkit.test.common.WebkitUtils;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
@@ -60,8 +60,9 @@
// A CountDownLatch is used here, instead of a Future, because that makes it
// easier to support requiring variable numbers of releaseBlock() calls
// to unblock.
- private CountDownLatch mLatch;
- private ResolvableFuture<Void> mBecameBlocked;
+ private final CountDownLatch mLatch;
+ private final ResolvableFuture<Void> mBecameBlocked;
+
JSBlocker(int requiredReleaseCount) {
mLatch = new CountDownLatch(requiredReleaseCount);
mBecameBlocked = ResolvableFuture.create();
@@ -188,7 +189,7 @@
WebViewRenderProcessClient client = makeWebViewRenderProcessClient(
() -> clientCalled.set(true),
() -> clientCalled.set(true)
- );
+ );
mWebViewOnUiThread.setWebViewRenderProcessClient(client);
mWebViewOnUiThread.setWebViewRenderProcessClient(null);
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewRenderProcessTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewRenderProcessTest.java
index 821703b..4c43f94 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewRenderProcessTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewRenderProcessTest.java
@@ -27,8 +27,8 @@
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SdkSuppress;
-import androidx.webkit.test.common.WebkitUtils;
import androidx.webkit.test.common.WebViewOnUiThread;
+import androidx.webkit.test.common.WebkitUtils;
import com.google.common.util.concurrent.ListenableFuture;
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewWebMessageCompatTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewWebMessageCompatTest.java
index 441da04..35255f4 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewWebMessageCompatTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewWebMessageCompatTest.java
@@ -22,8 +22,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
-import androidx.webkit.test.common.WebkitUtils;
import androidx.webkit.test.common.WebViewOnUiThread;
+import androidx.webkit.test.common.WebkitUtils;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewWebMessageListenerTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewWebMessageListenerTest.java
index 2fd1d76..6bf6164 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewWebMessageListenerTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewWebMessageListenerTest.java
@@ -16,16 +16,12 @@
package androidx.webkit;
-import android.net.Uri;
-import android.webkit.WebView;
-
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.webkit.test.common.TestWebMessageListener;
-import androidx.webkit.test.common.WebkitUtils;
import androidx.webkit.test.common.WebViewOnUiThread;
+import androidx.webkit.test.common.WebkitUtils;
-import org.jspecify.annotations.NonNull;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -36,8 +32,6 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
/**
* Test {@link WebViewCompat#addWebMessageListener} and {@link
@@ -120,7 +114,7 @@
// in JavaScript.
loadHtmlSync(BASIC_ARRAY_BUFFER_USAGE);
TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
- Assert.assertArrayEquals(new byte[] {1, 2, 3}, data.mMessage.getArrayBuffer());
+ Assert.assertArrayEquals(new byte[]{1, 2, 3}, data.mMessage.getArrayBuffer());
Assert.assertTrue(
"Should have no more message at this point.", mListener.hasNoMoreOnPostMessage());
@@ -266,7 +260,7 @@
@Test
public void testJavaScriptReplyProxyBasicUsage_ArrayBuffer() throws Exception {
WebkitUtils.checkFeature(WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER);
- verifyJavaScriptReplyProxyArrayBuffer(new byte[] {1, 2, 3, 4, 5});
+ verifyJavaScriptReplyProxyArrayBuffer(new byte[]{1, 2, 3, 4, 5});
}
@Test
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/internal/AssetHelperTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/internal/AssetHelperTest.java
index 3f31a37..1307b71 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/internal/AssetHelperTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/internal/AssetHelperTest.java
@@ -282,7 +282,7 @@
private byte[] readFully(InputStream stream) throws IOException {
ByteArrayOutputStream data = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
- for (;;) {
+ for (; ; ) {
int len = stream.read(buf);
if (len < 1) break;
data.write(buf, 0, len);
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewProviderAdapter.java b/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewProviderAdapter.java
index 1eb7d18..0bfb95e 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewProviderAdapter.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewProviderAdapter.java
@@ -18,13 +18,11 @@
import android.annotation.SuppressLint;
import android.net.Uri;
-import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.core.os.CancellationSignal;
-import androidx.webkit.PrerenderException;
import androidx.webkit.PrerenderOperationCallback;
import androidx.webkit.Profile;
import androidx.webkit.SpeculativeLoadingParameters;
@@ -203,19 +201,6 @@
@NonNull String url,
@Nullable CancellationSignal cancellationSignal,
@NonNull PrerenderOperationCallback callback) {
-
- ValueCallback<Void> activationCallback = (value) -> {
- // value will always be null.
- callback.onPrerenderActivated();
- };
- ValueCallback<Throwable> errorCallback = (throwable) -> {
- callback.onError(new PrerenderException("Prerender operation failed", throwable));
- };
- mImpl.prerenderUrl(
- url,
- cancellationSignal,
- activationCallback,
- errorCallback);
}
/**
@@ -228,22 +213,5 @@
@Nullable CancellationSignal cancellationSignal,
@NonNull SpeculativeLoadingParameters params,
@NonNull PrerenderOperationCallback callback) {
-
- InvocationHandler paramsBoundaryInterface =
- BoundaryInterfaceReflectionUtil.createInvocationHandlerFor(
- new SpeculativeLoadingParametersAdapter(params));
- ValueCallback<Void> activationCallback = (value) -> {
- // value will always be null.
- callback.onPrerenderActivated();
- };
- ValueCallback<Throwable> errorCallback = (throwable) -> {
- callback.onError(new PrerenderException("Prerender operation failed", throwable));
- };
- mImpl.prerenderUrl(
- url,
- cancellationSignal,
- paramsBoundaryInterface,
- activationCallback,
- errorCallback);
}
}
diff --git a/window/window/src/androidTest/java/androidx/window/area/WindowAreaControllerImplTest.kt b/window/window/src/androidTest/java/androidx/window/area/WindowAreaControllerImplTest.kt
index 1dcfeee..d17ea2d 100644
--- a/window/window/src/androidTest/java/androidx/window/area/WindowAreaControllerImplTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/area/WindowAreaControllerImplTest.kt
@@ -319,14 +319,47 @@
}
}
- /**
- * Tests the presentation flow on to a rear facing display works as expected. Similar to
- * [testPresentRearDisplayArea], but starts the presentation with a new instance of
- * [WindowAreaControllerImpl].
- */
@RequiresApi(Build.VERSION_CODES.Q)
@Test
- fun testPresentRearDisplayAreaWithNewController(): Unit =
+ fun testRearDisplayPresentationModeSessionEndedError(): Unit =
+ testScope.runTest {
+ assumeAtLeastVendorApiLevel(minVendorApiLevel)
+ val extensionComponent = FakeWindowAreaComponent()
+ val controller = WindowAreaControllerImpl(windowAreaComponent = extensionComponent)
+
+ extensionComponent.updateRearDisplayStatusListeners(STATUS_AVAILABLE)
+ extensionComponent.updateRearDisplayPresentationStatusListeners(STATUS_UNAVAILABLE)
+ val windowAreaInfo: WindowAreaInfo? =
+ async {
+ return@async controller.windowAreaInfos.first().firstOrNull {
+ it.type == WindowAreaInfo.Type.TYPE_REAR_FACING
+ }
+ }
+ .await()
+
+ assertNotNull(windowAreaInfo)
+ assertTrue {
+ windowAreaInfo.getCapability(OPERATION_PRESENT_ON_AREA).status ==
+ WINDOW_AREA_STATUS_UNAVAILABLE
+ }
+
+ val callback = TestWindowAreaPresentationSessionCallback()
+ activityScenario.scenario.onActivity { testActivity ->
+ controller.presentContentOnWindowArea(
+ windowAreaInfo.token,
+ testActivity,
+ Runnable::run,
+ callback
+ )
+ assert(!callback.sessionActive)
+ assert(callback.sessionError != null)
+ assert(callback.sessionError is IllegalStateException)
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.Q)
+ @Test
+ fun testPresentContentWithNewControllerThrowsException(): Unit =
testScope.runTest {
assumeAtLeastVendorApiLevel(minVendorApiLevel)
val extensions = FakeWindowAreaComponent()
@@ -360,117 +393,7 @@
Runnable::run,
callback
)
- assert(callback.sessionActive)
- assert(!callback.contentVisible)
- callback.presentation?.setContentView(TextView(testActivity))
- assert(callback.contentVisible)
- assert(callback.sessionActive)
-
- callback.presentation?.close()
- assert(!callback.contentVisible)
- assert(!callback.sessionActive)
- }
- }
-
- /**
- * Tests the presentation flow on to a rear facing display works as expected. Similar to
- * [testTransferToRearFacingWindowArea], but starts the presentation with a new instance of
- * [WindowAreaControllerImpl].
- */
- @RequiresApi(Build.VERSION_CODES.Q)
- @Test
- fun testTransferToRearDisplayAreaWithNewController(): Unit =
- testScope.runTest {
- assumeAtLeastVendorApiLevel(minVendorApiLevel)
- val extensions = FakeWindowAreaComponent()
- val controller = WindowAreaControllerImpl(windowAreaComponent = extensions)
- extensions.currentRearDisplayStatus = STATUS_AVAILABLE
- val callback = TestWindowAreaSessionCallback()
- val windowAreaInfo =
- async {
- return@async controller.windowAreaInfos.first().firstOrNull {
- it.type == WindowAreaInfo.Type.TYPE_REAR_FACING
- }
- }
- .await()
-
- assertNotNull(windowAreaInfo)
- assertEquals(
- windowAreaInfo.getCapability(OPERATION_TRANSFER_ACTIVITY_TO_AREA).status,
- WINDOW_AREA_STATUS_AVAILABLE
- )
-
- activityScenario.scenario.onActivity { testActivity ->
- testActivity.resetLayoutCounter()
- testActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
- testActivity.waitForLayout()
- }
-
- // Create a new controller to start the transfer.
- val controller2 = WindowAreaControllerImpl(windowAreaComponent = extensions)
-
- activityScenario.scenario.onActivity { testActivity ->
- assert(
- testActivity.requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
- )
- testActivity.resetLayoutCounter()
- controller2.transferActivityToWindowArea(
- windowAreaInfo.token,
- testActivity,
- Runnable::run,
- callback
- )
- }
-
- activityScenario.scenario.onActivity { testActivity ->
- assert(
- testActivity.requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
- )
- assert(callback.currentSession != null)
- testActivity.resetLayoutCounter()
- callback.endSession()
- }
- activityScenario.scenario.onActivity { testActivity ->
- assert(
- testActivity.requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
- )
- assert(callback.currentSession == null)
- }
- }
-
- @RequiresApi(Build.VERSION_CODES.Q)
- @Test
- fun testRearDisplayPresentationModeSessionEndedError(): Unit =
- testScope.runTest {
- assumeAtLeastVendorApiLevel(minVendorApiLevel)
- val extensionComponent = FakeWindowAreaComponent()
- val controller = WindowAreaControllerImpl(windowAreaComponent = extensionComponent)
-
- extensionComponent.updateRearDisplayStatusListeners(STATUS_AVAILABLE)
- extensionComponent.updateRearDisplayPresentationStatusListeners(STATUS_UNAVAILABLE)
- val windowAreaInfo: WindowAreaInfo? =
- async {
- return@async controller.windowAreaInfos.first().firstOrNull {
- it.type == WindowAreaInfo.Type.TYPE_REAR_FACING
- }
- }
- .await()
-
- assertNotNull(windowAreaInfo)
- assertTrue {
- windowAreaInfo.getCapability(OPERATION_PRESENT_ON_AREA).status ==
- WINDOW_AREA_STATUS_UNAVAILABLE
- }
-
- val callback = TestWindowAreaPresentationSessionCallback()
- activityScenario.scenario.onActivity { testActivity ->
- controller.presentContentOnWindowArea(
- windowAreaInfo.token,
- testActivity,
- Runnable::run,
- callback
- )
assert(!callback.sessionActive)
assert(callback.sessionError != null)
assert(callback.sessionError is IllegalStateException)
diff --git a/window/window/src/main/java/androidx/window/area/WindowAreaController.kt b/window/window/src/main/java/androidx/window/area/WindowAreaController.kt
index 3b88357..04864cc 100644
--- a/window/window/src/main/java/androidx/window/area/WindowAreaController.kt
+++ b/window/window/src/main/java/androidx/window/area/WindowAreaController.kt
@@ -130,37 +130,44 @@
private val TAG = WindowAreaController::class.simpleName
private var decorator: WindowAreaControllerDecorator = EmptyDecorator
+ private var windowAreaController: WindowAreaController? = null
/** Provides an instance of [WindowAreaController]. */
@JvmName("getOrCreate")
@JvmStatic
fun getOrCreate(): WindowAreaController {
- val windowAreaComponentExtensions =
- try {
- this::class.java.classLoader?.let {
- SafeWindowAreaComponentProvider(it).windowAreaComponent
- }
- } catch (t: Throwable) {
- if (BuildConfig.verificationMode == VerificationMode.LOG) {
- Log.d(TAG, "Failed to load WindowExtensions")
- }
- null
- }
+ return if (windowAreaController != null) windowAreaController!!
+ else {
- val deviceSupported =
- Build.VERSION.SDK_INT > Build.VERSION_CODES.Q &&
- windowAreaComponentExtensions != null &&
- ExtensionsUtil.safeVendorApiLevel >= 3
+ val windowAreaComponentExtensions =
+ try {
+ this::class.java.classLoader?.let {
+ SafeWindowAreaComponentProvider(it).windowAreaComponent
+ }
+ } catch (t: Throwable) {
+ if (BuildConfig.verificationMode == VerificationMode.LOG) {
+ Log.d(TAG, "Failed to load WindowExtensions")
+ }
+ null
+ }
- val controller =
- if (deviceSupported) {
- WindowAreaControllerImpl(
- windowAreaComponent = windowAreaComponentExtensions!!,
- )
- } else {
- EmptyWindowAreaControllerImpl()
- }
- return decorator.decorate(controller)
+ val deviceSupported =
+ Build.VERSION.SDK_INT > Build.VERSION_CODES.Q &&
+ windowAreaComponentExtensions != null &&
+ ExtensionsUtil.safeVendorApiLevel >= 3
+
+ val controller =
+ if (deviceSupported) {
+ WindowAreaControllerImpl(
+ windowAreaComponent = windowAreaComponentExtensions!!,
+ )
+ } else {
+ EmptyWindowAreaControllerImpl()
+ }
+ val decoratedController = decorator.decorate(controller)
+ windowAreaController = decoratedController
+ decoratedController
+ }
}
@JvmStatic
diff --git a/window/window/src/main/java/androidx/window/area/WindowAreaControllerImpl.kt b/window/window/src/main/java/androidx/window/area/WindowAreaControllerImpl.kt
index 445cb5d..5e97ca3 100644
--- a/window/window/src/main/java/androidx/window/area/WindowAreaControllerImpl.kt
+++ b/window/window/src/main/java/androidx/window/area/WindowAreaControllerImpl.kt
@@ -41,13 +41,9 @@
import androidx.window.layout.WindowMetricsCalculator
import androidx.window.reflection.Consumer2
import java.util.concurrent.Executor
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.launch
/**
* Implementation of WindowAreaController for devices that do implement the WindowAreaComponent on
@@ -208,18 +204,7 @@
return
}
- if (currentRearDisplayModeStatus == WINDOW_AREA_STATUS_UNKNOWN) {
- Log.d(TAG, "Force updating currentRearDisplayModeStatus")
- // currentRearDisplayModeStatus may be null if the client has not queried
- // WindowAreaController.windowAreaInfos using this instance. In this case, we query
- // it for a single value to force update currentRearDisplayModeStatus.
- CoroutineScope(executor.asCoroutineDispatcher()).launch {
- windowAreaInfos.first()
- startRearDisplayMode(activity, executor, windowAreaSessionCallback)
- }
- } else {
- startRearDisplayMode(activity, executor, windowAreaSessionCallback)
- }
+ startRearDisplayMode(activity, executor, windowAreaSessionCallback)
}
override fun presentContentOnWindowArea(
@@ -237,26 +222,7 @@
return
}
- if (currentRearDisplayPresentationStatus == WINDOW_AREA_STATUS_UNKNOWN) {
- Log.d(TAG, "Force updating currentRearDisplayPresentationStatus")
- // currentRearDisplayModeStatus may be null if the client has not queried
- // WindowAreaController.windowAreaInfos using this instance. In this case, we query
- // it for a single value to force update currentRearDisplayPresentationStatus.
- CoroutineScope(executor.asCoroutineDispatcher()).launch {
- windowAreaInfos.first()
- startRearDisplayPresentationMode(
- activity,
- executor,
- windowAreaPresentationSessionCallback
- )
- }
- } else {
- startRearDisplayPresentationMode(
- activity,
- executor,
- windowAreaPresentationSessionCallback
- )
- }
+ startRearDisplayPresentationMode(activity, executor, windowAreaPresentationSessionCallback)
}
private fun startRearDisplayMode(
diff --git a/xr/compose/material3/material3/api/current.txt b/xr/compose/material3/material3/api/current.txt
index 98fb6c4..0dc4412 100644
--- a/xr/compose/material3/material3/api/current.txt
+++ b/xr/compose/material3/material3/api/current.txt
@@ -39,6 +39,10 @@
property @SuppressCompatibility @androidx.xr.compose.material3.ExperimentalMaterial3XrApi public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.xr.compose.material3.VerticalOrbiterProperties?> LocalNavigationRailOrbiterProperties;
}
+ public final class ThreePaneScaffoldKt {
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.xr.compose.material3.ExperimentalMaterial3XrApi public static void ThreePaneScaffold(androidx.xr.compose.subspace.layout.SubspaceModifier modifier, androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldHorizontalOrder paneOrder, kotlin.jvm.functions.Function0<kotlin.Unit> secondaryPane, optional kotlin.jvm.functions.Function0<kotlin.Unit>? tertiaryPane, kotlin.jvm.functions.Function0<kotlin.Unit> primaryPane);
+ }
+
@SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.xr.compose.material3.ExperimentalMaterial3XrApi public final class VerticalOrbiterProperties {
ctor public VerticalOrbiterProperties(androidx.xr.compose.spatial.EdgeOffset offset, int position, androidx.compose.ui.Alignment.Vertical alignment, androidx.xr.compose.spatial.OrbiterSettings settings, androidx.xr.compose.subspace.layout.SpatialShape shape);
method public androidx.xr.compose.material3.VerticalOrbiterProperties copy(optional androidx.xr.compose.spatial.EdgeOffset? offset, optional androidx.xr.compose.spatial.OrbiterEdge.Vertical? position, optional androidx.compose.ui.Alignment.Vertical? alignment, optional androidx.xr.compose.spatial.OrbiterSettings? settings, optional androidx.xr.compose.subspace.layout.SpatialShape? shape);
diff --git a/xr/compose/material3/material3/api/restricted_current.txt b/xr/compose/material3/material3/api/restricted_current.txt
index 98fb6c4..0dc4412 100644
--- a/xr/compose/material3/material3/api/restricted_current.txt
+++ b/xr/compose/material3/material3/api/restricted_current.txt
@@ -39,6 +39,10 @@
property @SuppressCompatibility @androidx.xr.compose.material3.ExperimentalMaterial3XrApi public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.xr.compose.material3.VerticalOrbiterProperties?> LocalNavigationRailOrbiterProperties;
}
+ public final class ThreePaneScaffoldKt {
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.xr.compose.material3.ExperimentalMaterial3XrApi public static void ThreePaneScaffold(androidx.xr.compose.subspace.layout.SubspaceModifier modifier, androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldHorizontalOrder paneOrder, kotlin.jvm.functions.Function0<kotlin.Unit> secondaryPane, optional kotlin.jvm.functions.Function0<kotlin.Unit>? tertiaryPane, kotlin.jvm.functions.Function0<kotlin.Unit> primaryPane);
+ }
+
@SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.xr.compose.material3.ExperimentalMaterial3XrApi public final class VerticalOrbiterProperties {
ctor public VerticalOrbiterProperties(androidx.xr.compose.spatial.EdgeOffset offset, int position, androidx.compose.ui.Alignment.Vertical alignment, androidx.xr.compose.spatial.OrbiterSettings settings, androidx.xr.compose.subspace.layout.SpatialShape shape);
method public androidx.xr.compose.material3.VerticalOrbiterProperties copy(optional androidx.xr.compose.spatial.EdgeOffset? offset, optional androidx.xr.compose.spatial.OrbiterEdge.Vertical? position, optional androidx.compose.ui.Alignment.Vertical? alignment, optional androidx.xr.compose.spatial.OrbiterSettings? settings, optional androidx.xr.compose.subspace.layout.SpatialShape? shape);
diff --git a/xr/compose/material3/material3/build.gradle b/xr/compose/material3/material3/build.gradle
index abb0d4c..0cad526 100644
--- a/xr/compose/material3/material3/build.gradle
+++ b/xr/compose/material3/material3/build.gradle
@@ -36,6 +36,7 @@
dependencies {
api(libs.kotlinStdlib)
// Add dependencies here
+ implementation(project(":compose:ui:ui-unit"))
implementation(project(":compose:material3:adaptive:adaptive"))
implementation(project(":compose:material3:adaptive:adaptive-layout"))
implementation(project(":compose:material3:material3"))
diff --git a/xr/compose/material3/material3/src/main/java/androidx/xr/compose/material3/EnableXrComponentOverrides.kt b/xr/compose/material3/material3/src/main/java/androidx/xr/compose/material3/EnableXrComponentOverrides.kt
index 6862955..4411de4 100644
--- a/xr/compose/material3/material3/src/main/java/androidx/xr/compose/material3/EnableXrComponentOverrides.kt
+++ b/xr/compose/material3/material3/src/main/java/androidx/xr/compose/material3/EnableXrComponentOverrides.kt
@@ -21,6 +21,7 @@
import androidx.compose.material3.LocalNavigationRailComponentOverride
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveComponentOverrideApi
import androidx.compose.material3.adaptive.layout.LocalAnimatedPaneOverride
+import androidx.compose.material3.adaptive.layout.LocalThreePaneScaffoldOverride
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ProvidedValue
@@ -61,6 +62,7 @@
)
}
if (context.shouldOverrideComponent(XrComponentOverride.ThreePaneScaffold)) {
+ add(LocalThreePaneScaffoldOverride provides XrThreePaneScaffoldOverride)
add(LocalAnimatedPaneOverride provides XrAnimatedPaneOverride)
}
}
@@ -127,5 +129,10 @@
@Composable
override fun XrComponentOverrideEnablerContext.shouldOverrideComponent(
component: XrComponentOverride
- ): Boolean = isSpatializationEnabled
+ ): Boolean =
+ when (component) {
+ // TODO(b/388825260): Allow enabling ThreePaneScaffold once all edge-cases are fixed
+ XrComponentOverride.ThreePaneScaffold -> false
+ else -> isSpatializationEnabled
+ }
}
diff --git a/xr/compose/material3/material3/src/main/java/androidx/xr/compose/material3/ThreePaneScaffold.kt b/xr/compose/material3/material3/src/main/java/androidx/xr/compose/material3/ThreePaneScaffold.kt
new file mode 100644
index 0000000..ca1ae92
--- /dev/null
+++ b/xr/compose/material3/material3/src/main/java/androidx/xr/compose/material3/ThreePaneScaffold.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2024 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.xr.compose.material3
+
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveComponentOverrideApi
+import androidx.compose.material3.adaptive.layout.PaneScaffoldDirective
+import androidx.compose.material3.adaptive.layout.PaneScaffoldParentData
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldHorizontalOrder
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldOverride
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldOverrideContext
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.xr.compose.spatial.Subspace
+import androidx.xr.compose.subspace.SpatialLayoutSpacer
+import androidx.xr.compose.subspace.SpatialPanel
+import androidx.xr.compose.subspace.SpatialRow
+import androidx.xr.compose.subspace.layout.SubspaceModifier
+import androidx.xr.compose.subspace.layout.fillMaxHeight
+import androidx.xr.compose.subspace.layout.height
+import androidx.xr.compose.subspace.layout.width
+import kotlin.math.roundToInt
+
+/**
+ * A pane scaffold composable that can display up to three panes in the order that
+ * [ThreePaneScaffoldHorizontalOrder] specifies, and allocate margins and spacers according to
+ * [PaneScaffoldDirective].
+ *
+ * [ThreePaneScaffold] is the base composable functions of adaptive programming. Developers can
+ * freely pipeline the relevant adaptive signals and use them as input of the scaffold function to
+ * render the final adaptive layout.
+ *
+ * @param modifier The modifier to be applied to the layout.
+ * @param scaffoldDirective The top-level directives about how the scaffold should arrange its
+ * panes.
+ * @param paneOrder The horizontal order of the panes from start to end in the scaffold.
+ * @param secondaryPane The content of the secondary pane that has a priority lower then the primary
+ * pane but higher than the tertiary pane.
+ * @param tertiaryPane The content of the tertiary pane that has the lowest priority.
+ * @param primaryPane The content of the primary pane that has the highest priority.
+ */
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@ExperimentalMaterial3XrApi
+@Composable
+public fun ThreePaneScaffold(
+ modifier: SubspaceModifier,
+ scaffoldDirective: PaneScaffoldDirective,
+ paneOrder: ThreePaneScaffoldHorizontalOrder,
+ secondaryPane: @Composable () -> Unit,
+ tertiaryPane: (@Composable () -> Unit)? = null,
+ primaryPane: @Composable () -> Unit
+) {
+ Subspace {
+ SpatialRow(modifier = modifier.height(XrThreePaneScaffoldTokens.PanelHeight)) {
+ var drawSpacer = false // Only draws spacers after the first pane is drawn
+ paneOrder.each { role ->
+ when (role) {
+ ThreePaneScaffoldRole.Primary -> {
+ Panel(
+ scaffoldDirective,
+ XrThreePaneScaffoldTokens.PrimaryPanePanelWidth,
+ drawSpacer,
+ primaryPane
+ )
+ drawSpacer = true
+ }
+ ThreePaneScaffoldRole.Secondary -> {
+ Panel(
+ scaffoldDirective,
+ XrThreePaneScaffoldTokens.SecondaryPanePanelWidth,
+ drawSpacer,
+ secondaryPane
+ )
+ drawSpacer = true
+ }
+ ThreePaneScaffoldRole.Tertiary ->
+ if (tertiaryPane != null) {
+ Panel(
+ scaffoldDirective,
+ XrThreePaneScaffoldTokens.TertiaryPanePanelWidth,
+ drawSpacer,
+ tertiaryPane
+ )
+ drawSpacer = true
+ }
+ }
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@Composable
+private fun Panel(
+ scaffoldDirective: PaneScaffoldDirective,
+ defaultPreferredWidth: Dp,
+ drawSpacer: Boolean,
+ content: @Composable () -> Unit
+) {
+ if (drawSpacer) {
+ SpatialLayoutSpacer(SubspaceModifier.width(scaffoldDirective.horizontalPartitionSpacerSize))
+ }
+
+ SpatialPanel(SubspaceModifier.width(defaultPreferredWidth).fillMaxHeight()) {
+ Layout(content) { measurables, constraints ->
+ val measurable = measurables[0]
+ val parentData = measurable.parentData as? PaneScaffoldParentData
+ val widthFloat = parentData?.preferredWidth ?: defaultPreferredWidth
+ val width = widthFloat.toPx().roundToInt()
+ val height = constraints.maxHeight
+ return@Layout layout(width, height) {
+ measurable.measure(Constraints.fixed(width, height)).place(0, 0)
+ }
+ }
+ }
+}
+
+/**
+ * [ThreePaneScaffoldOverride] that uses the XR-specific [ThreePaneScaffold].
+ *
+ * Note that when using this override, any madifiers passed in to the 2D composable are ignored.
+ */
+@ExperimentalMaterial3XrApi
+@OptIn(
+ ExperimentalMaterial3AdaptiveApi::class,
+ ExperimentalMaterial3AdaptiveComponentOverrideApi::class
+)
+internal object XrThreePaneScaffoldOverride : ThreePaneScaffoldOverride {
+ @Composable
+ override fun ThreePaneScaffoldOverrideContext.ThreePaneScaffold() {
+ ThreePaneScaffold(
+ modifier = SubspaceModifier,
+ scaffoldDirective = scaffoldDirective.copy(maxHorizontalPartitions = 3),
+ paneOrder = paneOrder,
+ primaryPane = primaryPane,
+ secondaryPane = secondaryPane,
+ tertiaryPane = tertiaryPane,
+ )
+ }
+}
+
+// TODO(conradchen): Confirm the values with design
+private object XrThreePaneScaffoldTokens {
+ val PanelHeight = 1024.dp
+ val PrimaryPanePanelWidth = 800.dp
+ val SecondaryPanePanelWidth = 412.dp
+ val TertiaryPanePanelWidth = 412.dp
+}
+
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private inline fun ThreePaneScaffoldHorizontalOrder.each(action: (ThreePaneScaffoldRole) -> Unit) {
+ action(get(0))
+ action(get(1))
+ action(get(2))
+}