diff --git a/biometric/biometric/src/main/res/values-bs/strings.xml b/biometric/biometric/src/main/res/values-bs/strings.xml
index ecce54149002f..6c980671bb819 100644
--- a/biometric/biometric/src/main/res/values-bs/strings.xml
+++ b/biometric/biometric/src/main/res/values-bs/strings.xml
@@ -35,7 +35,7 @@
"Koristi otisak prsta"
"Koristi otključavanje licem"
"Koristi biometriju"
- "Koristi zaključavanje ekrana"
+ "Koristi otključavanje ekrana"
"Koristi otisak prsta ili zaključavanje ekrana"
"Koristi otključavanje licem ili zaključavanje ekrana"
"Koristi biometriju ili zaključavanje ekrana"
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/studio/StudioTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/studio/StudioTask.kt
index b44d0f7b27eaf..03d618e9344f7 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/studio/StudioTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/studio/StudioTask.kt
@@ -376,9 +376,9 @@ abstract class StudioTask : DefaultTask() {
) {
val url =
if (filename.contains("-mac")) {
- "https://redirector.gvt1.com/edgedl/android/studio/install/$studioVersion/$filename"
+ "https://edgedl.me.gvt1.com/android/studio/install/$studioVersion/$filename"
} else {
- "https://redirector.gvt1.com/edgedl/android/studio/ide-zips/$studioVersion/$filename"
+ "https://edgedl.me.gvt1.com/android/studio/ide-zips/$studioVersion/$filename"
}
val tmpDownloadPath = File("$destinationPath.tmp").absolutePath
println("Downloading $url to $tmpDownloadPath")
diff --git a/compose/integration-tests/hero/sysui/sysui-macrobenchmark/src/main/kotlin/androidx/compose/integration/hero/sysui/macrobenchmark/SysUiSceneTransitionLayoutHeroBenchmark.kt b/compose/integration-tests/hero/sysui/sysui-macrobenchmark/src/main/kotlin/androidx/compose/integration/hero/sysui/macrobenchmark/SysUiSceneTransitionLayoutHeroBenchmark.kt
index 47c88f49aa23b..ee70c18152e12 100644
--- a/compose/integration-tests/hero/sysui/sysui-macrobenchmark/src/main/kotlin/androidx/compose/integration/hero/sysui/macrobenchmark/SysUiSceneTransitionLayoutHeroBenchmark.kt
+++ b/compose/integration-tests/hero/sysui/sysui-macrobenchmark/src/main/kotlin/androidx/compose/integration/hero/sysui/macrobenchmark/SysUiSceneTransitionLayoutHeroBenchmark.kt
@@ -93,7 +93,7 @@ class SysUiSceneTransitionLayoutHeroBenchmark(private val compilationMode: Compi
}
companion object {
- const val ITERATIONS = 25
+ const val ITERATIONS = 10
@Parameterized.Parameters(name = "compilation={0}")
@JvmStatic
diff --git a/compose/integration-tests/hero/sysui/sysui-macrobenchmark/src/main/kotlin/androidx/compose/integration/hero/sysui/macrobenchmark/SysUiSceneTransitionLayoutStartupHeroBenchmark.kt b/compose/integration-tests/hero/sysui/sysui-macrobenchmark/src/main/kotlin/androidx/compose/integration/hero/sysui/macrobenchmark/SysUiSceneTransitionLayoutStartupHeroBenchmark.kt
index 0070326bc60ba..9f455488bc6a6 100644
--- a/compose/integration-tests/hero/sysui/sysui-macrobenchmark/src/main/kotlin/androidx/compose/integration/hero/sysui/macrobenchmark/SysUiSceneTransitionLayoutStartupHeroBenchmark.kt
+++ b/compose/integration-tests/hero/sysui/sysui-macrobenchmark/src/main/kotlin/androidx/compose/integration/hero/sysui/macrobenchmark/SysUiSceneTransitionLayoutStartupHeroBenchmark.kt
@@ -47,7 +47,7 @@ class SysUiSceneTransitionLayoutStartupHeroBenchmark(
// elements for the quick settings but also nested SceneTransitionLayouts for each
// notification.
sysuiHeroBenchmarkScope()
- .startDemoActivity(StlDemoConstants.SHADE_SCENE, notificationsInShade = 300)
+ .startDemoActivity(StlDemoConstants.SHADE_SCENE, notificationsInShade = 10)
}
}
diff --git a/compose/integration-tests/macrobenchmark-target/OWNERS b/compose/integration-tests/macrobenchmark-target/OWNERS
index ef0fe20041317..30c81f15cc38f 100644
--- a/compose/integration-tests/macrobenchmark-target/OWNERS
+++ b/compose/integration-tests/macrobenchmark-target/OWNERS
@@ -1,4 +1,5 @@
# Bug component: 378604
-andreykulikov@google.com
bentrengrove@google.com
-ccraik@google.com
\ No newline at end of file
+ccraik@google.com
+ashikov@google.com
+jossiwolf@google.com
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/PagerOfLazyGridActivity.kt b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/PagerOfLazyGridActivity.kt
index 0cf8ae29c6098..7b578d82efc7c 100644
--- a/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/PagerOfLazyGridActivity.kt
+++ b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/PagerOfLazyGridActivity.kt
@@ -21,7 +21,10 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.safeContent
+import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.pager.HorizontalPager
@@ -61,7 +64,12 @@ private fun HorizontalPagerOfLazyGrid(pages: Int = 100, gridItems: Int = 100) {
val pagerState: PagerState = rememberPagerState(initialPage = 1) { pages }
val coroutineScope = rememberCoroutineScope()
- Column(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background)) {
+ Column(
+ modifier =
+ Modifier.fillMaxSize()
+ .background(MaterialTheme.colors.background)
+ .windowInsetsPadding(WindowInsets.safeContent)
+ ) {
Button(
onClick = {
coroutineScope.launch { pagerState.animateScrollToPage(pagerState.currentPage + 1) }
diff --git a/compose/material3/material3/api/current.ignore b/compose/material3/material3/api/current.ignore
index d5e349a94e1d2..978db3c0d64d1 100644
--- a/compose/material3/material3/api/current.ignore
+++ b/compose/material3/material3/api/current.ignore
@@ -1075,3 +1075,7 @@ RemovedMethod: androidx.compose.material3.pulltorefresh.PullToRefreshDefaults#In
Binary breaking change: Removed method androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.IndicatorBox(androidx.compose.material3.pulltorefresh.PullToRefreshState,boolean,androidx.compose.ui.Modifier,float,androidx.compose.ui.graphics.Shape,long,float,kotlin.jvm.functions.Function1 super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>)
RemovedMethod: androidx.compose.material3.pulltorefresh.PullToRefreshKt#pullToRefresh(androidx.compose.ui.Modifier, boolean, androidx.compose.material3.pulltorefresh.PullToRefreshState, boolean, float, kotlin.jvm.functions.Function0):
Binary breaking change: Removed method androidx.compose.material3.pulltorefresh.PullToRefreshKt.pullToRefresh(androidx.compose.ui.Modifier,boolean,androidx.compose.material3.pulltorefresh.PullToRefreshState,boolean,float,kotlin.jvm.functions.Function0)
+
+
+RemovedProperty: androidx.compose.material3.AppBarMenuState#isExpanded:
+ Source breaking change: Removed property androidx.compose.material3.AppBarMenuState#isExpanded
diff --git a/compose/material3/material3/api/restricted_current.ignore b/compose/material3/material3/api/restricted_current.ignore
index d5e349a94e1d2..978db3c0d64d1 100644
--- a/compose/material3/material3/api/restricted_current.ignore
+++ b/compose/material3/material3/api/restricted_current.ignore
@@ -1075,3 +1075,7 @@ RemovedMethod: androidx.compose.material3.pulltorefresh.PullToRefreshDefaults#In
Binary breaking change: Removed method androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.IndicatorBox(androidx.compose.material3.pulltorefresh.PullToRefreshState,boolean,androidx.compose.ui.Modifier,float,androidx.compose.ui.graphics.Shape,long,float,kotlin.jvm.functions.Function1 super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>)
RemovedMethod: androidx.compose.material3.pulltorefresh.PullToRefreshKt#pullToRefresh(androidx.compose.ui.Modifier, boolean, androidx.compose.material3.pulltorefresh.PullToRefreshState, boolean, float, kotlin.jvm.functions.Function0):
Binary breaking change: Removed method androidx.compose.material3.pulltorefresh.PullToRefreshKt.pullToRefresh(androidx.compose.ui.Modifier,boolean,androidx.compose.material3.pulltorefresh.PullToRefreshState,boolean,float,kotlin.jvm.functions.Function0)
+
+
+RemovedProperty: androidx.compose.material3.AppBarMenuState#isExpanded:
+ Source breaking change: Removed property androidx.compose.material3.AppBarMenuState#isExpanded
diff --git a/compose/runtime/runtime/api/current.ignore b/compose/runtime/runtime/api/current.ignore
new file mode 100644
index 0000000000000..2d0b69d6d95bd
--- /dev/null
+++ b/compose/runtime/runtime/api/current.ignore
@@ -0,0 +1,5 @@
+// Baseline format: 1.0
+ChangedAnnotationRetention: androidx.compose.runtime.internal.FunctionKeyMeta:
+ Class androidx.compose.runtime.internal.FunctionKeyMeta incompatibly changed its retention from RUNTIME to BINARY
+ChangedAnnotationRetention: androidx.compose.runtime.internal.FunctionKeyMeta.Container:
+ Class androidx.compose.runtime.internal.FunctionKeyMeta.Container incompatibly changed its retention from RUNTIME to BINARY
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index bca0409eb8960..e23f7bddd6eee 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -910,7 +910,7 @@ package androidx.compose.runtime.internal {
method @androidx.compose.runtime.ComposeCompilerApi public static Void illegalDecoyCallException(String fName);
}
- @androidx.compose.runtime.ComposeCompilerApi @kotlin.annotation.Repeatable @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.RUNTIME) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION}) public @interface FunctionKeyMeta {
+ @androidx.compose.runtime.ComposeCompilerApi @kotlin.annotation.Repeatable @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION}) public @interface FunctionKeyMeta {
ctor @KotlinOnly public FunctionKeyMeta(int key, int startOffset, int endOffset);
method @InaccessibleFromKotlin public abstract int endOffset();
method @InaccessibleFromKotlin public abstract int key();
@@ -920,7 +920,7 @@ package androidx.compose.runtime.internal {
property public abstract int startOffset;
}
- @androidx.compose.runtime.ComposeCompilerApi @kotlin.annotation.Repeatable @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.RUNTIME) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION}) public static @interface FunctionKeyMeta.Container {
+ @androidx.compose.runtime.ComposeCompilerApi @kotlin.annotation.Repeatable @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION}) public static @interface FunctionKeyMeta.Container {
method public abstract androidx.compose.runtime.internal.FunctionKeyMeta[] value();
}
diff --git a/compose/runtime/runtime/api/restricted_current.ignore b/compose/runtime/runtime/api/restricted_current.ignore
new file mode 100644
index 0000000000000..2d0b69d6d95bd
--- /dev/null
+++ b/compose/runtime/runtime/api/restricted_current.ignore
@@ -0,0 +1,5 @@
+// Baseline format: 1.0
+ChangedAnnotationRetention: androidx.compose.runtime.internal.FunctionKeyMeta:
+ Class androidx.compose.runtime.internal.FunctionKeyMeta incompatibly changed its retention from RUNTIME to BINARY
+ChangedAnnotationRetention: androidx.compose.runtime.internal.FunctionKeyMeta.Container:
+ Class androidx.compose.runtime.internal.FunctionKeyMeta.Container incompatibly changed its retention from RUNTIME to BINARY
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index ac8b011dbe5e8..c5675a504247e 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -965,7 +965,7 @@ package androidx.compose.runtime.internal {
method @androidx.compose.runtime.ComposeCompilerApi public static Void illegalDecoyCallException(String fName);
}
- @androidx.compose.runtime.ComposeCompilerApi @kotlin.annotation.Repeatable @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.RUNTIME) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION}) public @interface FunctionKeyMeta {
+ @androidx.compose.runtime.ComposeCompilerApi @kotlin.annotation.Repeatable @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION}) public @interface FunctionKeyMeta {
ctor @KotlinOnly public FunctionKeyMeta(int key, int startOffset, int endOffset);
method @InaccessibleFromKotlin public abstract int endOffset();
method @InaccessibleFromKotlin public abstract int key();
@@ -975,7 +975,7 @@ package androidx.compose.runtime.internal {
property public abstract int startOffset;
}
- @androidx.compose.runtime.ComposeCompilerApi @kotlin.annotation.Repeatable @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.RUNTIME) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION}) public static @interface FunctionKeyMeta.Container {
+ @androidx.compose.runtime.ComposeCompilerApi @kotlin.annotation.Repeatable @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION}) public static @interface FunctionKeyMeta.Container {
method public abstract androidx.compose.runtime.internal.FunctionKeyMeta[] value();
}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/FunctionKeyMeta.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/FunctionKeyMeta.kt
index 221cd36fecb96..b61cc22409c90 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/FunctionKeyMeta.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/FunctionKeyMeta.kt
@@ -30,7 +30,7 @@ import androidx.compose.runtime.ComposeCompilerApi
*/
@ComposeCompilerApi
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
-@Retention(AnnotationRetention.RUNTIME)
+@Retention(AnnotationRetention.BINARY)
@Repeatable
public annotation class FunctionKeyMeta(val key: Int, val startOffset: Int, val endOffset: Int)
diff --git a/constraintlayout/constraintlayout-compose/api/current.ignore b/constraintlayout/constraintlayout-compose/api/current.ignore
index bdeea458bd925..45e3dcfecc984 100644
--- a/constraintlayout/constraintlayout-compose/api/current.ignore
+++ b/constraintlayout/constraintlayout-compose/api/current.ignore
@@ -389,3 +389,7 @@ RemovedMethod: androidx.constraintlayout.compose.State#setRootIncomingConstraint
Binary breaking change: Removed method androidx.constraintlayout.compose.State.setRootIncomingConstraints(long)
RemovedMethod: androidx.constraintlayout.compose.VerticalAnchorable#linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor, float, float):
Binary breaking change: Removed method androidx.constraintlayout.compose.VerticalAnchorable.linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor,float,float)
+
+
+RemovedProperty: androidx.constraintlayout.compose.ConstrainedLayoutReference#id:
+ Source breaking change: Removed property androidx.constraintlayout.compose.ConstrainedLayoutReference#id
diff --git a/constraintlayout/constraintlayout-compose/api/restricted_current.ignore b/constraintlayout/constraintlayout-compose/api/restricted_current.ignore
index 06741ae384cdc..d1ee419dae853 100644
--- a/constraintlayout/constraintlayout-compose/api/restricted_current.ignore
+++ b/constraintlayout/constraintlayout-compose/api/restricted_current.ignore
@@ -435,3 +435,9 @@ RemovedMethod: androidx.constraintlayout.compose.State#setRootIncomingConstraint
Binary breaking change: Removed method androidx.constraintlayout.compose.State.setRootIncomingConstraints(long)
RemovedMethod: androidx.constraintlayout.compose.VerticalAnchorable#linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor, float, float):
Binary breaking change: Removed method androidx.constraintlayout.compose.VerticalAnchorable.linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor,float,float)
+
+
+RemovedProperty: androidx.constraintlayout.compose.ConstrainedLayoutReference#id:
+ Source breaking change: Removed property androidx.constraintlayout.compose.ConstrainedLayoutReference#id
+RemovedProperty: androidx.constraintlayout.compose.ToolingUtilsKt#designInfoProvider:
+ Source breaking change: Removed property androidx.constraintlayout.compose.ToolingUtilsKt#designInfoProvider
diff --git a/graphics/graphics-core/api/current.ignore b/graphics/graphics-core/api/current.ignore
index 7014c649214d1..159958725e878 100644
--- a/graphics/graphics-core/api/current.ignore
+++ b/graphics/graphics-core/api/current.ignore
@@ -53,6 +53,168 @@ AddedMethod: androidx.graphics.surface.SurfaceControlCompat.Transaction#setBuffe
Added method androidx.graphics.surface.SurfaceControlCompat.Transaction.setBuffer$default(androidx.graphics.surface.SurfaceControlCompat.Transaction,androidx.graphics.surface.SurfaceControlCompat,android.hardware.HardwareBuffer,androidx.hardware.SyncFenceCompat,kotlin.jvm.functions.Function1,int,Object)
+AddedProperty: androidx.graphics.CanvasBufferedRenderer.RenderResult.Companion#ERROR_UNKNOWN:
+ Added property androidx.graphics.CanvasBufferedRenderer.RenderResult.Companion#ERROR_UNKNOWN
+AddedProperty: androidx.graphics.CanvasBufferedRenderer.RenderResult.Companion#SUCCESS:
+ Added property androidx.graphics.CanvasBufferedRenderer.RenderResult.Companion#SUCCESS
+AddedProperty: androidx.graphics.opengl.SyncStrategy.Companion#ALWAYS:
+ Added property androidx.graphics.opengl.SyncStrategy.Companion#ALWAYS
+AddedProperty: androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#EGL_COLOR_COMPONENT_TYPE_EXT:
+ Added property androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#EGL_COLOR_COMPONENT_TYPE_EXT
+AddedProperty: androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#EGL_COLOR_COMPONENT_TYPE_FIXED_EXT:
+ Added property androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#EGL_COLOR_COMPONENT_TYPE_FIXED_EXT
+AddedProperty: androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT:
+ Added property androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT
+AddedProperty: androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#RGBA_1010102:
+ Added property androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#RGBA_1010102
+AddedProperty: androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#RGBA_8888:
+ Added property androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#RGBA_8888
+AddedProperty: androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#RGBA_F16:
+ Added property androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#RGBA_F16
+AddedProperty: androidx.graphics.opengl.egl.EGLSpec.Companion#V14:
+ Added property androidx.graphics.opengl.egl.EGLSpec.Companion#V14
+AddedProperty: androidx.graphics.opengl.egl.EGLVersion.Companion#Unknown:
+ Added property androidx.graphics.opengl.egl.EGLVersion.Companion#Unknown
+AddedProperty: androidx.graphics.opengl.egl.EGLVersion.Companion#V14:
+ Added property androidx.graphics.opengl.egl.EGLVersion.Companion#V14
+AddedProperty: androidx.graphics.opengl.egl.EGLVersion.Companion#V15:
+ Added property androidx.graphics.opengl.egl.EGLVersion.Companion#V15
+AddedProperty: androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_IDENTITY:
+ Added property androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_IDENTITY
+AddedProperty: androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_MIRROR_HORIZONTAL:
+ Added property androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_MIRROR_HORIZONTAL
+AddedProperty: androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_MIRROR_VERTICAL:
+ Added property androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_MIRROR_VERTICAL
+AddedProperty: androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_ROTATE_180:
+ Added property androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_ROTATE_180
+AddedProperty: androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_ROTATE_270:
+ Added property androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_ROTATE_270
+AddedProperty: androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_ROTATE_90:
+ Added property androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_ROTATE_90
+AddedProperty: androidx.graphics.surface.SurfaceControlCompat.Companion#CHANGE_FRAME_RATE_ALWAYS:
+ Added property androidx.graphics.surface.SurfaceControlCompat.Companion#CHANGE_FRAME_RATE_ALWAYS
+AddedProperty: androidx.graphics.surface.SurfaceControlCompat.Companion#CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS:
+ Added property androidx.graphics.surface.SurfaceControlCompat.Companion#CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
+AddedProperty: androidx.graphics.surface.SurfaceControlCompat.Companion#FRAME_RATE_COMPATIBILITY_DEFAULT:
+ Added property androidx.graphics.surface.SurfaceControlCompat.Companion#FRAME_RATE_COMPATIBILITY_DEFAULT
+AddedProperty: androidx.graphics.surface.SurfaceControlCompat.Companion#FRAME_RATE_COMPATIBILITY_FIXED_SOURCE:
+ Added property androidx.graphics.surface.SurfaceControlCompat.Companion#FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_ADOBE_RGB:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_ADOBE_RGB
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_BT2020:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_BT2020
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_BT2020_HLG:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_BT2020_HLG
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_BT2020_PQ:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_BT2020_PQ
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_BT601_525:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_BT601_525
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_BT601_625:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_BT601_625
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_BT709:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_BT709
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_DCI_P3:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_DCI_P3
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_DEPTH:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_DEPTH
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_DISPLAY_P3:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_DISPLAY_P3
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_DYNAMIC_DEPTH:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_DYNAMIC_DEPTH
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_HEIF:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_HEIF
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_JFIF:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_JFIF
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_JPEG_R:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_JPEG_R
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_SCRGB:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_SCRGB
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_SCRGB_LINEAR:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_SCRGB_LINEAR
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_SRGB:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_SRGB
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_SRGB_LINEAR:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_SRGB_LINEAR
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_UNKNOWN:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_UNKNOWN
+AddedProperty: androidx.hardware.DataSpace.Companion#RANGE_EXTENDED:
+ Added property androidx.hardware.DataSpace.Companion#RANGE_EXTENDED
+AddedProperty: androidx.hardware.DataSpace.Companion#RANGE_FULL:
+ Added property androidx.hardware.DataSpace.Companion#RANGE_FULL
+AddedProperty: androidx.hardware.DataSpace.Companion#RANGE_LIMITED:
+ Added property androidx.hardware.DataSpace.Companion#RANGE_LIMITED
+AddedProperty: androidx.hardware.DataSpace.Companion#RANGE_UNSPECIFIED:
+ Added property androidx.hardware.DataSpace.Companion#RANGE_UNSPECIFIED
+AddedProperty: androidx.hardware.SyncFenceCompat.Companion#SIGNAL_TIME_INVALID:
+ Added property androidx.hardware.SyncFenceCompat.Companion#SIGNAL_TIME_INVALID
+AddedProperty: androidx.hardware.SyncFenceCompat.Companion#SIGNAL_TIME_PENDING:
+ Added property androidx.hardware.SyncFenceCompat.Companion#SIGNAL_TIME_PENDING
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_ANDROID_CLIENT_BUFFER:
+ Added property androidx.opengl.EGLExt.Companion#EGL_ANDROID_CLIENT_BUFFER
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_ANDROID_IMAGE_NATIVE_BUFFER:
+ Added property androidx.opengl.EGLExt.Companion#EGL_ANDROID_IMAGE_NATIVE_BUFFER
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_ANDROID_NATIVE_FENCE_SYNC:
+ Added property androidx.opengl.EGLExt.Companion#EGL_ANDROID_NATIVE_FENCE_SYNC
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_CONDITION_SATISFIED_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_CONDITION_SATISFIED_KHR
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_EXT_BUFFER_AGE:
+ Added property androidx.opengl.EGLExt.Companion#EGL_EXT_BUFFER_AGE
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_EXT_GL_COLORSPACE_BT2020_PQ:
+ Added property androidx.opengl.EGLExt.Companion#EGL_EXT_GL_COLORSPACE_BT2020_PQ
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH:
+ Added property androidx.opengl.EGLExt.Companion#EGL_EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_EXT_GL_COLORSPACE_SCRGB:
+ Added property androidx.opengl.EGLExt.Companion#EGL_EXT_GL_COLORSPACE_SCRGB
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_EXT_PIXEL_FORMAT_FLOAT:
+ Added property androidx.opengl.EGLExt.Companion#EGL_EXT_PIXEL_FORMAT_FLOAT
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_FALSE:
+ Added property androidx.opengl.EGLExt.Companion#EGL_FALSE
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_FOREVER_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_FOREVER_KHR
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_IMG_CONTEXT_PRIORITY:
+ Added property androidx.opengl.EGLExt.Companion#EGL_IMG_CONTEXT_PRIORITY
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_KHR_FENCE_SYNC:
+ Added property androidx.opengl.EGLExt.Companion#EGL_KHR_FENCE_SYNC
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_KHR_GL_COLORSPACE:
+ Added property androidx.opengl.EGLExt.Companion#EGL_KHR_GL_COLORSPACE
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_KHR_IMAGE:
+ Added property androidx.opengl.EGLExt.Companion#EGL_KHR_IMAGE
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_KHR_IMAGE_BASE:
+ Added property androidx.opengl.EGLExt.Companion#EGL_KHR_IMAGE_BASE
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_KHR_NO_CONFIG_CONTEXT:
+ Added property androidx.opengl.EGLExt.Companion#EGL_KHR_NO_CONFIG_CONTEXT
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_KHR_PARTIAL_UPDATE:
+ Added property androidx.opengl.EGLExt.Companion#EGL_KHR_PARTIAL_UPDATE
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_KHR_SURFACELESS_CONTEXT:
+ Added property androidx.opengl.EGLExt.Companion#EGL_KHR_SURFACELESS_CONTEXT
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_KHR_SWAP_BUFFERS_WITH_DAMAGE:
+ Added property androidx.opengl.EGLExt.Companion#EGL_KHR_SWAP_BUFFERS_WITH_DAMAGE
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_KHR_WAIT_SYNC:
+ Added property androidx.opengl.EGLExt.Companion#EGL_KHR_WAIT_SYNC
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_SIGNALED_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_SIGNALED_KHR
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_SYNC_CONDITION_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_SYNC_CONDITION_KHR
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_SYNC_FENCE_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_SYNC_FENCE_KHR
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_SYNC_FLUSH_COMMANDS_BIT_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_SYNC_FLUSH_COMMANDS_BIT_KHR
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_SYNC_NATIVE_FENCE_ANDROID:
+ Added property androidx.opengl.EGLExt.Companion#EGL_SYNC_NATIVE_FENCE_ANDROID
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_SYNC_PRIOR_COMMANDS_COMPLETE_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_SYNC_PRIOR_COMMANDS_COMPLETE_KHR
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_SYNC_STATUS_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_SYNC_STATUS_KHR
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_SYNC_TYPE_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_SYNC_TYPE_KHR
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_TIMEOUT_EXPIRED_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_TIMEOUT_EXPIRED_KHR
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_TRUE:
+ Added property androidx.opengl.EGLExt.Companion#EGL_TRUE
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_UNSIGNALED_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_UNSIGNALED_KHR
+
+
RemovedFromJava: androidx.graphics.opengl.egl.EGLConfigAttributes.Builder#to(int, int):
Source breaking change: method androidx.graphics.opengl.egl.EGLConfigAttributes.Builder.to(int,int) can no longer be resolved from Java source
RemovedFromJava: androidx.graphics.opengl.egl.EGLConfigAttributesKt#EGLConfigAttributes(kotlin.jvm.functions.Function1 super androidx.graphics.opengl.egl.EGLConfigAttributes.Builder,kotlin.Unit>):
diff --git a/graphics/graphics-core/api/restricted_current.ignore b/graphics/graphics-core/api/restricted_current.ignore
index 3b02b659eb9ee..f77a62bdfc5cc 100644
--- a/graphics/graphics-core/api/restricted_current.ignore
+++ b/graphics/graphics-core/api/restricted_current.ignore
@@ -53,6 +53,170 @@ AddedMethod: androidx.graphics.surface.SurfaceControlCompat.Transaction#setBuffe
Added method androidx.graphics.surface.SurfaceControlCompat.Transaction.setBuffer$default(androidx.graphics.surface.SurfaceControlCompat.Transaction,androidx.graphics.surface.SurfaceControlCompat,android.hardware.HardwareBuffer,androidx.hardware.SyncFenceCompat,kotlin.jvm.functions.Function1,int,Object)
+AddedProperty: androidx.graphics.CanvasBufferedRenderer.RenderResult.Companion#ERROR_UNKNOWN:
+ Added property androidx.graphics.CanvasBufferedRenderer.RenderResult.Companion#ERROR_UNKNOWN
+AddedProperty: androidx.graphics.CanvasBufferedRenderer.RenderResult.Companion#SUCCESS:
+ Added property androidx.graphics.CanvasBufferedRenderer.RenderResult.Companion#SUCCESS
+AddedProperty: androidx.graphics.opengl.SyncStrategy.Companion#ALWAYS:
+ Added property androidx.graphics.opengl.SyncStrategy.Companion#ALWAYS
+AddedProperty: androidx.graphics.opengl.egl.EGLConfigAttributes#attrs:
+ Added property androidx.graphics.opengl.egl.EGLConfigAttributes#attrs
+AddedProperty: androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#EGL_COLOR_COMPONENT_TYPE_EXT:
+ Added property androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#EGL_COLOR_COMPONENT_TYPE_EXT
+AddedProperty: androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#EGL_COLOR_COMPONENT_TYPE_FIXED_EXT:
+ Added property androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#EGL_COLOR_COMPONENT_TYPE_FIXED_EXT
+AddedProperty: androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT:
+ Added property androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT
+AddedProperty: androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#RGBA_1010102:
+ Added property androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#RGBA_1010102
+AddedProperty: androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#RGBA_8888:
+ Added property androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#RGBA_8888
+AddedProperty: androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#RGBA_F16:
+ Added property androidx.graphics.opengl.egl.EGLConfigAttributes.Companion#RGBA_F16
+AddedProperty: androidx.graphics.opengl.egl.EGLSpec.Companion#V14:
+ Added property androidx.graphics.opengl.egl.EGLSpec.Companion#V14
+AddedProperty: androidx.graphics.opengl.egl.EGLVersion.Companion#Unknown:
+ Added property androidx.graphics.opengl.egl.EGLVersion.Companion#Unknown
+AddedProperty: androidx.graphics.opengl.egl.EGLVersion.Companion#V14:
+ Added property androidx.graphics.opengl.egl.EGLVersion.Companion#V14
+AddedProperty: androidx.graphics.opengl.egl.EGLVersion.Companion#V15:
+ Added property androidx.graphics.opengl.egl.EGLVersion.Companion#V15
+AddedProperty: androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_IDENTITY:
+ Added property androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_IDENTITY
+AddedProperty: androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_MIRROR_HORIZONTAL:
+ Added property androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_MIRROR_HORIZONTAL
+AddedProperty: androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_MIRROR_VERTICAL:
+ Added property androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_MIRROR_VERTICAL
+AddedProperty: androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_ROTATE_180:
+ Added property androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_ROTATE_180
+AddedProperty: androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_ROTATE_270:
+ Added property androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_ROTATE_270
+AddedProperty: androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_ROTATE_90:
+ Added property androidx.graphics.surface.SurfaceControlCompat.Companion#BUFFER_TRANSFORM_ROTATE_90
+AddedProperty: androidx.graphics.surface.SurfaceControlCompat.Companion#CHANGE_FRAME_RATE_ALWAYS:
+ Added property androidx.graphics.surface.SurfaceControlCompat.Companion#CHANGE_FRAME_RATE_ALWAYS
+AddedProperty: androidx.graphics.surface.SurfaceControlCompat.Companion#CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS:
+ Added property androidx.graphics.surface.SurfaceControlCompat.Companion#CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
+AddedProperty: androidx.graphics.surface.SurfaceControlCompat.Companion#FRAME_RATE_COMPATIBILITY_DEFAULT:
+ Added property androidx.graphics.surface.SurfaceControlCompat.Companion#FRAME_RATE_COMPATIBILITY_DEFAULT
+AddedProperty: androidx.graphics.surface.SurfaceControlCompat.Companion#FRAME_RATE_COMPATIBILITY_FIXED_SOURCE:
+ Added property androidx.graphics.surface.SurfaceControlCompat.Companion#FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_ADOBE_RGB:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_ADOBE_RGB
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_BT2020:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_BT2020
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_BT2020_HLG:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_BT2020_HLG
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_BT2020_PQ:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_BT2020_PQ
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_BT601_525:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_BT601_525
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_BT601_625:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_BT601_625
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_BT709:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_BT709
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_DCI_P3:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_DCI_P3
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_DEPTH:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_DEPTH
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_DISPLAY_P3:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_DISPLAY_P3
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_DYNAMIC_DEPTH:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_DYNAMIC_DEPTH
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_HEIF:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_HEIF
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_JFIF:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_JFIF
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_JPEG_R:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_JPEG_R
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_SCRGB:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_SCRGB
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_SCRGB_LINEAR:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_SCRGB_LINEAR
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_SRGB:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_SRGB
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_SRGB_LINEAR:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_SRGB_LINEAR
+AddedProperty: androidx.hardware.DataSpace.Companion#DATASPACE_UNKNOWN:
+ Added property androidx.hardware.DataSpace.Companion#DATASPACE_UNKNOWN
+AddedProperty: androidx.hardware.DataSpace.Companion#RANGE_EXTENDED:
+ Added property androidx.hardware.DataSpace.Companion#RANGE_EXTENDED
+AddedProperty: androidx.hardware.DataSpace.Companion#RANGE_FULL:
+ Added property androidx.hardware.DataSpace.Companion#RANGE_FULL
+AddedProperty: androidx.hardware.DataSpace.Companion#RANGE_LIMITED:
+ Added property androidx.hardware.DataSpace.Companion#RANGE_LIMITED
+AddedProperty: androidx.hardware.DataSpace.Companion#RANGE_UNSPECIFIED:
+ Added property androidx.hardware.DataSpace.Companion#RANGE_UNSPECIFIED
+AddedProperty: androidx.hardware.SyncFenceCompat.Companion#SIGNAL_TIME_INVALID:
+ Added property androidx.hardware.SyncFenceCompat.Companion#SIGNAL_TIME_INVALID
+AddedProperty: androidx.hardware.SyncFenceCompat.Companion#SIGNAL_TIME_PENDING:
+ Added property androidx.hardware.SyncFenceCompat.Companion#SIGNAL_TIME_PENDING
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_ANDROID_CLIENT_BUFFER:
+ Added property androidx.opengl.EGLExt.Companion#EGL_ANDROID_CLIENT_BUFFER
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_ANDROID_IMAGE_NATIVE_BUFFER:
+ Added property androidx.opengl.EGLExt.Companion#EGL_ANDROID_IMAGE_NATIVE_BUFFER
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_ANDROID_NATIVE_FENCE_SYNC:
+ Added property androidx.opengl.EGLExt.Companion#EGL_ANDROID_NATIVE_FENCE_SYNC
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_CONDITION_SATISFIED_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_CONDITION_SATISFIED_KHR
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_EXT_BUFFER_AGE:
+ Added property androidx.opengl.EGLExt.Companion#EGL_EXT_BUFFER_AGE
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_EXT_GL_COLORSPACE_BT2020_PQ:
+ Added property androidx.opengl.EGLExt.Companion#EGL_EXT_GL_COLORSPACE_BT2020_PQ
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH:
+ Added property androidx.opengl.EGLExt.Companion#EGL_EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_EXT_GL_COLORSPACE_SCRGB:
+ Added property androidx.opengl.EGLExt.Companion#EGL_EXT_GL_COLORSPACE_SCRGB
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_EXT_PIXEL_FORMAT_FLOAT:
+ Added property androidx.opengl.EGLExt.Companion#EGL_EXT_PIXEL_FORMAT_FLOAT
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_FALSE:
+ Added property androidx.opengl.EGLExt.Companion#EGL_FALSE
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_FOREVER_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_FOREVER_KHR
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_IMG_CONTEXT_PRIORITY:
+ Added property androidx.opengl.EGLExt.Companion#EGL_IMG_CONTEXT_PRIORITY
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_KHR_FENCE_SYNC:
+ Added property androidx.opengl.EGLExt.Companion#EGL_KHR_FENCE_SYNC
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_KHR_GL_COLORSPACE:
+ Added property androidx.opengl.EGLExt.Companion#EGL_KHR_GL_COLORSPACE
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_KHR_IMAGE:
+ Added property androidx.opengl.EGLExt.Companion#EGL_KHR_IMAGE
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_KHR_IMAGE_BASE:
+ Added property androidx.opengl.EGLExt.Companion#EGL_KHR_IMAGE_BASE
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_KHR_NO_CONFIG_CONTEXT:
+ Added property androidx.opengl.EGLExt.Companion#EGL_KHR_NO_CONFIG_CONTEXT
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_KHR_PARTIAL_UPDATE:
+ Added property androidx.opengl.EGLExt.Companion#EGL_KHR_PARTIAL_UPDATE
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_KHR_SURFACELESS_CONTEXT:
+ Added property androidx.opengl.EGLExt.Companion#EGL_KHR_SURFACELESS_CONTEXT
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_KHR_SWAP_BUFFERS_WITH_DAMAGE:
+ Added property androidx.opengl.EGLExt.Companion#EGL_KHR_SWAP_BUFFERS_WITH_DAMAGE
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_KHR_WAIT_SYNC:
+ Added property androidx.opengl.EGLExt.Companion#EGL_KHR_WAIT_SYNC
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_SIGNALED_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_SIGNALED_KHR
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_SYNC_CONDITION_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_SYNC_CONDITION_KHR
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_SYNC_FENCE_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_SYNC_FENCE_KHR
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_SYNC_FLUSH_COMMANDS_BIT_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_SYNC_FLUSH_COMMANDS_BIT_KHR
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_SYNC_NATIVE_FENCE_ANDROID:
+ Added property androidx.opengl.EGLExt.Companion#EGL_SYNC_NATIVE_FENCE_ANDROID
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_SYNC_PRIOR_COMMANDS_COMPLETE_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_SYNC_PRIOR_COMMANDS_COMPLETE_KHR
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_SYNC_STATUS_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_SYNC_STATUS_KHR
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_SYNC_TYPE_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_SYNC_TYPE_KHR
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_TIMEOUT_EXPIRED_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_TIMEOUT_EXPIRED_KHR
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_TRUE:
+ Added property androidx.opengl.EGLExt.Companion#EGL_TRUE
+AddedProperty: androidx.opengl.EGLExt.Companion#EGL_UNSIGNALED_KHR:
+ Added property androidx.opengl.EGLExt.Companion#EGL_UNSIGNALED_KHR
+
+
RemovedFromJava: androidx.graphics.opengl.egl.EGLConfigAttributes.Builder#to(int, int):
Source breaking change: method androidx.graphics.opengl.egl.EGLConfigAttributes.Builder.to(int,int) can no longer be resolved from Java source
RemovedFromJava: androidx.graphics.opengl.egl.EGLConfigAttributesKt#EGLConfigAttributes(kotlin.jvm.functions.Function1 super androidx.graphics.opengl.egl.EGLConfigAttributes.Builder,kotlin.Unit>):
diff --git a/health/connect/connect-client/api/current.txt b/health/connect/connect-client/api/current.txt
index 49c0e6eda73c4..c43af2c355c2b 100644
--- a/health/connect/connect-client/api/current.txt
+++ b/health/connect/connect-client/api/current.txt
@@ -289,7 +289,7 @@ package androidx.health.connect.client.records {
field public static final int ACTIVITY_INTENSITY_TYPE_VIGOROUS = 1; // 0x1
field public static final androidx.health.connect.client.records.ActivityIntensityRecord.Companion Companion;
field public static final androidx.health.connect.client.aggregate.AggregateMetric DURATION_TOTAL;
- field public static final androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL;
+ field public static final androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL;
field public static final androidx.health.connect.client.aggregate.AggregateMetric MODERATE_DURATION_TOTAL;
field public static final androidx.health.connect.client.aggregate.AggregateMetric VIGOROUS_DURATION_TOTAL;
}
@@ -298,7 +298,7 @@ package androidx.health.connect.client.records {
property public static int ACTIVITY_INTENSITY_TYPE_MODERATE;
property public static int ACTIVITY_INTENSITY_TYPE_VIGOROUS;
property public androidx.health.connect.client.aggregate.AggregateMetric DURATION_TOTAL;
- property public androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL;
+ property public androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL;
property public androidx.health.connect.client.aggregate.AggregateMetric MODERATE_DURATION_TOTAL;
property public androidx.health.connect.client.aggregate.AggregateMetric VIGOROUS_DURATION_TOTAL;
}
diff --git a/health/connect/connect-client/api/restricted_current.txt b/health/connect/connect-client/api/restricted_current.txt
index 51480ab736d82..6a526f78a51b3 100644
--- a/health/connect/connect-client/api/restricted_current.txt
+++ b/health/connect/connect-client/api/restricted_current.txt
@@ -289,7 +289,7 @@ package androidx.health.connect.client.records {
field public static final int ACTIVITY_INTENSITY_TYPE_VIGOROUS = 1; // 0x1
field public static final androidx.health.connect.client.records.ActivityIntensityRecord.Companion Companion;
field public static final androidx.health.connect.client.aggregate.AggregateMetric DURATION_TOTAL;
- field public static final androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL;
+ field public static final androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL;
field public static final androidx.health.connect.client.aggregate.AggregateMetric MODERATE_DURATION_TOTAL;
field public static final androidx.health.connect.client.aggregate.AggregateMetric VIGOROUS_DURATION_TOTAL;
}
@@ -298,7 +298,7 @@ package androidx.health.connect.client.records {
property public static int ACTIVITY_INTENSITY_TYPE_MODERATE;
property public static int ACTIVITY_INTENSITY_TYPE_VIGOROUS;
property public androidx.health.connect.client.aggregate.AggregateMetric DURATION_TOTAL;
- property public androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL;
+ property public androidx.health.connect.client.aggregate.AggregateMetric INTENSITY_MINUTES_TOTAL;
property public androidx.health.connect.client.aggregate.AggregateMetric MODERATE_DURATION_TOTAL;
property public androidx.health.connect.client.aggregate.AggregateMetric VIGOROUS_DURATION_TOTAL;
}
diff --git a/health/connect/connect-client/lint-baseline.xml b/health/connect/connect-client/lint-baseline.xml
index 381356704607d..e8e3305e88241 100644
--- a/health/connect/connect-client/lint-baseline.xml
+++ b/health/connect/connect-client/lint-baseline.xml
@@ -1,23 +1,5 @@
-
-
-
-
-
-
-
-
-
+
= 16)
-
- private val grantPermissionRule: GrantPermissionRule =
- GrantPermissionRule.grant(
- *listOf(
- HealthPermission.getWritePermission(ActivityIntensityRecord::class),
- HealthPermission.getReadPermission(ActivityIntensityRecord::class),
- )
- .toTypedArray()
- )
-
- @get:Rule val ruleChain: RuleChain = RuleChain.outerRule(assumeRule).around(grantPermissionRule)
-
- @After
- fun tearDown() = runTest {
- healthConnectClient.deleteRecords(
- ActivityIntensityRecord::class,
- TimeRangeFilter.after(Instant.EPOCH),
- )
- }
-
- // The test aims to verify the wiring with the framework rather than the aggregation logic
- // itself.
- @Test
- fun aggregateRecords() = runTest {
- val insertRecordsResponse =
- healthConnectClient.insertRecords(
- listOf(
- ActivityIntensityRecord(
- startTime = START_TIME,
- startZoneOffset = ZoneOffset.UTC,
- endTime = START_TIME.plusSeconds(150),
- endZoneOffset = ZoneOffset.UTC,
- metadata = Metadata.manualEntry(),
- activityIntensityType = ACTIVITY_INTENSITY_TYPE_VIGOROUS,
- ),
- ActivityIntensityRecord(
- startTime = START_TIME.plusSeconds(180),
- startZoneOffset = ZoneOffset.UTC,
- endTime = START_TIME.plusSeconds(300),
- endZoneOffset = ZoneOffset.UTC,
- metadata = Metadata.manualEntry(),
- activityIntensityType = ACTIVITY_INTENSITY_TYPE_MODERATE,
- ),
- )
- )
- assertThat(insertRecordsResponse.recordIdsList.size).isEqualTo(2)
-
- val aggregateResponse =
- healthConnectClient.aggregate(
- AggregateRequest(
- setOf(
- ActivityIntensityRecord.DURATION_TOTAL,
- ActivityIntensityRecord.MODERATE_DURATION_TOTAL,
- ActivityIntensityRecord.VIGOROUS_DURATION_TOTAL,
- ActivityIntensityRecord.INTENSITY_MINUTES_TOTAL,
- ),
- TimeRangeFilter.after(Instant.EPOCH),
- )
- )
-
- with(aggregateResponse) {
- assertThat(this[ActivityIntensityRecord.DURATION_TOTAL])
- .isEqualTo(Duration.ofSeconds(270))
- assertThat(this[ActivityIntensityRecord.MODERATE_DURATION_TOTAL])
- .isEqualTo(Duration.ofSeconds(120))
- assertThat(this[ActivityIntensityRecord.VIGOROUS_DURATION_TOTAL])
- .isEqualTo(Duration.ofSeconds(150))
- assertThat(this[ActivityIntensityRecord.INTENSITY_MINUTES_TOTAL]).isEqualTo(7)
- }
- }
-}
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/MindfulnessSessionRecordsTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/MindfulnessSessionRecordsTest.kt
deleted file mode 100644
index f56873dd24f54..0000000000000
--- a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/MindfulnessSessionRecordsTest.kt
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2026 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.health.connect.client.impl.platform.records
-
-import android.annotation.TargetApi
-import android.content.Context
-import android.os.Build
-import android.os.ext.SdkExtensions
-import androidx.health.connect.client.AssumeRule
-import androidx.health.connect.client.HealthConnectClient
-import androidx.health.connect.client.impl.HealthConnectClientUpsideDownImpl
-import androidx.health.connect.client.permission.HealthPermission
-import androidx.health.connect.client.records.MindfulnessSessionRecord
-import androidx.health.connect.client.records.MindfulnessSessionRecord.Companion.MINDFULNESS_SESSION_TYPE_BREATHING
-import androidx.health.connect.client.records.MindfulnessSessionRecord.Companion.MINDFULNESS_SESSION_TYPE_MEDITATION
-import androidx.health.connect.client.records.metadata.Metadata
-import androidx.health.connect.client.request.AggregateRequest
-import androidx.health.connect.client.time.TimeRangeFilter
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
-import androidx.test.rule.GrantPermissionRule
-import com.google.common.truth.Truth.assertThat
-import java.time.Duration
-import java.time.Instant
-import java.time.LocalDate
-import java.time.ZoneOffset
-import kotlinx.coroutines.test.runTest
-import org.junit.After
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.RuleChain
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-@MediumTest
-@TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-class MindfulnessSessionRecordTest {
-
- private val context: Context = ApplicationProvider.getApplicationContext()
- private val healthConnectClient: HealthConnectClient =
- HealthConnectClientUpsideDownImpl(context)
-
- private companion object {
- private val START_TIME =
- LocalDate.now().minusDays(5).atStartOfDay().toInstant(ZoneOffset.UTC)
- }
-
- private val assumeRule: AssumeRule =
- AssumeRule(SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 15)
-
- private val grantPermissionRule: GrantPermissionRule =
- GrantPermissionRule.grant(
- *listOf(
- HealthPermission.getWritePermission(MindfulnessSessionRecord::class),
- HealthPermission.getReadPermission(MindfulnessSessionRecord::class),
- )
- .toTypedArray()
- )
-
- @get:Rule val ruleChain: RuleChain = RuleChain.outerRule(assumeRule).around(grantPermissionRule)
-
- @After
- fun tearDown() = runTest {
- healthConnectClient.deleteRecords(
- MindfulnessSessionRecord::class,
- TimeRangeFilter.after(Instant.EPOCH),
- )
- }
-
- // The test aims to verify the wiring with the framework rather than the aggregation logic
- // itself.
- @Test
- fun aggregateRecords() = runTest {
- val insertRecordsResponse =
- healthConnectClient.insertRecords(
- listOf(
- MindfulnessSessionRecord(
- startTime = START_TIME,
- startZoneOffset = ZoneOffset.UTC,
- endTime = START_TIME.plusSeconds(150),
- endZoneOffset = ZoneOffset.UTC,
- metadata = Metadata.manualEntry(),
- mindfulnessSessionType = MINDFULNESS_SESSION_TYPE_MEDITATION,
- ),
- MindfulnessSessionRecord(
- startTime = START_TIME.plusSeconds(180),
- startZoneOffset = ZoneOffset.UTC,
- endTime = START_TIME.plusSeconds(300),
- endZoneOffset = ZoneOffset.UTC,
- metadata = Metadata.manualEntry(),
- mindfulnessSessionType = MINDFULNESS_SESSION_TYPE_BREATHING,
- ),
- )
- )
- assertThat(insertRecordsResponse.recordIdsList.size).isEqualTo(2)
-
- val aggregateResponse =
- healthConnectClient.aggregate(
- AggregateRequest(
- setOf(MindfulnessSessionRecord.MINDFULNESS_DURATION_TOTAL),
- TimeRangeFilter.after(Instant.EPOCH),
- )
- )
-
- with(aggregateResponse) {
- assertThat(this[MindfulnessSessionRecord.MINDFULNESS_DURATION_TOTAL])
- .isEqualTo(Duration.ofSeconds(270))
- }
- }
-}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/aggregate/AggregationMappings.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/aggregate/AggregationMappings.kt
index b7360653ee2e9..73eb1b793c745 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/aggregate/AggregationMappings.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/aggregate/AggregationMappings.kt
@@ -29,7 +29,6 @@ import android.health.connect.datatypes.FloorsClimbedRecord as PlatformFloorsCli
import android.health.connect.datatypes.HeartRateRecord as PlatformHeartRateRecord
import android.health.connect.datatypes.HeightRecord as PlatformHeightRecord
import android.health.connect.datatypes.HydrationRecord as PlatformHydrationRecord
-import android.health.connect.datatypes.MindfulnessSessionRecord as PlatformMindfulnessSessionRecord
import android.health.connect.datatypes.NutritionRecord as PlatformNutritionRecord
import android.health.connect.datatypes.PowerRecord as PlatformPowerRecord
import android.health.connect.datatypes.StepsRecord as PlatformStepsRecord
@@ -41,10 +40,11 @@ import android.health.connect.datatypes.units.Length as PlatformLength
import android.health.connect.datatypes.units.Mass as PlatformMass
import android.health.connect.datatypes.units.Power as PlatformPower
import android.health.connect.datatypes.units.Volume as PlatformVolume
+import android.os.Build
+import android.os.ext.SdkExtensions
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.health.connect.client.aggregate.AggregateMetric
-import androidx.health.connect.client.impl.platform.records.PlatformActivityIntensityRecord
import androidx.health.connect.client.impl.platform.records.PlatformBloodPressureRecord
import androidx.health.connect.client.impl.platform.records.PlatformCyclingPedalingCadenceRecord
import androidx.health.connect.client.impl.platform.records.PlatformExerciseSessionRecord
@@ -56,7 +56,6 @@ import androidx.health.connect.client.impl.platform.records.PlatformSpeedRecord
import androidx.health.connect.client.impl.platform.records.PlatformStepsCadenceRecord
import androidx.health.connect.client.impl.platform.records.PlatformVelocity
import androidx.health.connect.client.records.ActiveCaloriesBurnedRecord
-import androidx.health.connect.client.records.ActivityIntensityRecord
import androidx.health.connect.client.records.BasalMetabolicRateRecord
import androidx.health.connect.client.records.BloodPressureRecord
import androidx.health.connect.client.records.CyclingPedalingCadenceRecord
@@ -67,7 +66,6 @@ import androidx.health.connect.client.records.FloorsClimbedRecord
import androidx.health.connect.client.records.HeartRateRecord
import androidx.health.connect.client.records.HeightRecord
import androidx.health.connect.client.records.HydrationRecord
-import androidx.health.connect.client.records.MindfulnessSessionRecord
import androidx.health.connect.client.records.NutritionRecord
import androidx.health.connect.client.records.PowerRecord
import androidx.health.connect.client.records.RestingHeartRateRecord
@@ -79,10 +77,6 @@ import androidx.health.connect.client.records.StepsRecord
import androidx.health.connect.client.records.TotalCaloriesBurnedRecord
import androidx.health.connect.client.records.WeightRecord
import androidx.health.connect.client.records.WheelchairPushesRecord
-import androidx.health.connect.client.records.isAtLeastSdkExtension10
-import androidx.health.connect.client.records.isAtLeastSdkExtension13
-import androidx.health.connect.client.records.isAtLeastSdkExtension15
-import androidx.health.connect.client.records.isAtLeastSdkExtension16
import androidx.health.connect.client.units.Energy
import androidx.health.connect.client.units.Length
import androidx.health.connect.client.units.Mass
@@ -94,7 +88,7 @@ import androidx.health.connect.client.units.Volume
import java.time.Duration
private val DOUBLE_AGGREGATION_METRIC_TYPE_SDK_EXT_10_PAIRS =
- if (isAtLeastSdkExtension10()) {
+ if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10) {
arrayOf(
CyclingPedalingCadenceRecord.RPM_AVG to PlatformCyclingPedalingCadenceRecord.RPM_AVG,
CyclingPedalingCadenceRecord.RPM_MAX to PlatformCyclingPedalingCadenceRecord.RPM_MAX,
@@ -115,38 +109,12 @@ internal val DOUBLE_AGGREGATION_METRIC_TYPE_MAP:
*DOUBLE_AGGREGATION_METRIC_TYPE_SDK_EXT_10_PAIRS,
)
-internal val DURATION_TO_LONG_AGGREGATION_METRIC_TYPE_MAP:
+internal val DURATION_AGGREGATION_METRIC_TYPE_MAP:
Map, PlatformAggregateMetric> =
mapOf(
ExerciseSessionRecord.EXERCISE_DURATION_TOTAL to
PlatformExerciseSessionRecord.EXERCISE_DURATION_TOTAL,
SleepSessionRecord.SLEEP_DURATION_TOTAL to PlatformSleepSessionRecord.SLEEP_DURATION_TOTAL,
- *if (isAtLeastSdkExtension15()) {
- arrayOf(
- MindfulnessSessionRecord.MINDFULNESS_DURATION_TOTAL to
- PlatformMindfulnessSessionRecord.MINDFULNESS_DURATION_TOTAL
- )
- } else {
- emptyArray()
- },
- )
-
-internal val DURATION_AGGREGATION_METRIC_TYPE_MAP:
- Map, PlatformAggregateMetric> =
- mapOf(
- *if (isAtLeastSdkExtension16()) {
- arrayOf(
- ActivityIntensityRecord.MODERATE_DURATION_TOTAL to
- PlatformActivityIntensityRecord.MODERATE_DURATION_TOTAL,
- ActivityIntensityRecord.VIGOROUS_DURATION_TOTAL to
- PlatformActivityIntensityRecord.VIGOROUS_DURATION_TOTAL,
- ActivityIntensityRecord.DURATION_TOTAL to
- PlatformActivityIntensityRecord.DURATION_TOTAL,
- )
- as Array, PlatformAggregateMetric>>
- } else {
- emptyArray()
- }
)
internal val ENERGY_AGGREGATION_METRIC_TYPE_MAP:
@@ -185,14 +153,6 @@ internal val LONG_AGGREGATION_METRIC_TYPE_MAP:
StepsRecord.COUNT_TOTAL to PlatformStepsRecord.STEPS_COUNT_TOTAL,
WheelchairPushesRecord.COUNT_TOTAL to
PlatformWheelchairPushesRecord.WHEEL_CHAIR_PUSHES_COUNT_TOTAL,
- *if (isAtLeastSdkExtension16()) {
- arrayOf(
- ActivityIntensityRecord.INTENSITY_MINUTES_TOTAL to
- PlatformActivityIntensityRecord.INTENSITY_MINUTES_TOTAL
- )
- } else {
- emptyArray()
- },
)
internal val GRAMS_AGGREGATION_METRIC_TYPE_MAP:
@@ -240,7 +200,7 @@ internal val GRAMS_AGGREGATION_METRIC_TYPE_MAP:
NutritionRecord.VITAMIN_E_TOTAL to PlatformNutritionRecord.VITAMIN_E_TOTAL,
NutritionRecord.VITAMIN_K_TOTAL to PlatformNutritionRecord.VITAMIN_K_TOTAL,
NutritionRecord.ZINC_TOTAL to PlatformNutritionRecord.ZINC_TOTAL,
- *if (isAtLeastSdkExtension10()) {
+ *if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10) {
arrayOf(NutritionRecord.TRANS_FAT_TOTAL to PlatformNutritionRecord.TRANS_FAT_TOTAL)
} else {
emptyArray()
@@ -265,7 +225,7 @@ internal val POWER_AGGREGATION_METRIC_TYPE_MAP:
internal val PRESSURE_AGGREGATION_METRIC_TYPE_MAP:
Map, PlatformAggregateMetric> =
- if (isAtLeastSdkExtension10()) {
+ if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10) {
arrayOf(
BloodPressureRecord.DIASTOLIC_AVG to PlatformBloodPressureRecord.DIASTOLIC_AVG,
BloodPressureRecord.DIASTOLIC_MAX to PlatformBloodPressureRecord.DIASTOLIC_MAX,
@@ -282,7 +242,7 @@ internal val PRESSURE_AGGREGATION_METRIC_TYPE_MAP:
@SuppressLint("NewApi") // API 35 is covered by sdk extension check
internal val TEMPERATURE_DELTA_METRIC_TYPE_MAP:
Map, PlatformAggregateMetric<*>> =
- if (isAtLeastSdkExtension13()) {
+ if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 13) {
arrayOf(
SkinTemperatureRecord.TEMPERATURE_DELTA_AVG to
PlatformSkinTemperatureRecord.SKIN_TEMPERATURE_DELTA_AVG,
@@ -298,7 +258,7 @@ internal val TEMPERATURE_DELTA_METRIC_TYPE_MAP:
internal val VELOCITY_AGGREGATION_METRIC_TYPE_MAP:
Map, PlatformAggregateMetric> =
- if (isAtLeastSdkExtension10()) {
+ if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10) {
arrayOf(
SpeedRecord.SPEED_AVG to PlatformSpeedRecord.SPEED_AVG,
SpeedRecord.SPEED_MAX to PlatformSpeedRecord.SPEED_MAX,
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/request/RequestConverters.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/request/RequestConverters.kt
index 86db8cb75de8a..0d2edd5ffea21 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/request/RequestConverters.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/request/RequestConverters.kt
@@ -32,7 +32,6 @@ import androidx.annotation.RestrictTo
import androidx.health.connect.client.aggregate.AggregateMetric
import androidx.health.connect.client.impl.platform.aggregate.DOUBLE_AGGREGATION_METRIC_TYPE_MAP
import androidx.health.connect.client.impl.platform.aggregate.DURATION_AGGREGATION_METRIC_TYPE_MAP
-import androidx.health.connect.client.impl.platform.aggregate.DURATION_TO_LONG_AGGREGATION_METRIC_TYPE_MAP
import androidx.health.connect.client.impl.platform.aggregate.ENERGY_AGGREGATION_METRIC_TYPE_MAP
import androidx.health.connect.client.impl.platform.aggregate.GRAMS_AGGREGATION_METRIC_TYPE_MAP
import androidx.health.connect.client.impl.platform.aggregate.KILOGRAMS_AGGREGATION_METRIC_TYPE_MAP
@@ -130,7 +129,6 @@ fun AggregateGroupByPeriodRequest.toPlatformRequest(): AggregateRecordsRequest.toAggregationType(): AggregationType {
return DOUBLE_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType?
?: DURATION_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType?
- ?: DURATION_TO_LONG_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType?
?: ENERGY_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType?
?: GRAMS_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType?
?: LENGTH_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType?
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/response/ResponseConverters.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/response/ResponseConverters.kt
index b5afbdd9a8418..48af0c5c435cc 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/response/ResponseConverters.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/response/ResponseConverters.kt
@@ -37,7 +37,6 @@ import androidx.health.connect.client.aggregate.AggregationResultGroupedByDurati
import androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod
import androidx.health.connect.client.impl.platform.aggregate.DOUBLE_AGGREGATION_METRIC_TYPE_MAP
import androidx.health.connect.client.impl.platform.aggregate.DURATION_AGGREGATION_METRIC_TYPE_MAP
-import androidx.health.connect.client.impl.platform.aggregate.DURATION_TO_LONG_AGGREGATION_METRIC_TYPE_MAP
import androidx.health.connect.client.impl.platform.aggregate.ENERGY_AGGREGATION_METRIC_TYPE_MAP
import androidx.health.connect.client.impl.platform.aggregate.GRAMS_AGGREGATION_METRIC_TYPE_MAP
import androidx.health.connect.client.impl.platform.aggregate.KILOGRAMS_AGGREGATION_METRIC_TYPE_MAP
@@ -59,7 +58,6 @@ import androidx.health.connect.client.impl.platform.records.toSdkDataOrigin
import androidx.health.connect.client.impl.platform.request.toAggregationType
import androidx.health.connect.client.units.Energy
import androidx.health.connect.client.units.Mass
-import java.time.Duration
import java.time.LocalDateTime
import java.time.ZoneOffset
@@ -141,16 +139,11 @@ internal fun getLongMetricValues(
): Map {
return buildMap {
metricValueMap.forEach { (key, value) ->
- when (key) {
- in LONG_AGGREGATION_METRIC_TYPE_MAP -> {
- this[key.metricKey] = value as Long
- }
- in DURATION_TO_LONG_AGGREGATION_METRIC_TYPE_MAP -> {
- this[key.metricKey] = value as Long
- }
- in DURATION_AGGREGATION_METRIC_TYPE_MAP -> {
- this[key.metricKey] = (value as Duration).toMillis()
- }
+ if (
+ key in DURATION_AGGREGATION_METRIC_TYPE_MAP ||
+ key in LONG_AGGREGATION_METRIC_TYPE_MAP
+ ) {
+ this[key.metricKey] = value as Long
}
}
}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ActivityIntensityRecord.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ActivityIntensityRecord.kt
index d71988374767d..95ce39591aaa7 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ActivityIntensityRecord.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ActivityIntensityRecord.kt
@@ -138,10 +138,10 @@ class ActivityIntensityRecord(
* [HealthConnectFeatures.FEATURE_ACTIVITY_INTENSITY] as the argument.
*/
@JvmField
- val INTENSITY_MINUTES_TOTAL: AggregateMetric =
- AggregateMetric.longMetric(
+ val INTENSITY_MINUTES_TOTAL: AggregateMetric =
+ AggregateMetric.durationMetric(
"ActivityIntensity",
- aggregationType = AggregateMetric.AggregationType.TOTAL,
+ aggregationType = AggregateMetric.AggregationType.DURATION,
fieldName = "intensityMinutes",
)
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/Utils.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/Utils.kt
index aaeb330d8afa7..972f00399efa4 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/Utils.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/Utils.kt
@@ -23,24 +23,14 @@ import android.os.ext.SdkExtensions
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
-// We additionally check for SDK_INT check for Robolectric tests, which doesn't fully support
-// SdkExtensions.
-@RequiresApi(Build.VERSION_CODES.R)
-internal fun isAtLeastSdkExtension10(): Boolean {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM ||
- SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10
-}
-
@RequiresApi(Build.VERSION_CODES.R)
internal fun isAtLeastSdkExtension13(): Boolean {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM ||
- SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 13
+ return SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 13
}
@RequiresApi(Build.VERSION_CODES.R)
internal fun isAtLeastSdkExtension15(): Boolean {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA ||
- SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 15
+ return SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 15
}
@RequiresApi(Build.VERSION_CODES.R)
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/platform/aggregate/AggregationMappingsTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/platform/aggregate/AggregationMappingsTest.kt
deleted file mode 100644
index 9388db7421550..0000000000000
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/platform/aggregate/AggregationMappingsTest.kt
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.health.connect.client.impl.platform.aggregate
-
-import androidx.health.connect.client.aggregate.AggregateMetric
-import androidx.health.connect.client.impl.converters.datatype.RECORDS_CLASS_NAME_MAP
-import androidx.health.connect.client.records.isAtLeastSdkExtension16
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.google.common.truth.Truth.assertWithMessage
-import java.lang.reflect.Field
-import java.lang.reflect.Modifier
-import org.junit.Assume
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.annotation.Config
-
-@RunWith(AndroidJUnit4::class)
-@Config(minSdk = 36)
-class AggregationMappingsTest {
-
- private val allAggregationMaps =
- listOf(
- DOUBLE_AGGREGATION_METRIC_TYPE_MAP,
- DURATION_AGGREGATION_METRIC_TYPE_MAP,
- DURATION_TO_LONG_AGGREGATION_METRIC_TYPE_MAP,
- ENERGY_AGGREGATION_METRIC_TYPE_MAP,
- GRAMS_AGGREGATION_METRIC_TYPE_MAP,
- KILOGRAMS_AGGREGATION_METRIC_TYPE_MAP,
- LENGTH_AGGREGATION_METRIC_TYPE_MAP,
- LONG_AGGREGATION_METRIC_TYPE_MAP,
- POWER_AGGREGATION_METRIC_TYPE_MAP,
- PRESSURE_AGGREGATION_METRIC_TYPE_MAP,
- TEMPERATURE_DELTA_METRIC_TYPE_MAP,
- VELOCITY_AGGREGATION_METRIC_TYPE_MAP,
- VOLUME_AGGREGATION_METRIC_TYPE_MAP,
- )
-
- @Before
- fun setUp() {
- // Update this when adding new aggregate metrics.
- Assume.assumeTrue(isAtLeastSdkExtension16())
- }
-
- @Test
- fun allAggregateMetrics_areAddedToAggregationMaps() {
- val recordClasses = RECORDS_CLASS_NAME_MAP.keys
- val aggregateMetrics =
- recordClasses.flatMap { recordClass ->
- recordClass.java.fields
- .filter { it.isAggregateMetric() }
- .map { it.get(null) as AggregateMetric<*> }
- }
-
- val allMappedMetrics = allAggregationMaps.flatMap { it.keys }
- val missingMetrics = aggregateMetrics.filter { !allMappedMetrics.contains(it) }
- val presentMetrics = aggregateMetrics.filter { allMappedMetrics.contains(it) }
- assertWithMessage(
- "Missing metrics: ${missingMetrics.map { it.metricKey }}, Present Metrics: ${presentMetrics.map { it.metricKey }}"
- )
- .that(allMappedMetrics)
- .containsAtLeastElementsIn(aggregateMetrics)
- }
-
- private fun Field.isAggregateMetric(): Boolean {
- return Modifier.isStatic(modifiers) &&
- Modifier.isPublic(modifiers) &&
- AggregateMetric::class.java.isAssignableFrom(type)
- }
-}
diff --git a/navigation/navigation-runtime/api/current.ignore b/navigation/navigation-runtime/api/current.ignore
index 480ff049303fb..8f01dff5d1534 100644
--- a/navigation/navigation-runtime/api/current.ignore
+++ b/navigation/navigation-runtime/api/current.ignore
@@ -1,6 +1,6 @@
// Baseline format: 1.0
DefaultValueChange: androidx.navigation.NavController#popBackStack(boolean, boolean) parameter #1:
- Source breaking change: Attempted to remove default value from parameter saveState in androidx.navigation.NavController.popBackStack
+ Source breaking change: Attempted to remove default value from parameter saveState in androidx.navigation.NavController.popBackStack(boolean inclusive, boolean saveState)
RemovedFromBytecode: androidx.navigation.ActivityNavArgsLazyKt#navArgs(android.app.Activity):
@@ -87,3 +87,13 @@ RemovedFromKotlin: androidx.navigation.NavController#setGraph(androidx.navigatio
Source breaking change: method androidx.navigation.NavController.setGraph(androidx.navigation.NavGraph) can no longer be resolved from Kotlin source
RemovedFromKotlin: androidx.navigation.NavHost#getNavController():
Source breaking change: method androidx.navigation.NavHost.getNavController() can no longer be resolved from Kotlin source
+
+
+RemovedProperty: androidx.navigation.NavController.Companion#KEY_DEEP_LINK_ARGS:
+ Source breaking change: Removed property androidx.navigation.NavController.Companion#KEY_DEEP_LINK_ARGS
+RemovedProperty: androidx.navigation.NavController.Companion#KEY_DEEP_LINK_EXTRAS:
+ Source breaking change: Removed property androidx.navigation.NavController.Companion#KEY_DEEP_LINK_EXTRAS
+RemovedProperty: androidx.navigation.NavController.Companion#KEY_DEEP_LINK_HANDLED:
+ Source breaking change: Removed property androidx.navigation.NavController.Companion#KEY_DEEP_LINK_HANDLED
+RemovedProperty: androidx.navigation.NavController.Companion#KEY_DEEP_LINK_IDS:
+ Source breaking change: Removed property androidx.navigation.NavController.Companion#KEY_DEEP_LINK_IDS
diff --git a/navigation/navigation-runtime/api/restricted_current.ignore b/navigation/navigation-runtime/api/restricted_current.ignore
index 480ff049303fb..8f01dff5d1534 100644
--- a/navigation/navigation-runtime/api/restricted_current.ignore
+++ b/navigation/navigation-runtime/api/restricted_current.ignore
@@ -1,6 +1,6 @@
// Baseline format: 1.0
DefaultValueChange: androidx.navigation.NavController#popBackStack(boolean, boolean) parameter #1:
- Source breaking change: Attempted to remove default value from parameter saveState in androidx.navigation.NavController.popBackStack
+ Source breaking change: Attempted to remove default value from parameter saveState in androidx.navigation.NavController.popBackStack(boolean inclusive, boolean saveState)
RemovedFromBytecode: androidx.navigation.ActivityNavArgsLazyKt#navArgs(android.app.Activity):
@@ -87,3 +87,13 @@ RemovedFromKotlin: androidx.navigation.NavController#setGraph(androidx.navigatio
Source breaking change: method androidx.navigation.NavController.setGraph(androidx.navigation.NavGraph) can no longer be resolved from Kotlin source
RemovedFromKotlin: androidx.navigation.NavHost#getNavController():
Source breaking change: method androidx.navigation.NavHost.getNavController() can no longer be resolved from Kotlin source
+
+
+RemovedProperty: androidx.navigation.NavController.Companion#KEY_DEEP_LINK_ARGS:
+ Source breaking change: Removed property androidx.navigation.NavController.Companion#KEY_DEEP_LINK_ARGS
+RemovedProperty: androidx.navigation.NavController.Companion#KEY_DEEP_LINK_EXTRAS:
+ Source breaking change: Removed property androidx.navigation.NavController.Companion#KEY_DEEP_LINK_EXTRAS
+RemovedProperty: androidx.navigation.NavController.Companion#KEY_DEEP_LINK_HANDLED:
+ Source breaking change: Removed property androidx.navigation.NavController.Companion#KEY_DEEP_LINK_HANDLED
+RemovedProperty: androidx.navigation.NavController.Companion#KEY_DEEP_LINK_IDS:
+ Source breaking change: Removed property androidx.navigation.NavController.Companion#KEY_DEEP_LINK_IDS
diff --git a/pdf/pdf-compose/src/androidTest/kotlin/androidx/pdf/compose/FakePdfDocument.kt b/pdf/pdf-compose/src/androidTest/kotlin/androidx/pdf/compose/FakePdfDocument.kt
index 6c83c870e6b92..3ecad232d46a5 100644
--- a/pdf/pdf-compose/src/androidTest/kotlin/androidx/pdf/compose/FakePdfDocument.kt
+++ b/pdf/pdf-compose/src/androidTest/kotlin/androidx/pdf/compose/FakePdfDocument.kt
@@ -38,6 +38,7 @@ import androidx.pdf.content.PdfPageLinkContent
import androidx.pdf.content.PdfPageTextContent
import androidx.pdf.content.SelectionBoundary
import androidx.pdf.models.FormWidgetInfo
+import java.util.concurrent.Executor
import kotlin.math.roundToInt
import kotlin.random.Random
@@ -184,7 +185,8 @@ internal open class FakePdfDocument(
}
override fun addOnPdfContentInvalidatedListener(
- listener: PdfDocument.OnPdfContentInvalidatedListener
+ executor: Executor,
+ listener: PdfDocument.OnPdfContentInvalidatedListener,
) {
return
}
diff --git a/pdf/pdf-document-service/src/androidTest/kotlin/androidx/pdf/PdfFormFillingTest.kt b/pdf/pdf-document-service/src/androidTest/kotlin/androidx/pdf/PdfFormFillingTest.kt
index e7845f910818a..68c2b21a601f2 100644
--- a/pdf/pdf-document-service/src/androidTest/kotlin/androidx/pdf/PdfFormFillingTest.kt
+++ b/pdf/pdf-document-service/src/androidTest/kotlin/androidx/pdf/PdfFormFillingTest.kt
@@ -28,9 +28,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
import junit.framework.TestCase.assertEquals
+import junit.framework.TestCase.assertTrue
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
@@ -839,6 +844,39 @@ class PdfFormFillingTest {
verifyApplyEditThrowsException(TEXT_FORM, setChoiceOnTF)
}
+ @Test
+ fun pdfContentInvalidatedListener_calledOnCorrectExecutorThread() = runTest {
+ withEditableDocument(CLICK_FORM) { document ->
+ var listenerThread: Thread? = null
+ val listenerLatch = CountDownLatch(1)
+
+ val callingThread: Thread = Thread.currentThread()
+ val customThreadName = "CustomThread"
+
+ val customExecutor =
+ Executors.newSingleThreadExecutor { command -> Thread(command, customThreadName) }
+
+ document.addOnPdfContentInvalidatedListener(
+ customExecutor,
+ object : PdfDocument.OnPdfContentInvalidatedListener {
+ override fun onPdfContentInvalidated(pageNumber: Int, dirtyAreas: List) {
+ listenerThread = Thread.currentThread()
+ listenerLatch.countDown()
+ }
+ },
+ )
+ val clickPoint = PdfPoint(pageNum = 0, x = 145f, y = 80f)
+ val editRec = FormEditInfo.createClick(1, clickPoint = clickPoint)
+
+ document.applyEdit(editRec)
+
+ assertTrue(listenerLatch.await(5, TimeUnit.SECONDS))
+ assertNotEquals(callingThread, listenerThread)
+ assertEquals(listenerThread?.name, customThreadName)
+ customExecutor.shutdown()
+ }
+ }
+
companion object {
private const val CLICK_FORM = "click_form.pdf"
private const val TEXT_FORM = "text_form.pdf"
@@ -892,6 +930,7 @@ class PdfFormFillingTest {
) {
withEditableDocument(fileName) { document ->
document.addOnPdfContentInvalidatedListener(
+ { command -> command.run() },
object : PdfDocument.OnPdfContentInvalidatedListener {
override fun onPdfContentInvalidated(
pageNumber: Int,
@@ -899,7 +938,7 @@ class PdfFormFillingTest {
) {
assertThat(fullyContains(expectedDirtyArea, dirtyAreas)).isTrue()
}
- }
+ },
)
val formWidgetInfos =
document.getFormWidgetInfos(pageNum, intArrayOf(before.widgetType))
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 9a3fb73e5893a..889a449512a8e 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
@@ -45,6 +45,7 @@ import androidx.pdf.service.connect.PdfServiceConnection
import androidx.pdf.utils.toAndroidClass
import androidx.pdf.utils.toContentClass
import java.util.concurrent.CopyOnWriteArrayList
+import java.util.concurrent.Executor
import java.util.concurrent.TimeoutException
import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.CoroutineContext
@@ -101,7 +102,7 @@ public class SandboxedPdfDocument(
private val closeScope = CoroutineScope(coroutineContext + SupervisorJob())
private val onPdfContentInvalidatedListeners:
- CopyOnWriteArrayList =
+ CopyOnWriteArrayList> =
CopyOnWriteArrayList()
/**
@@ -234,23 +235,24 @@ public class SandboxedPdfDocument(
}
override fun addOnPdfContentInvalidatedListener(
- listener: PdfDocument.OnPdfContentInvalidatedListener
+ executor: Executor,
+ listener: PdfDocument.OnPdfContentInvalidatedListener,
) {
- onPdfContentInvalidatedListeners.add(listener)
+ onPdfContentInvalidatedListeners.add(Pair(executor, listener))
}
override fun removeOnPdfContentInvalidatedListener(
listener: PdfDocument.OnPdfContentInvalidatedListener
) {
- onPdfContentInvalidatedListeners.remove(listener)
+ onPdfContentInvalidatedListeners.removeIf { it.second == listener }
}
override suspend fun applyEdit(record: FormEditInfo) {
val dirtyAreas = withDocument { document ->
document.applyEdit(record.pageNumber, record.toAndroidClass())
}
- onPdfContentInvalidatedListeners.forEach {
- it.onPdfContentInvalidated(record.pageNumber, dirtyAreas)
+ onPdfContentInvalidatedListeners.forEach { (executor, listener) ->
+ executor.execute { listener.onPdfContentInvalidated(record.pageNumber, dirtyAreas) }
}
}
diff --git a/pdf/pdf-document-service/src/test/kotlin/androidx/pdf/FakeEditablePdfDocument.kt b/pdf/pdf-document-service/src/test/kotlin/androidx/pdf/FakeEditablePdfDocument.kt
index 823556399523c..cf4a2dad63e0c 100644
--- a/pdf/pdf-document-service/src/test/kotlin/androidx/pdf/FakeEditablePdfDocument.kt
+++ b/pdf/pdf-document-service/src/test/kotlin/androidx/pdf/FakeEditablePdfDocument.kt
@@ -30,6 +30,7 @@ import androidx.pdf.content.PageSelection
import androidx.pdf.models.FormEditInfo
import androidx.pdf.models.FormWidgetInfo
import java.util.UUID
+import java.util.concurrent.Executor
/** Fake implementation of [EditablePdfDocument] for testing. */
internal class FakeEditablePdfDocument(
@@ -50,7 +51,8 @@ internal class FakeEditablePdfDocument(
}
override fun addOnPdfContentInvalidatedListener(
- listener: PdfDocument.OnPdfContentInvalidatedListener
+ executor: Executor,
+ listener: PdfDocument.OnPdfContentInvalidatedListener,
) {
TODO("Not yet implemented")
}
diff --git a/pdf/pdf-ink/build.gradle b/pdf/pdf-ink/build.gradle
index 750da8b131832..5705ce05ddd03 100644
--- a/pdf/pdf-ink/build.gradle
+++ b/pdf/pdf-ink/build.gradle
@@ -59,8 +59,6 @@ dependencies {
testImplementation(libs.testCoreKtx)
testImplementation(libs.testExtJunitKtx)
testImplementation(libs.kotlinCoroutinesTest)
- testImplementation(libs.mockitoCore)
- testImplementation(libs.mockitoKotlin4)
androidTestImplementation(libs.junit)
androidTestImplementation(libs.testRunner)
@@ -71,9 +69,7 @@ dependencies {
androidTestImplementation(libs.kotlinCoroutinesTest)
androidTestImplementation(libs.espressoCore)
androidTestImplementation(libs.bundles.espressoContrib)
- androidTestImplementation(libs.espressoIdlingResource)
androidTestImplementation(project(":test:screenshot:screenshot"))
- androidTestImplementation("androidx.fragment:fragment-testing:1.7.1")
}
androidx {
diff --git a/pdf/pdf-ink/src/androidTest/assets/sample_form.pdf b/pdf/pdf-ink/src/androidTest/assets/sample_form.pdf
deleted file mode 100755
index ab9460180429c..0000000000000
Binary files a/pdf/pdf-ink/src/androidTest/assets/sample_form.pdf and /dev/null differ
diff --git a/pdf/pdf-ink/src/androidTest/kotlin/ScubaConstants.kt b/pdf/pdf-ink/src/androidTest/kotlin/ScubaConstants.kt
index 099a82474850d..02f69c38a200f 100644
--- a/pdf/pdf-ink/src/androidTest/kotlin/ScubaConstants.kt
+++ b/pdf/pdf-ink/src/androidTest/kotlin/ScubaConstants.kt
@@ -34,14 +34,6 @@ internal const val ANNOTATION_TOOLBAR_IN_DARK_MODE = "annotation_toolbar_in_dark
internal const val ANNOTATION_TOOLBAR_IN_LIGHT_MODE = "annotation_toolbar_in_light_mode"
internal const val ANNOTATION_TOOLBAR_COLLAPSED = "annotation_toolbar_collapsed"
-internal const val ANNOTATION_TOOLBAR_DOCKED_START_WITH_BRUSH_SIZE =
- "annotation_toolbar_docked_start_with_brush_size"
-internal const val ANNOTATION_TOOLBAR_DOCKED_START_WITH_COLOR_PALETTE =
- "annotation_toolbar_docked_start_with_color_palette"
-internal const val ANNOTATION_TOOLBAR_DOCKED_END_WITH_BRUSH_SIZE =
- "annotation_toolbar_docked_end_with_brush_size"
-internal const val ANNOTATION_TOOLBAR_DOCKED_END_WITH_COLOR_PALETTE =
- "annotation_toolbar_docked_end_with_color_palette"
internal const val BRUSH_SIZE_SELECTED_ON_STEP_0 = "brush_size_selector_on_step_0"
internal const val BRUSH_SIZE_SELECTED_ON_STEP_4 = "brush_size_selector_on_step_4"
internal const val BRUSH_SIZE_IN_VERTICAL_ORIENTATION = "brush_size_in_vertical_orientation"
diff --git a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/FakeEditablePdfDocument.kt b/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/FakeEditablePdfDocument.kt
index 5eac2768c01c4..a94d1a795be50 100644
--- a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/FakeEditablePdfDocument.kt
+++ b/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/FakeEditablePdfDocument.kt
@@ -5,7 +5,7 @@
* 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
+ * 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,
@@ -41,6 +41,7 @@ import androidx.pdf.models.FormEditInfo
import androidx.pdf.models.FormWidgetInfo
import androidx.pdf.models.ListItem
import java.util.UUID
+import java.util.concurrent.Executor
import kotlin.random.Random
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -277,7 +278,8 @@ internal open class FakeEditablePdfDocument(
}
override fun addOnPdfContentInvalidatedListener(
- listener: PdfDocument.OnPdfContentInvalidatedListener
+ executor: Executor,
+ listener: PdfDocument.OnPdfContentInvalidatedListener,
) {}
override fun removeOnPdfContentInvalidatedListener(
diff --git a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/fragment/EditablePdfViewerFragmentTests.kt b/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/fragment/EditablePdfViewerFragmentTests.kt
deleted file mode 100644
index f4b47bc22c9f8..0000000000000
--- a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/fragment/EditablePdfViewerFragmentTests.kt
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright 2026 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.ink.fragment
-
-import android.content.pm.ActivityInfo
-import android.os.Build
-import android.os.ext.SdkExtensions
-import android.widget.LinearLayout
-import androidx.annotation.RequiresExtension
-import androidx.fragment.app.testing.FragmentScenario
-import androidx.fragment.app.testing.launchFragmentInContainer
-import androidx.lifecycle.Lifecycle
-import androidx.pdf.R as PdfR
-import androidx.pdf.ink.R
-import androidx.pdf.ink.view.AnnotationToolbar
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_BOTTOM
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_END
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_START
-import androidx.pdf.util.FragmentTestUtils.scenarioLoadDocument
-import androidx.pdf.util.Preconditions
-import androidx.pdf.util.ToolbarMatchers.matchesToolbarMask
-import androidx.pdf.util.ToolbarMatchers.withDockState
-import androidx.pdf.util.ToolbarViewActions
-import androidx.pdf.util.ToolbarViewActions.performDragAndDrop
-import androidx.test.espresso.Espresso.onIdle
-import androidx.test.espresso.Espresso.onView
-import androidx.test.espresso.IdlingRegistry
-import androidx.test.espresso.action.ViewActions
-import androidx.test.espresso.action.ViewActions.click
-import androidx.test.espresso.assertion.ViewAssertions.matches
-import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
-import androidx.test.espresso.matcher.ViewMatchers.withId
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.LargeTest
-import com.google.common.truth.Truth.assertThat
-import org.hamcrest.CoreMatchers.not
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@LargeTest
-@RunWith(AndroidJUnit4::class)
-@RequiresExtension(extension = Build.VERSION_CODES.S, version = 18)
-class EditablePdfViewerFragmentTests {
- private lateinit var scenario: FragmentScenario
-
- @Before
- fun setup() {
- scenario =
- launchFragmentInContainer(
- themeResId =
- com.google.android.material.R.style.Theme_Material3_DayNight_NoActionBar,
- initialState = Lifecycle.State.INITIALIZED,
- )
- .onFragment { fragment ->
- IdlingRegistry.getInstance()
- .register(fragment.pdfLoadingIdlingResource.countingIdlingResource)
- }
- }
-
- @After
- fun cleanup() {
- if (!::scenario.isInitialized) return
-
- scenario.onFragment { fragment ->
- IdlingRegistry.getInstance()
- .unregister(fragment.pdfLoadingIdlingResource.countingIdlingResource)
- }
- scenario.close()
- }
-
- private fun loadDocumentAndSetupFragment() {
- scenarioLoadDocument(
- scenario = scenario,
- filename = TEST_DOCUMENT_FILE,
- nextState = Lifecycle.State.RESUMED,
- orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
- )
- onIdle() // Wait for document to load
-
- scenario.onFragment { fragment ->
- Preconditions.checkArgument(
- fragment.documentLoaded,
- "Unable to load document due to ${fragment.documentError?.message}",
- )
- fragment.setIsAnnotationIntentResolvable(true)
- fragment.isToolboxVisible = true
- }
- }
-
- private fun enterEditMode() {
- onView(withId(PdfR.id.edit_fab)).apply {
- check(matches(isDisplayed()))
- perform(click())
- check(matches(not(isDisplayed())))
- }
- onIdle()
- }
-
- @Test
- fun test_annotationToolbar_dockedAtBottom_andCanDragToStart() {
- if (!isRequiredSdkExtensionAvailable()) return
-
- loadDocumentAndSetupFragment()
- enterEditMode()
-
- onView(withId(R.id.annotationToolbar))
- .check(matches(isDisplayed()))
- .check(matches(withDockState(DOCK_STATE_BOTTOM)))
-
- // Initiate drag event
- onView(withId(R.id.annotationToolbar)).perform(ViewActions.longClick())
- onIdle()
- // Verify toolbar collapses on long press
- onView(withId(R.id.collapsed_tool)).check(matches(isDisplayed()))
-
- // Drag to the left side of the screen
- performDragAndDrop(
- toolbarId = R.id.annotationToolbar,
- to = ToolbarViewActions.DragTarget.LEFT,
- )
- onIdle()
-
- // Verify toolbar docked to the left side
- onView(withId(R.id.annotationToolbar)).check(matches(withDockState(DOCK_STATE_START)))
-
- // Verify tool tray orientation is vertical
- scenario.onFragment { fragment ->
- val toolTray = fragment.view?.findViewById(R.id.tool_tray)
- assertThat(toolTray?.orientation).isEqualTo(LinearLayout.VERTICAL)
- }
- }
-
- @Test
- fun test_annotationToolbar_persistsDockStateThroughRotation() {
- if (!isRequiredSdkExtensionAvailable()) return
-
- loadDocumentAndSetupFragment()
- enterEditMode()
-
- // Move toolbar to the END (Right) side
- onView(withId(R.id.annotationToolbar)).perform(ViewActions.longClick())
- performDragAndDrop(
- toolbarId = R.id.annotationToolbar,
- to = ToolbarViewActions.DragTarget.RIGHT,
- )
- onIdle()
-
- // Rotate the device to Landscape
- scenario.onFragment { fragment ->
- fragment.activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
- }
- onIdle()
-
- // Verify it remains on the END side after rotation
- onView(withId(R.id.annotationToolbar)).check(matches(withDockState(DOCK_STATE_END)))
- }
-
- @Test
- fun test_toolbarMovement_updatesWetStrokesMaskPath() {
- if (!isRequiredSdkExtensionAvailable()) return
-
- loadDocumentAndSetupFragment()
- enterEditMode()
-
- var toolbar: AnnotationToolbar? = null
- scenario.onFragment { fragment ->
- toolbar = fragment.view?.findViewById(R.id.annotationToolbar)
- }
-
- // Initial check: Toolbar is at bottom, mask should be at bottom
- onView(withId(R.id.pdf_wet_strokes_view)).check(matches(matchesToolbarMask(toolbar!!)))
-
- // Move the toolbar to the START (Left) side
- onView(withId(R.id.annotationToolbar)).perform(ViewActions.longClick())
- performDragAndDrop(
- R.id.annotationToolbar,
- to = ToolbarViewActions.DragTarget.LEFT,
- ) // Using the helper from previous step
- onIdle()
-
- // Verify the mask path updated to the new location (START side)
- onView(withId(R.id.pdf_wet_strokes_view)).check(matches(matchesToolbarMask(toolbar!!)))
- }
-
- @Test
- fun test_annotationToolbar_reExpands_onLongPressWithoutMove() {
- if (!isRequiredSdkExtensionAvailable()) return
-
- loadDocumentAndSetupFragment()
- enterEditMode()
-
- // Perform Long Press but do NOT call performDragAndDrop
- onView(withId(R.id.annotationToolbar)).perform(ViewActions.longClick())
- onIdle()
-
- // Simulate releasing the touch (Action Up)
- onView(withId(R.id.annotationToolbar)).perform(click())
- // Since we didn't move, it should re-expand at same dock position
- onView(withId(R.id.tool_tray)).check(matches(isDisplayed()))
- onView(withId(R.id.collapsed_tool)).check(matches(not(isDisplayed())))
- onView(withId(R.id.annotationToolbar)).check(matches(withDockState(DOCK_STATE_BOTTOM)))
- }
-
- companion object {
- private const val TEST_DOCUMENT_FILE = "sample_form.pdf"
- private const val REQUIRED_EXTENSION_VERSION = 18
-
- fun isRequiredSdkExtensionAvailable(): Boolean {
- // Get the device's version for the specified SDK extension
- val deviceExtensionVersion = SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R)
- return deviceExtensionVersion >= REQUIRED_EXTENSION_VERSION
- }
- }
-}
diff --git a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/fragment/TestEditablePdfViewerFragment.kt b/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/fragment/TestEditablePdfViewerFragment.kt
deleted file mode 100644
index c138c731c3064..0000000000000
--- a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/fragment/TestEditablePdfViewerFragment.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright 2026 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.ink.fragment
-
-import android.os.Build
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.FrameLayout
-import android.widget.FrameLayout.LayoutParams
-import android.widget.FrameLayout.LayoutParams.MATCH_PARENT
-import androidx.annotation.RequiresExtension
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.core.view.ViewCompat
-import androidx.core.view.WindowInsetsCompat
-import androidx.pdf.PdfDocument
-import androidx.pdf.ink.EditablePdfViewerFragment
-import androidx.pdf.util.PdfIdlingResource
-import java.util.UUID
-
-/**
- * A subclass fragment from [EditablePdfViewerFragment] to include
- * [androidx.test.espresso.IdlingResource] while loading pdf document.
- */
-@RequiresExtension(extension = Build.VERSION_CODES.S, version = 18)
-internal class TestEditablePdfViewerFragment : EditablePdfViewerFragment {
-
- constructor() : super()
-
- val pdfLoadingIdlingResource = PdfIdlingResource(PDF_LOAD_RESOURCE_NAME)
-
- var pdfDocument: PdfDocument? = null
- var documentLoaded = false
- var documentError: Throwable? = null
- private var hostView: FrameLayout? = null
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?,
- ): View? {
- val view = super.onCreateView(inflater, container, savedInstanceState) as ConstraintLayout
-
- // Inflate the custom layout for this fragment
- hostView =
- FrameLayout(requireContext()).apply {
- layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)
- }
- hostView?.let { hostView -> handleInsets(hostView) }
-
- // Add the default PDF viewer to the custom layout
- hostView?.addView(view)
- return hostView
- }
-
- override fun onLoadDocumentSuccess(document: PdfDocument) {
- super.onLoadDocumentSuccess(document)
- pdfDocument = document
- documentLoaded = true
- pdfLoadingIdlingResource.decrement()
- }
-
- override fun onLoadDocumentError(error: Throwable) {
- super.onLoadDocumentError(error)
- documentError = error
- pdfLoadingIdlingResource.decrement()
- }
-
- fun setIsAnnotationIntentResolvable(value: Boolean) {
- setAnnotationIntentResolvability(value)
- }
-
- companion object {
- // Resource name must be unique to avoid conflicts while running multiple test scenarios
- private val PDF_LOAD_RESOURCE_NAME = "PdfLoad-${UUID.randomUUID()}"
-
- fun handleInsets(hostView: View) {
- ViewCompat.setOnApplyWindowInsetsListener(hostView) { view, insets ->
- // Get the insets for the system bars (status bar, navigation bar)
- val systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
-
- // Adjust the padding of the container view to accommodate system windows
- view.setPadding(
- view.paddingLeft,
- systemBarsInsets.top,
- view.paddingRight,
- systemBarsInsets.bottom,
- )
- insets
- }
- }
- }
-}
diff --git a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/view/AnnotationToolbarTest.kt b/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/view/AnnotationToolbarTest.kt
index 34b0a1e461e5f..780b60e22658e 100644
--- a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/view/AnnotationToolbarTest.kt
+++ b/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/view/AnnotationToolbarTest.kt
@@ -18,14 +18,10 @@ package androidx.pdf.ink.view
import android.content.Context
import android.view.ViewGroup.LayoutParams
-import android.widget.LinearLayout
import androidx.pdf.PdfTestActivity
import androidx.pdf.ink.R
import androidx.pdf.ink.util.setSliderValue
import androidx.pdf.ink.util.withSliderValue
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_BOTTOM
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_END
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_START
import androidx.pdf.ink.view.tool.AnnotationToolView
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onIdle
@@ -42,7 +38,6 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.google.android.material.slider.Slider
-import com.google.common.truth.Truth.assertThat
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertTrue
import kotlin.test.assertNotNull
@@ -309,61 +304,6 @@ class AnnotationToolbarTest {
assertFalse(annotationToolbar.isConfigPopupVisible)
}
- @Test
- fun testSetDockState_updatesOrientationAndConstraints() {
- var toolbar: AnnotationToolbar? = null
- setupAnnotationToolbar { toolbar = it }
-
- assertNotNull(toolbar)
-
- // Change dock state to START (Vertical)
- activityRule.scenario.onActivity { toolbar.dockState = DOCK_STATE_START }
- onIdle()
-
- // Verify tool tray orientation updated to Vertical
- val toolTray = toolbar.findViewById(R.id.tool_tray)
- assertThat(toolTray.orientation).isEqualTo(LinearLayout.VERTICAL)
-
- // Change dock state back to BOTTOM (Horizontal)
- activityRule.scenario.onActivity { toolbar.dockState = DOCK_STATE_BOTTOM }
- onIdle()
-
- // Verify tool tray orientation reverted to Horizontal
- assertThat(toolTray?.orientation).isEqualTo(LinearLayout.HORIZONTAL)
- }
-
- @Test
- fun testDockState_restoresOnConfigChange() {
- // Prepare activity with a toolbar
- PdfTestActivity.onCreateCallback = { activity ->
- activity.container.addView(
- createToolbar(activity),
- LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT),
- )
- }
-
- with(ActivityScenario.launch(PdfTestActivity::class.java)) {
- // Set dock state to END
- onActivity { activity ->
- val toolbar = activity.findViewById(ANNOTATION_TOOLBAR_VIEW_ID)
- toolbar.dockState = DOCK_STATE_END
- }
- onIdle()
-
- // Recreate activity
- recreate()
-
- // Assert dock state is restored
- onActivity { activity ->
- val toolbar = activity.findViewById(ANNOTATION_TOOLBAR_VIEW_ID)
- assertThat(toolbar.dockState).isEqualTo(DOCK_STATE_END)
-
- val toolTray = toolbar.findViewById(R.id.tool_tray)
- assertThat(toolTray.orientation).isEqualTo(LinearLayout.VERTICAL)
- }
- }
- }
-
private fun assertColorPaletteChecks() {
onView(withId(R.id.color_palette_button)).check(matches(isEnabled()))
// assert initially color palette is not visible
diff --git a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/view/AnnotationToolbarUiTest.kt b/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/view/AnnotationToolbarUiTest.kt
index 6a9f20ea0171c..b0eec8c5bc9f5 100644
--- a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/view/AnnotationToolbarUiTest.kt
+++ b/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/view/AnnotationToolbarUiTest.kt
@@ -18,10 +18,6 @@ package androidx.pdf.ink.view
import ANNOTATION_TOOLBAR
import ANNOTATION_TOOLBAR_COLLAPSED
-import ANNOTATION_TOOLBAR_DOCKED_END_WITH_BRUSH_SIZE
-import ANNOTATION_TOOLBAR_DOCKED_END_WITH_COLOR_PALETTE
-import ANNOTATION_TOOLBAR_DOCKED_START_WITH_BRUSH_SIZE
-import ANNOTATION_TOOLBAR_DOCKED_START_WITH_COLOR_PALETTE
import ANNOTATION_TOOLBAR_IN_DARK_MODE
import ANNOTATION_TOOLBAR_IN_LIGHT_MODE
import ANNOTATION_TOOLBAR_WITH_COLOR_PALETTE_VISIBLE
@@ -38,8 +34,6 @@ import androidx.pdf.ink.R
import androidx.pdf.ink.util.clickItemAt
import androidx.pdf.ink.util.setSliderValue
import androidx.pdf.ink.view.colorpalette.ColorPaletteAdapter
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_END
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_START
import androidx.pdf.ink.view.tool.AnnotationToolView
import androidx.test.espresso.Espresso.onIdle
import androidx.test.espresso.Espresso.onView
@@ -165,58 +159,6 @@ class AnnotationToolbarUiTest {
assertScreenshot(ANNOTATION_TOOLBAR_VIEW_ID, screenshotRule, ANNOTATION_TOOLBAR_COLLAPSED)
}
- @Test
- fun test_annotationToolbar_dockedStart_withBrushSlider() {
- setupAnnotationToolbar { it.dockState = DOCK_STATE_START }
- // Open brush slider
- onView(withId(R.id.pen_button)).perform(click())
-
- assertScreenshot(
- ANNOTATION_TOOLBAR_VIEW_ID,
- screenshotRule,
- ANNOTATION_TOOLBAR_DOCKED_START_WITH_BRUSH_SIZE,
- )
- }
-
- @Test
- fun test_annotationToolbar_dockedStart_withColorPalette() {
- setupAnnotationToolbar { it.dockState = DOCK_STATE_START }
- // Open color palette
- onView(withId(R.id.color_palette_button)).perform(click())
-
- assertScreenshot(
- ANNOTATION_TOOLBAR_VIEW_ID,
- screenshotRule,
- ANNOTATION_TOOLBAR_DOCKED_START_WITH_COLOR_PALETTE,
- )
- }
-
- @Test
- fun test_annotationToolbar_dockedEnd_withBrushSlider() {
- setupAnnotationToolbar { it.dockState = DOCK_STATE_END }
- // Open brush slider
- onView(withId(R.id.pen_button)).perform(click())
-
- assertScreenshot(
- ANNOTATION_TOOLBAR_VIEW_ID,
- screenshotRule,
- ANNOTATION_TOOLBAR_DOCKED_END_WITH_BRUSH_SIZE,
- )
- }
-
- @Test
- fun test_annotationToolbar_dockedEnd_withColorPalette() {
- setupAnnotationToolbar { it.dockState = DOCK_STATE_END }
- // Open color palette
- onView(withId(R.id.color_palette_button)).perform(click())
-
- assertScreenshot(
- ANNOTATION_TOOLBAR_VIEW_ID,
- screenshotRule,
- ANNOTATION_TOOLBAR_DOCKED_END_WITH_COLOR_PALETTE,
- )
- }
-
private fun setupAnnotationToolbar(
isDarkMode: Boolean = false,
callback: (AnnotationToolbar) -> Unit = {},
diff --git a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/view/draganddrop/ToolbarCoordinatorTest.kt b/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/view/draganddrop/ToolbarCoordinatorTest.kt
deleted file mode 100644
index 88ef751068469..0000000000000
--- a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/view/draganddrop/ToolbarCoordinatorTest.kt
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright 2026 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.ink.view.draganddrop
-
-import android.view.Gravity
-import android.view.ViewGroup
-import android.widget.FrameLayout
-import androidx.pdf.PdfTestActivity
-import androidx.pdf.ink.view.AnnotationToolbar
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_BOTTOM
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_END
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_START
-import androidx.pdf.util.ToolbarViewActions
-import androidx.pdf.util.ToolbarViewActions.performDragAndDrop
-import androidx.test.espresso.Espresso.onIdle
-import androidx.test.espresso.Espresso.onView
-import androidx.test.espresso.action.ViewActions.longClick
-import androidx.test.espresso.matcher.ViewMatchers.withId
-import androidx.test.ext.junit.rules.ActivityScenarioRule
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import com.google.common.truth.Truth.assertThat
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class ToolbarCoordinatorTest {
-
- @get:Rule val activityRule = ActivityScenarioRule(PdfTestActivity::class.java)
-
- private val instrumentation = InstrumentationRegistry.getInstrumentation()
- private lateinit var coordinator: ToolbarCoordinator
- private lateinit var toolbar: AnnotationToolbar
-
- @Before
- fun setUp() {
- activityRule.scenario.onActivity { activity ->
- coordinator =
- ToolbarCoordinator(activity).apply {
- id = COORDINATOR_VIEW_ID
- areAnimationsEnabled = false
- }
- toolbar =
- AnnotationToolbar(activity).apply {
- id = TOOLBAR_VIEW_ID
- areAnimationsEnabled = false
- }
- // Use FrameLayout.LayoutParams since coordinator is added to a FrameLayout container
- activity.container.addView(
- coordinator,
- FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT,
- ),
- )
- coordinator.attachToolbar(toolbar)
- }
- instrumentation.waitForIdleSync()
- }
-
- @After
- fun tearDown() {
- activityRule.scenario.onActivity { activity -> activity.container.removeAllViews() }
- PdfTestActivity.onCreateCallback = {}
- }
-
- @Test
- fun attachToolbar_setsInitialStateToBottom() {
- onIdle()
- assertThat(toolbar.dockState).isEqualTo(DOCK_STATE_BOTTOM)
-
- val params = toolbar.layoutParams as FrameLayout.LayoutParams
- assertThat(params.gravity).isEqualTo(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)
- }
-
- @Test
- fun dragAndDrop_toLeftSide_snapsToStart() {
- onView(withId(TOOLBAR_VIEW_ID)).perform(longClick())
- onIdle()
-
- performDragAndDrop(toolbarId = TOOLBAR_VIEW_ID, to = ToolbarViewActions.DragTarget.LEFT)
- onIdle()
-
- // Verify dock state updated to START
- assertThat(toolbar.dockState).isEqualTo(DOCK_STATE_START)
-
- val params = toolbar.layoutParams as FrameLayout.LayoutParams
- assertThat(params.gravity).isEqualTo(Gravity.CENTER_VERTICAL or Gravity.START)
- }
-
- @Test
- fun dragAndDrop_toRightSide_snapsToEnd() {
- onView(withId(TOOLBAR_VIEW_ID)).perform(longClick())
- onIdle()
-
- performDragAndDrop(toolbarId = TOOLBAR_VIEW_ID, to = ToolbarViewActions.DragTarget.RIGHT)
- onIdle()
-
- assertThat(toolbar.dockState).isEqualTo(DOCK_STATE_END)
- val params = toolbar.layoutParams as FrameLayout.LayoutParams
- assertThat(params.gravity).isEqualTo(Gravity.CENTER_VERTICAL or Gravity.END)
- }
-
- @Test
- fun dragAndDrop_toBottomSide_snapsToBottom() {
- // Move to Start first so we can drag back to bottom
- activityRule.scenario.onActivity { toolbar.dockState = DOCK_STATE_START }
- onIdle()
-
- onView(withId(TOOLBAR_VIEW_ID)).perform(longClick())
- onIdle()
-
- performDragAndDrop(toolbarId = TOOLBAR_VIEW_ID, to = ToolbarViewActions.DragTarget.BOTTOM)
- onIdle()
-
- assertThat(toolbar.dockState).isEqualTo(DOCK_STATE_BOTTOM)
- val params = toolbar.layoutParams as FrameLayout.LayoutParams
- assertThat(params.gravity).isEqualTo(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)
- }
-
- companion object {
- private const val COORDINATOR_VIEW_ID = 1001
- private const val TOOLBAR_VIEW_ID = 1002
- }
-}
diff --git a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/view/state/ToolbarInitializerTest.kt b/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/view/state/ToolbarInitializerTest.kt
index b32e0f77681f1..ff60101e9d5ca 100644
--- a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/view/state/ToolbarInitializerTest.kt
+++ b/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/view/state/ToolbarInitializerTest.kt
@@ -19,7 +19,6 @@ package androidx.pdf.ink.view.state
import android.content.Context
import androidx.pdf.ink.view.colorpalette.model.getHighlightPaletteItems
import androidx.pdf.ink.view.colorpalette.model.getPenPaletteItems
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState
import androidx.pdf.ink.view.tool.model.AnnotationToolsKey
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -67,7 +66,7 @@ class ToolbarInitializerTest {
.isEqualTo(defaultHighlighterColorIndex)
assertThat(initialState.highlighterState.paletteItem)
.isEqualTo(highlightPaletteItems[defaultHighlighterColorIndex])
- assertThat(initialState.dockedState).isEqualTo(ToolbarDockState.DOCK_STATE_BOTTOM)
+
assertThat(initialState.isExpanded).isTrue()
}
}
diff --git a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/util/FragmentTestUtils.kt b/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/util/FragmentTestUtils.kt
deleted file mode 100644
index fe435279d3d9c..0000000000000
--- a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/util/FragmentTestUtils.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2026 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.util
-
-import android.os.Build
-import android.os.ext.SdkExtensions
-import androidx.annotation.RequiresExtension
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.testing.FragmentScenario
-import androidx.lifecycle.Lifecycle
-import androidx.pdf.ink.fragment.TestEditablePdfViewerFragment
-import androidx.test.platform.app.InstrumentationRegistry
-
-internal object FragmentTestUtils {
-
- private const val TEST_DOCUMENT_FILE = "sample_form.pdf"
-
- @RequiresExtension(extension = Build.VERSION_CODES.S, version = 13)
- internal fun scenarioLoadDocument(
- scenario: FragmentScenario,
- filename: String = TEST_DOCUMENT_FILE,
- nextState: Lifecycle.State,
- orientation: Int,
- ): FragmentScenario {
- val context = InstrumentationRegistry.getInstrumentation().context
- val inputStream = context.assets.open(filename)
-
- scenario.moveToState(nextState)
- scenario.onFragment { it.requireActivity().requestedOrientation = orientation }
-
- // Load the document in the fragment
- scenario.onFragment { fragment ->
- if (
- fragment is TestEditablePdfViewerFragment &&
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
- SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S) >= 18
- ) {
- fragment.pdfLoadingIdlingResource.increment()
- fragment.documentUri = TestUtils.saveStream(inputStream, fragment.requireContext())
- }
- }
-
- return scenario
- }
-}
diff --git a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/util/PdfIdlingResource.kt b/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/util/PdfIdlingResource.kt
deleted file mode 100644
index 5cc185da85cf6..0000000000000
--- a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/util/PdfIdlingResource.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2026 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.util
-
-import androidx.test.espresso.idling.CountingIdlingResource
-import java.util.concurrent.atomic.AtomicInteger
-
-/**
- * A wrapper around Espresso's [CountingIdlingResource] that will help to define idling resource for
- * any background work.
- */
-internal class PdfIdlingResource(private val resourceName: String) {
-
- val countingIdlingResource: CountingIdlingResource = CountingIdlingResource(resourceName)
- val decrementCounter: AtomicInteger = AtomicInteger()
-
- fun increment() {
- countingIdlingResource.increment()
- if (decrementCounter.get() > 0) {
- decrementCounter.andDecrement
- decrement()
- }
- }
-
- fun decrement() {
- // Check if idling resource is not already idle
- if (!countingIdlingResource.isIdleNow) {
- countingIdlingResource.decrement()
- } else {
- decrementCounter.andIncrement
- }
- }
-}
diff --git a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/util/TestUtils.kt b/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/util/TestUtils.kt
deleted file mode 100644
index f7ece2a8e7f63..0000000000000
--- a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/util/TestUtils.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2026 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.util
-
-import android.content.Context
-import android.net.Uri
-import java.io.File
-import java.io.FileOutputStream
-import java.io.InputStream
-
-object TestUtils {
- private val TEMP_FILE_NAME = "temp"
- private val TEMP_FILE_TYPE = ".pdf"
-
- fun saveStream(inputStream: InputStream, context: Context): Uri {
- val tempFile = File.createTempFile(TEMP_FILE_NAME, TEMP_FILE_TYPE, context.cacheDir)
- FileOutputStream(tempFile).use { outputStream -> inputStream.copyTo(outputStream) }
- return Uri.fromFile(tempFile)
- }
-}
diff --git a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/util/ToolbarMatchers.kt b/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/util/ToolbarMatchers.kt
deleted file mode 100644
index 1b905afefd150..0000000000000
--- a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/util/ToolbarMatchers.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2026 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.util
-
-import android.graphics.RectF
-import android.view.View
-import androidx.ink.authoring.InProgressStrokesView
-import androidx.pdf.ink.view.AnnotationToolbar
-import androidx.test.espresso.matcher.BoundedMatcher
-import org.hamcrest.Matcher
-
-internal object ToolbarMatchers {
- /**
- * Verifies that the [InProgressStrokesView] has a mask that matches the bounds of the toolbar.
- */
- fun matchesToolbarMask(toolbar: AnnotationToolbar): Matcher {
- return object :
- BoundedMatcher(InProgressStrokesView::class.java) {
- override fun describeTo(description: org.hamcrest.Description) {
- description.appendText(
- "with mask path matching toolbar at ${toolbar.x}, ${toolbar.y}"
- )
- }
-
- override fun matchesSafely(view: InProgressStrokesView): Boolean {
- val mask = view.maskPath ?: return false
- val bounds = RectF()
- mask.computeBounds(bounds, true)
-
- // The mask should roughly match the toolbar's location and size
- return bounds.left == toolbar.x &&
- bounds.top == toolbar.y &&
- bounds.width() == toolbar.width.toFloat() &&
- bounds.height() == toolbar.height.toFloat()
- }
- }
- }
-
- /** Custom Matcher to check the internal dockState of the AnnotationToolbar. */
- fun withDockState(expectedState: Int): Matcher {
- return object : BoundedMatcher(AnnotationToolbar::class.java) {
- override fun describeTo(description: org.hamcrest.Description) {
- description.appendText("with dockState: $expectedState")
- }
-
- override fun matchesSafely(toolbar: AnnotationToolbar): Boolean {
- return toolbar.dockState == expectedState
- }
- }
- }
-}
diff --git a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/util/ToolbarViewActions.kt b/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/util/ToolbarViewActions.kt
deleted file mode 100644
index 593fd3f6e001f..0000000000000
--- a/pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/util/ToolbarViewActions.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2026 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.util
-
-import android.view.View
-import androidx.pdf.ink.view.draganddrop.ToolbarCoordinator
-import androidx.test.espresso.Espresso.onView
-import androidx.test.espresso.action.GeneralLocation
-import androidx.test.espresso.action.GeneralSwipeAction
-import androidx.test.espresso.action.Press
-import androidx.test.espresso.action.Swipe
-import androidx.test.espresso.matcher.ViewMatchers.withId
-
-internal object ToolbarViewActions {
-
- // Space from boundary of the view
- private const val DELTA_FROM_BOUNDARY = 10f
-
- /**
- * Performs a drag and drop action from the center of the toolbar to a specified target edge of
- * the [ToolbarCoordinator].
- *
- * @param to The target edge to drag the toolbar towards.
- */
- fun performDragAndDrop(toolbarId: Int, to: DragTarget) {
- onView(withId(toolbarId))
- .perform(
- GeneralSwipeAction(
- Swipe.FAST,
- GeneralLocation.CENTER,
- { view ->
- val coordinator = view.parent as View
- val screenPos = IntArray(2)
- coordinator.getLocationOnScreen(screenPos)
- val x: Float
- val y: Float
-
- when (to) {
- DragTarget.LEFT -> {
- x = screenPos[0].toFloat() + DELTA_FROM_BOUNDARY
- y = screenPos[1].toFloat() + (coordinator.height / 2f)
- }
- DragTarget.RIGHT -> {
- x = screenPos[0].toFloat() + coordinator.width - DELTA_FROM_BOUNDARY
- y = screenPos[1].toFloat() + (coordinator.height / 2f)
- }
- DragTarget.BOTTOM -> {
- x = screenPos[0].toFloat() + (coordinator.width / 2f)
- y =
- screenPos[1].toFloat() + coordinator.height -
- DELTA_FROM_BOUNDARY
- }
- }
- floatArrayOf(x, y)
- },
- Press.FINGER,
- )
- )
- }
-
- enum class DragTarget {
- LEFT,
- RIGHT,
- BOTTOM,
- }
-}
diff --git a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/EditablePdfViewerFragment.kt b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/EditablePdfViewerFragment.kt
index 8205e5971021b..5992c7f07bf0c 100644
--- a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/EditablePdfViewerFragment.kt
+++ b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/EditablePdfViewerFragment.kt
@@ -30,7 +30,6 @@ import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams
import androidx.annotation.RequiresExtension
import androidx.annotation.RestrictTo
import androidx.constraintlayout.widget.ConstraintLayout
@@ -60,7 +59,6 @@ import androidx.pdf.ink.util.PageTransformCalculator
import androidx.pdf.ink.util.toHighlighterConfig
import androidx.pdf.ink.util.toInkBrush
import androidx.pdf.ink.view.AnnotationToolbar
-import androidx.pdf.ink.view.draganddrop.ToolbarCoordinator
import androidx.pdf.ink.view.tool.AnnotationToolInfo
import androidx.pdf.view.PdfContentLayout
import androidx.pdf.view.PdfView
@@ -204,25 +202,6 @@ public open class EditablePdfViewerFragment : PdfViewerFragment {
private lateinit var wetStrokesViewTouchHandler: WetStrokesViewTouchHandler
private lateinit var pdfContentLayoutTouchListener: PdfContentLayoutTouchListener
private lateinit var annotationToolbar: AnnotationToolbar
-
- private lateinit var toolbarCoordinator: ToolbarCoordinator
-
- private val toolbarLayoutChangeListener =
- View.OnLayoutChangeListener {
- v,
- left,
- top,
- right,
- bottom,
- oldLeft,
- oldTop,
- oldRight,
- oldBottom ->
- if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) {
- wetStrokesView.maskPath = createToolbarMaskPath()
- }
- }
-
private lateinit var pageInfoProvider: PageInfoProviderImpl
private val annotationsViewDispatcher = AnnotationsViewTouchEventDispatcher()
@@ -281,7 +260,6 @@ public open class EditablePdfViewerFragment : PdfViewerFragment {
wetStrokesView =
InProgressStrokesView(requireContext()).apply {
- id = R.id.pdf_wet_strokes_view
layoutParams =
ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
@@ -299,12 +277,9 @@ public open class EditablePdfViewerFragment : PdfViewerFragment {
ViewGroup.LayoutParams.MATCH_PARENT,
)
}
- annotationToolbar =
- inflater.inflate(R.layout.annotation_toolbar_layout, null, false) as AnnotationToolbar
- toolbarCoordinator =
- ToolbarCoordinator(requireContext()).apply {
- layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
- }
+
+ inflater.inflate(R.layout.annotation_toolbar_layout, rootView, true)
+ annotationToolbar = rootView.findViewById(R.id.annotationToolbar)
val pdfContentLayout =
rootView.findViewById(
@@ -313,8 +288,6 @@ public open class EditablePdfViewerFragment : PdfViewerFragment {
pdfContentLayout.addView(annotationView)
pdfContentLayout.addView(wetStrokesView)
- rootView.addView(toolbarCoordinator)
-
return rootView
}
@@ -345,7 +318,6 @@ public open class EditablePdfViewerFragment : PdfViewerFragment {
setupPdfViewListeners()
setupAnnotationViewListeners()
setupAnnotationToolbar()
- setupToolbarCoordinator(annotationToolbar)
}
private fun setupAnnotationViewListeners() {
@@ -380,10 +352,6 @@ public open class EditablePdfViewerFragment : PdfViewerFragment {
}
}
- private fun setupToolbarCoordinator(toolbar: AnnotationToolbar) {
- toolbarCoordinator.apply { attachToolbar(toolbar) }
- }
-
/**
* If the document is an [EditablePdfDocument], sets it for editing and initializes draft state.
* This method must call `super.onLoadDocumentSuccess(document)` first.
@@ -414,9 +382,6 @@ public open class EditablePdfViewerFragment : PdfViewerFragment {
wetStrokesView.removeFinishedStrokesListener(wetStrokesOnFinishedListener)
annotationToolbar.setAnnotationToolbarListener(null)
pdfContainer.setOnTouchListener(null)
- if (::annotationToolbar.isInitialized) {
- annotationToolbar.removeOnLayoutChangeListener(toolbarLayoutChangeListener)
- }
}
private fun updateUiForEditMode(isEnabled: Boolean) {
@@ -431,9 +396,8 @@ public open class EditablePdfViewerFragment : PdfViewerFragment {
} else {
annotationToolbar.apply {
reset()
- wetStrokesView.maskPath = null
+ post { wetStrokesView.maskPath = null }
}
- toolbarCoordinator.updateLayout()
}
}
@@ -606,7 +570,6 @@ public open class EditablePdfViewerFragment : PdfViewerFragment {
}
private fun setupAnnotationToolbar() {
- annotationToolbar.addOnLayoutChangeListener(toolbarLayoutChangeListener)
annotationToolbar.setAnnotationToolbarListener(
object : AnnotationToolbar.AnnotationToolbarListener {
override fun onToolChanged(toolInfo: AnnotationToolInfo) {
diff --git a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/AnnotationToolbar.kt b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/AnnotationToolbar.kt
index 4abe678e446ef..f967f2d4722c3 100644
--- a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/AnnotationToolbar.kt
+++ b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/AnnotationToolbar.kt
@@ -17,20 +17,15 @@
package androidx.pdf.ink.view
import android.content.Context
-import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.os.Parcelable
import android.util.AttributeSet
import android.view.LayoutInflater
-import android.view.MotionEvent
-import android.view.View
-import android.view.ViewGroup
import android.view.animation.DecelerateInterpolator
import android.widget.LinearLayout
import android.widget.LinearLayout.HORIZONTAL
-import android.widget.LinearLayout.VERTICAL
import androidx.annotation.RestrictTo
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
@@ -48,13 +43,6 @@ import androidx.pdf.ink.view.colorpalette.model.Emoji
import androidx.pdf.ink.view.colorpalette.model.PaletteItem
import androidx.pdf.ink.view.colorpalette.model.getHighlightPaletteItems
import androidx.pdf.ink.view.colorpalette.model.getPenPaletteItems
-import androidx.pdf.ink.view.draganddrop.AnnotationToolbarTouchHandler
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_BOTTOM
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_END
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_START
-import androidx.pdf.ink.view.draganddrop.ToolbarDragListener
-import androidx.pdf.ink.view.layout.AnnotationToolbarConstraintSet
import androidx.pdf.ink.view.state.AnnotationToolbarState
import androidx.pdf.ink.view.state.ToolbarEffect
import androidx.pdf.ink.view.state.ToolbarInitializer
@@ -73,22 +61,20 @@ import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Job
import kotlinx.coroutines.android.asCoroutineDispatcher
import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.jetbrains.annotations.VisibleForTesting
/**
* A toolbar that hosts a set of annotation tools for interacting with a PDF document.
*
- * This custom [android.view.ViewGroup] contains a predefined set of [AnnotationToolView] buttons
- * such as pen, highlighter, eraser, etc. aligned based on the [dockState] set.
+ * This custom [android.view.ViewGroup] contains a predefined set of [AnnotaonToolView] buttons such
+ * as pen, highlighter, eraser, etc. aligned based on the [LinearLayout.orientation] set.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class AnnotationToolbar
@JvmOverloads
constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
- ConstraintLayout(context, attrs, defStyle), ToolbarDockState {
+ ConstraintLayout(context, attrs, defStyle) {
private val viewModel =
AnnotationToolbarViewModel(ToolbarInitializer.createInitialState(context))
@@ -96,9 +82,9 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
/**
* A [android.view.ViewGroup] containing all the annotation tools button.
*
- * Custom tools can be dynamically added to this container using [ViewGroup.addView].
+ * Custom tools can be dynamically added to this container using [LinearLayout.addView].
*/
- public val toolTray: ViewGroup
+ public val toolTray: LinearLayout
/**
* Controls the enabled state of the undo button.
@@ -153,25 +139,6 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
viewModel.updateState(ToolbarInitializer.createInitialState(context = context))
}
- override var dockState: Int
- get() = viewModel.state.value.dockedState
- set(value) {
- if (viewModel.state.value.dockedState == value) return
-
- viewModel.onAction(ToolbarIntent.DockStateChanged(value))
- }
-
- /**
- * Sets a listener to receive drag events when the toolbar is being moved and docked.
- *
- * @param dragListener The [ToolbarDragListener] to be notified of drag lifecycle events.
- */
- public fun setOnToolbarDragListener(dragListener: ToolbarDragListener) {
- toolbarTouchHandler.setOnDragListener(dragListener)
- }
-
- private val constraintSet = AnnotationToolbarConstraintSet(this.context)
-
private val pen: AnnotationToolView
private val highlighter: AnnotationToolView
private val eraser: AnnotationToolView
@@ -198,14 +165,6 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
ContextCompat.getDrawable(context, R.drawable.color_palette_icon_stroke)?.mutate()
}
- private val toolbarTouchHandler: AnnotationToolbarTouchHandler by lazy {
- AnnotationToolbarTouchHandler(this) { event ->
- // Intercepting a long press during a slide on the brush size selector is unintended.
- // Ignore long press detection when the touch target is the brush size selector.
- (brushSizeSelectorView.isVisible && brushSizeSelectorView.isTouchInView(event))
- }
- }
-
// Required to disable any animation while performing screenshot tests
@VisibleForTesting internal var areAnimationsEnabled: Boolean = true
@@ -254,6 +213,9 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
}
private fun setupToolTray() {
+ // default orientation
+ toolTray.orientation = HORIZONTAL
+
// Set click listeners for all tool views
pen.setOnClickListener { viewModel.onAction(ToolbarIntent.PenToolClicked) }
highlighter.setOnClickListener { viewModel.onAction(ToolbarIntent.HighlighterToolClicked) }
@@ -295,17 +257,6 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
}
private suspend fun collectUiStates() = coroutineScope {
- /**
- * Collects any expensive UI updates (such as the toolbar's `dockedState`) only when they
- * are distinct from the previous state.
- */
- launch {
- viewModel.state
- .map { it.dockedState }
- .distinctUntilChanged()
- .collect { dockedState -> updateDockState(dockedState) }
- }
-
launch {
viewModel.state.collect { state ->
updateExpandedState(state)
@@ -506,16 +457,6 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
}
}
- override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
- return toolbarTouchHandler.onInterceptTouchEvent(event)
- }
-
- override fun onTouchEvent(event: MotionEvent?): Boolean {
- if (event == null) return super.onTouchEvent(event)
-
- return toolbarTouchHandler.onTouchEvent(event) || super.onTouchEvent(event)
- }
-
private fun updateExpandedState(state: AnnotationToolbarState) {
if (areAnimationsEnabled) {
val transition = AutoTransition().apply { duration = AUTO_TRANSITION_DURATION }
@@ -526,31 +467,6 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
collapsedIcon.isVisible = !state.isExpanded
}
- private fun updateDockState(dockedState: Int) {
- when (dockedState) {
- DOCK_STATE_START -> {
- if (toolTray is LinearLayout) toolTray.orientation = VERTICAL
- undoRedoContainer.orientation = VERTICAL
- brushSizeSelectorView.orientation = VERTICAL
- constraintSet.dockStateStart.applyTo(this)
- }
-
- DOCK_STATE_BOTTOM -> {
- if (toolTray is LinearLayout) toolTray.orientation = HORIZONTAL
- undoRedoContainer.orientation = HORIZONTAL
- brushSizeSelectorView.orientation = HORIZONTAL
- constraintSet.dockStateBottom.applyTo(this)
- }
-
- DOCK_STATE_END -> {
- if (toolTray is LinearLayout) toolTray.orientation = VERTICAL
- undoRedoContainer.orientation = VERTICAL
- brushSizeSelectorView.orientation = VERTICAL
- constraintSet.dockStateEnd.applyTo(this)
- }
- }
- }
-
/**
* Interface definition for a callback to be invoked when interaction occurs with the
* [AnnotationToolbar].
@@ -565,10 +481,10 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
*/
public fun onToolChanged(toolInfo: AnnotationToolInfo)
- /** Called when an undo button is clicked if [canUndo] is set to enable. */
+ /** Called when a undo button is clicked if [canUndo] is set to enabled. */
public fun onUndo()
- /** Called when a redo button is clicked if [canRedo] is set to enable. */
+ /** Called when a redo button is clicked if [canRedo] is set to enabled. */
public fun onRedo()
/**
@@ -587,10 +503,3 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
private const val AUTO_TRANSITION_DURATION = 250L
}
}
-
-/** Helper function to check if a touch event is within the bounds of a given view. */
-internal fun View.isTouchInView(event: MotionEvent): Boolean {
- val viewRect = Rect()
- getHitRect(viewRect)
- return viewRect.contains(event.x.toInt(), event.y.toInt())
-}
diff --git a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/AnnotationToolbarViewModel.kt b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/AnnotationToolbarViewModel.kt
index 2927efe5e6f74..882c29e56309b 100644
--- a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/AnnotationToolbarViewModel.kt
+++ b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/AnnotationToolbarViewModel.kt
@@ -93,8 +93,6 @@ internal class AnnotationToolbarViewModel(initialState: AnnotationToolbarState)
is ToolbarIntent.BrushSizeChanged -> onBrushSizeChanged(intent)
is ToolbarIntent.ColorSelected -> onColorSelected(intent)
is ToolbarIntent.DismissPopups -> hideAnyPopup()
- is ToolbarIntent.DockStateChanged ->
- _state.value = _state.value.copy(dockedState = intent.dockedState)
is ToolbarIntent.ExpandToolbar -> expandOrCollapseToolbar(isExpanded = true)
is ToolbarIntent.CollapseToolbar -> expandOrCollapseToolbar(isExpanded = false)
}
diff --git a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/draganddrop/AnchorManager.kt b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/draganddrop/AnchorManager.kt
deleted file mode 100644
index 14c29d0d1a750..0000000000000
--- a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/draganddrop/AnchorManager.kt
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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.ink.view.draganddrop
-
-import android.view.View
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_BOTTOM
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_END
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_START
-import kotlin.math.hypot
-
-/**
- * Manages a set of views that act as anchor points for a drag-and-drop operation.
- *
- * This class is responsible for:
- * - Toggling the visibility of the anchor views.
- * - Calculating which anchor is closest to a given point on the screen.
- * - Providing visual feedback by highlighting the closest (active) anchor.
- *
- * @param left The [View] representing the anchor on the start/left side.
- * @param right The [View] representing the anchor on the end/right side.
- * @param bottom The [View] representing the anchor on the bottom.
- */
-internal class AnchorManager(
- private val left: View,
- private val right: View,
- private val bottom: View,
-) {
-
- private val anchors =
- mapOf(DOCK_STATE_START to left, DOCK_STATE_END to right, DOCK_STATE_BOTTOM to bottom)
-
- fun showAnchors() {
- anchors.values.forEach { view ->
- // Reset to inactive state initially
- view.alpha = ALPHA_INACTIVE
- view.visibility = View.VISIBLE
- }
- }
-
- fun hideAnchors() {
- anchors.values.forEach { it.visibility = View.GONE }
- }
-
- /** Calculates nearest anchor, updates UI highlights, and returns the closest State. */
- fun updateHighlightingAndGetClosest(
- currentX: Float,
- currentY: Float,
- viewWidth: Int,
- viewHeight: Int,
- ): Int {
- val centerX = currentX + viewWidth / 2
- val centerY = currentY + viewHeight / 2
-
- val closestEntry = anchors.minByOrNull { (_, view) -> getDistance(centerX, centerY, view) }
-
- anchors.values.forEach { it.alpha = ALPHA_INACTIVE }
-
- // Highlight the closest one and return its state.
- closestEntry?.let { (state, view) ->
- view.alpha = ALPHA_ACTIVE
- return state
- }
-
- return DOCK_STATE_BOTTOM
- }
-
- fun getAnchorView(@ToolbarDockState.DockState state: Int): View? = anchors[state]
-
- private fun getDistance(x1: Float, y1: Float, target: View): Float {
- val x2 = target.x + target.width / 2
- val y2 = target.y + target.height / 2
- return hypot(x1 - x2, y1 - y2)
- }
-
- companion object {
- // Opacity values for visual feedback
- private const val ALPHA_ACTIVE = 0.8f
- private const val ALPHA_INACTIVE = 0.3f
- }
-}
diff --git a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/draganddrop/AnnotationToolbarTouchHandler.kt b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/draganddrop/AnnotationToolbarTouchHandler.kt
deleted file mode 100644
index ff0c391dc85ee..0000000000000
--- a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/draganddrop/AnnotationToolbarTouchHandler.kt
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright 2026 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.ink.view.draganddrop
-
-import android.view.GestureDetector
-import android.view.HapticFeedbackConstants
-import android.view.MotionEvent
-import android.view.View
-import android.view.ViewConfiguration
-import kotlin.math.abs
-
-/**
- * A delegate class that encapsulates all touch handling logic for the
- * [androidx.pdf.ink.view.AnnotationToolbar].
- *
- * This handler is responsible for:
- * 1. **Detecting a long-press** on the contents of the toolbar to initiate a drag operation.
- * 2. **Notifying an external [ToolbarDragListener]** about the start, move, and end of a drag
- * gesture, without handling the view movement itself.
- *
- * @param toolbarView The [androidx.pdf.ink.view.AnnotationToolbar] instance whose touches are being
- * handled.
- * @param isTouchOnInteractiveChild A lambda to verify if long press is registered on long press
- * interactive child(for e.g. brush size selector).
- */
-internal class AnnotationToolbarTouchHandler(
- private val toolbarView: View,
- private val isTouchOnInteractiveChild: (MotionEvent) -> Boolean,
-) {
- private var isDragging = false
- private val touchSlop = ViewConfiguration.get(toolbarView.context).scaledTouchSlop
-
- private var dragListener: ToolbarDragListener? = null
-
- fun setOnDragListener(listener: ToolbarDragListener) {
- dragListener = listener
- }
-
- private val gestureDetector: GestureDetector =
- GestureDetector(
- toolbarView.context,
- object : GestureDetector.SimpleOnGestureListener() {
- override fun onLongPress(event: MotionEvent) {
- isDragging = true
- // Critical: Stop children (buttons) from handling this touch any further
- toolbarView.parent.requestDisallowInterceptTouchEvent(true)
- toolbarView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
- dragListener?.onDragStart(event)
- }
-
- override fun onScroll(
- e1: MotionEvent?,
- e2: MotionEvent,
- distanceX: Float,
- distanceY: Float,
- ): Boolean {
- val dx = abs(e2.x - (e1?.x ?: 0f))
- val dy = abs(e2.y - (e1?.y ?: 0f))
-
- // Only make a decision once the user has moved past the touch slop threshold
- if (dx > touchSlop || dy > touchSlop) {
- gestureDetector.setIsLongpressEnabled(false)
- }
-
- // We return false here because we don't want onScroll to consume the event.
- // We only use it to detect the start of a scroll and disable long press.
- return false
- }
-
- override fun onDown(e: MotionEvent): Boolean {
- // Re-enable long press detection at the start of every new gesture.
- gestureDetector.setIsLongpressEnabled(true)
- // Necessary to continue tracking the gesture
- return true
- }
- },
- )
-
- fun onInterceptTouchEvent(event: MotionEvent): Boolean {
- if (isTouchOnInteractiveChild(event)) return false
-
- gestureDetector.onTouchEvent(event)
-
- // If dragging is in progress, "steal" the event stream from child views (buttons)
- return isDragging
- }
-
- fun onTouchEvent(event: MotionEvent): Boolean {
- if (isTouchOnInteractiveChild(event)) return false
-
- gestureDetector.onTouchEvent(event)
-
- when (event.actionMasked) {
- MotionEvent.ACTION_MOVE -> {
- if (isDragging) dragListener?.onDragMove(event)
- }
- MotionEvent.ACTION_UP,
- MotionEvent.ACTION_CANCEL -> {
- if (isDragging) dragListener?.onDragEnd()
-
- isDragging = false
- toolbarView.parent.requestDisallowInterceptTouchEvent(false)
- }
- }
-
- return isDragging
- }
-}
diff --git a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/draganddrop/ToolbarCoordinator.kt b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/draganddrop/ToolbarCoordinator.kt
deleted file mode 100644
index bb2fa9bbf5662..0000000000000
--- a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/draganddrop/ToolbarCoordinator.kt
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright 2026 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.ink.view.draganddrop
-
-import android.content.Context
-import android.util.AttributeSet
-import android.view.Gravity
-import android.view.LayoutInflater
-import android.view.MotionEvent
-import android.view.animation.OvershootInterpolator
-import android.widget.FrameLayout
-import androidx.pdf.ink.R
-import androidx.pdf.ink.view.AnnotationToolbar
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_BOTTOM
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_END
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_START
-import org.jetbrains.annotations.VisibleForTesting
-
-/**
- * A [FrameLayout] layout that manages the dragging, dropping, and docking of an
- * [AnnotationToolbar].
- *
- * This coordinator is responsible for providing visible anchor points for docking, listening for
- * drag gestures initiated on the attached toolbar, moving the toolbar in response to the user's
- * drag input and finally snapping it to the closest anchor when the drag ends.
- *
- * It also applied the correct layout parameters and orientation for toolbar's final docked state.
- */
-internal class ToolbarCoordinator(context: Context, attrs: AttributeSet? = null) :
- FrameLayout(context, attrs) {
-
- // Required to disable any animation while performing ui tests
- @VisibleForTesting internal var areAnimationsEnabled: Boolean = true
-
- private var toolbar: AnnotationToolbar? = null
-
- private val anchorManager: AnchorManager
-
- private lateinit var toolbarDragListener: ToolbarDragListener
-
- private val collapseToolWidth = resources.getDimensionPixelSize(R.dimen.annotation_tool_width)
- private val collapsedToolHeight =
- resources.getDimensionPixelSize(R.dimen.annotation_tool_height)
-
- private val margin16Dp = resources.getDimensionPixelSize(R.dimen.margin_16dp)
-
- init {
- LayoutInflater.from(context).inflate(R.layout.toolbar_coordinator, this, true)
-
- anchorManager =
- AnchorManager(
- left = findViewById(R.id.anchorLeft),
- right = findViewById(R.id.anchorRight),
- bottom = findViewById(R.id.anchorBottom),
- )
- }
-
- /**
- * Attaches the [AnnotationToolbar] to this coordinator and sets up drag-and-drop handling.
- *
- * @param view The [AnnotationToolbar] instance to manage.
- */
- fun attachToolbar(view: AnnotationToolbar) {
- // Remove if already added
- toolbar?.let { removeView(it) }
-
- this.toolbar = view
-
- addView(view)
- initializeDragAndDrop()
- updateLayout()
- }
-
- /** Re-applies the layout constraints for the toolbar's current dock state. */
- fun updateLayout() {
- toolbar?.post {
- applyDockLayoutParams(
- toolbar?.dockState ?: throw IllegalStateException("dock state not initialized")
- )
- }
- }
-
- private fun initializeDragAndDrop() {
- val toolbar = toolbar ?: return
- val isToolbarVertical = toolbar.dockState != DOCK_STATE_BOTTOM
-
- toolbarDragListener =
- object : ToolbarDragListener {
-
- override fun onDragStart(event: MotionEvent) {
- anchorManager.showAnchors()
- toolbar.collapseToolbar()
- }
-
- override fun onDragMove(event: MotionEvent) {
- val collapseViewSize =
- if (isToolbarVertical) collapsedToolHeight else collapseToolWidth
- toolbar.x = event.rawX - collapseViewSize
- toolbar.y = event.rawY - collapseViewSize
-
- anchorManager.updateHighlightingAndGetClosest(
- toolbar.x,
- toolbar.y,
- toolbar.width,
- toolbar.height,
- )
- }
-
- override fun onDragEnd() {
- val closestState =
- anchorManager.updateHighlightingAndGetClosest(
- toolbar.x,
- toolbar.y,
- toolbar.width,
- toolbar.height,
- )
-
- anchorManager.hideAnchors()
-
- snapToState(closestState)
- }
- }
-
- toolbar.setOnToolbarDragListener(toolbarDragListener)
- }
-
- /**
- * Animates the toolbar to its final position over the target anchor.
- *
- * @param state The target [ToolbarDockState] to snap to.
- */
- private fun snapToState(@ToolbarDockState.DockState state: Int) {
- val localToolbar = toolbar ?: return
- val targetAnchor = anchorManager.getAnchorView(state) ?: return
-
- // Calculate target position (centering toolbar over anchor)
- val targetX = targetAnchor.x + (targetAnchor.width / 2) - (localToolbar.width / 2)
- val targetY = targetAnchor.y + (targetAnchor.height / 2) - (localToolbar.height / 2)
-
- dockToolbar(localToolbar, targetX, targetY, state)
- }
-
- private fun dockToolbar(
- toolbar: AnnotationToolbar,
- targetX: Float,
- targetY: Float,
- state: Int,
- ) {
- if (areAnimationsEnabled) {
- toolbar
- .animate()
- .x(targetX)
- .y(targetY)
- .setDuration(SNAP_ANIMATION_DURATION)
- .setInterpolator(OvershootInterpolator(SNAP_BOUNCE_TENSION)) // The "Snap" bounce
- .withEndAction {
- applyDockLayoutParams(state)
- toolbar.post { toolbar.expandToolbar() }
- }
- .start()
- } else {
- toolbar.x = targetX
- toolbar.y = targetY
- applyDockLayoutParams(state)
- toolbar.post { toolbar.expandToolbar() }
- }
- }
-
- /**
- * Applies the final layout parameters to the toolbar based on its new docked state.
- *
- * @param state The target [ToolbarDockState].
- */
- private fun applyDockLayoutParams(@ToolbarDockState.DockState state: Int) {
- val localToolbar = toolbar ?: return
-
- val params = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
-
- // Reset toolbar translation; critical as we previously animated view.translateX and
- // view.translateY
- localToolbar.translationX = 0f
- localToolbar.translationY = 0f
-
- when (state) {
- DOCK_STATE_START -> {
- params.gravity = Gravity.CENTER_VERTICAL or Gravity.START
- params.marginStart = margin16Dp
- }
- DOCK_STATE_END -> {
- params.gravity = Gravity.CENTER_VERTICAL or Gravity.END
- params.marginEnd = margin16Dp
- }
- DOCK_STATE_BOTTOM -> {
- params.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
- params.bottomMargin = margin16Dp
- }
- }
-
- localToolbar.layoutParams = params
- localToolbar.dockState = state
- }
-
- companion object {
- private const val SNAP_ANIMATION_DURATION = 250L
- private const val SNAP_BOUNCE_TENSION = 1.0f
- }
-}
diff --git a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/draganddrop/ToolbarDockState.kt b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/draganddrop/ToolbarDockState.kt
deleted file mode 100644
index c8f70cc8a2b95..0000000000000
--- a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/draganddrop/ToolbarDockState.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2026 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.ink.view.draganddrop
-
-import androidx.annotation.IntDef
-import androidx.annotation.RestrictTo
-
-/**
- * Interface defining a contract for UI components that can be docked to specific edges of a
- * container, such as the start, end, or bottom.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public interface ToolbarDockState {
-
- /**
- * An annotation that defines the set of integer constants representing the valid docking states
- * for a component.
- */
- @Retention(AnnotationRetention.SOURCE)
- @IntDef(DOCK_STATE_START, DOCK_STATE_BOTTOM, DOCK_STATE_END)
- public annotation class DockState
-
- /**
- * The current docking state of the component.
- *
- * Implementations should update their layout and orientation according to the value of this
- * property.
- */
- @DockState public var dockState: Int
-
- public companion object {
- /**
- * Represents a state where the component is docked to the start (left, in LTR mode) edge.
- */
- public const val DOCK_STATE_START: Int = 0
-
- /** Represents a state where the component is docked to the bottom edge. */
- public const val DOCK_STATE_BOTTOM: Int = 1
-
- /**
- * Represents a state where the component is docked to the end (right, in LTR mode) edge.
- */
- public const val DOCK_STATE_END: Int = 2
- }
-}
diff --git a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/draganddrop/ToolbarDragListener.kt b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/draganddrop/ToolbarDragListener.kt
deleted file mode 100644
index ef7205acf32d7..0000000000000
--- a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/draganddrop/ToolbarDragListener.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2026 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.ink.view.draganddrop
-
-import android.view.MotionEvent
-import androidx.annotation.RestrictTo
-import androidx.pdf.ink.view.AnnotationToolbar
-
-/**
- * Interface definition for callbacks to be invoked when a drag gesture is performed on the
- * [AnnotationToolbar].
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public interface ToolbarDragListener {
- /**
- * Called when a long-press gesture is detected and a drag operation is initiated.
- *
- * @param event The raw [MotionEvent] containing the current pointer coordinates, which can be
- * used to calculate initial touch offset.
- */
- public fun onDragStart(event: MotionEvent)
-
- /**
- * Called for each movement of the user's finger across the screen during a drag.
- *
- * @param event The raw [MotionEvent] containing the current pointer coordinates, which can be
- * used to update the toolbar's position.
- */
- public fun onDragMove(event: MotionEvent)
-
- /**
- * Called when the user lifts their finger, completing the drag operation. This is typically
- * used to snap the toolbar to a final docked position.
- */
- public fun onDragEnd()
-}
diff --git a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/layout/AnnotationToolbarConstraintSet.kt b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/layout/AnnotationToolbarConstraintSet.kt
deleted file mode 100644
index 6f50009964f9f..0000000000000
--- a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/layout/AnnotationToolbarConstraintSet.kt
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright 2026 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.ink.view.layout
-
-import android.content.Context
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.pdf.ink.R
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_BOTTOM
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_END
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_START
-
-internal class AnnotationToolbarConstraintSet(context: Context) {
-
- private val margin16dp = context.resources.getDimensionPixelSize(R.dimen.margin_16dp)
- private val colorPaletteMaxWidth =
- context.resources.getDimensionPixelSize(R.dimen.color_palette_max_width)
-
- val dockStateStart: ConstraintSet = createConstraintSetFor(DOCK_STATE_START)
- val dockStateEnd: ConstraintSet = createConstraintSetFor(DOCK_STATE_END)
- val dockStateBottom: ConstraintSet = createConstraintSetFor(DOCK_STATE_BOTTOM)
-
- /**
- * Creates a new [ConstraintSet] configured for the given [ToolbarDockState.DockState]. This is
- * the main factory method that delegates to helper functions.
- */
- private fun createConstraintSetFor(@ToolbarDockState.DockState dockState: Int): ConstraintSet {
- return ConstraintSet().apply {
- applyToolTrayConstraints(dockState)
- applyColorPaletteConstraints(dockState)
- applyBrushSliderConstraints(dockState)
- }
- }
-
- /** Applies the constraints for the main `scrollable_tool_tray_container`. */
- private fun ConstraintSet.applyToolTrayConstraints(@ToolbarDockState.DockState dockState: Int) {
- clear(R.id.scrollable_tool_tray_container, ConstraintSet.TOP)
- clear(R.id.scrollable_tool_tray_container, ConstraintSet.BOTTOM)
- clear(R.id.scrollable_tool_tray_container, ConstraintSet.START)
- clear(R.id.scrollable_tool_tray_container, ConstraintSet.END)
-
- constrainWidth(R.id.scrollable_tool_tray_container, ConstraintSet.WRAP_CONTENT)
- constrainHeight(R.id.scrollable_tool_tray_container, ConstraintSet.WRAP_CONTENT)
-
- when (dockState) {
- DOCK_STATE_START -> {
- connect(
- R.id.scrollable_tool_tray_container,
- ConstraintSet.TOP,
- ConstraintSet.PARENT_ID,
- ConstraintSet.TOP,
- )
- connect(
- R.id.scrollable_tool_tray_container,
- ConstraintSet.BOTTOM,
- ConstraintSet.PARENT_ID,
- ConstraintSet.BOTTOM,
- )
- connect(
- R.id.scrollable_tool_tray_container,
- ConstraintSet.START,
- ConstraintSet.PARENT_ID,
- ConstraintSet.START,
- )
- }
- DOCK_STATE_END -> {
- connect(
- R.id.scrollable_tool_tray_container,
- ConstraintSet.TOP,
- ConstraintSet.PARENT_ID,
- ConstraintSet.TOP,
- )
- connect(
- R.id.scrollable_tool_tray_container,
- ConstraintSet.BOTTOM,
- ConstraintSet.PARENT_ID,
- ConstraintSet.BOTTOM,
- )
- connect(
- R.id.scrollable_tool_tray_container,
- ConstraintSet.END,
- ConstraintSet.PARENT_ID,
- ConstraintSet.END,
- )
- }
- DOCK_STATE_BOTTOM -> {
- connect(
- R.id.scrollable_tool_tray_container,
- ConstraintSet.START,
- ConstraintSet.PARENT_ID,
- ConstraintSet.START,
- )
- connect(
- R.id.scrollable_tool_tray_container,
- ConstraintSet.END,
- ConstraintSet.PARENT_ID,
- ConstraintSet.END,
- )
- connect(
- R.id.scrollable_tool_tray_container,
- ConstraintSet.BOTTOM,
- ConstraintSet.PARENT_ID,
- ConstraintSet.BOTTOM,
- )
- }
- }
- }
-
- /** Applies the constraints for the `color_palette` view. */
- private fun ConstraintSet.applyColorPaletteConstraints(
- @ToolbarDockState.DockState dockState: Int
- ) {
- clear(R.id.color_palette, ConstraintSet.TOP)
- clear(R.id.color_palette, ConstraintSet.BOTTOM)
- clear(R.id.color_palette, ConstraintSet.START)
- clear(R.id.color_palette, ConstraintSet.END)
- setVisibility(R.id.color_palette, ConstraintSet.GONE)
-
- when (dockState) {
- DOCK_STATE_START -> {
- constrainWidth(R.id.color_palette, colorPaletteMaxWidth)
- constrainHeight(R.id.color_palette, ConstraintSet.MATCH_CONSTRAINT)
- connect(
- R.id.color_palette,
- ConstraintSet.TOP,
- R.id.scrollable_tool_tray_container,
- ConstraintSet.TOP,
- )
- connect(
- R.id.color_palette,
- ConstraintSet.BOTTOM,
- R.id.scrollable_tool_tray_container,
- ConstraintSet.BOTTOM,
- )
- connect(
- R.id.color_palette,
- ConstraintSet.START,
- R.id.scrollable_tool_tray_container,
- ConstraintSet.END,
- )
- setMargin(R.id.color_palette, ConstraintSet.START, margin16dp)
- }
- DOCK_STATE_END -> {
- constrainWidth(R.id.color_palette, colorPaletteMaxWidth)
- constrainHeight(R.id.color_palette, ConstraintSet.MATCH_CONSTRAINT)
- connect(
- R.id.color_palette,
- ConstraintSet.TOP,
- R.id.scrollable_tool_tray_container,
- ConstraintSet.TOP,
- )
- connect(
- R.id.color_palette,
- ConstraintSet.BOTTOM,
- R.id.scrollable_tool_tray_container,
- ConstraintSet.BOTTOM,
- )
- connect(
- R.id.color_palette,
- ConstraintSet.END,
- R.id.scrollable_tool_tray_container,
- ConstraintSet.START,
- )
- setMargin(R.id.color_palette, ConstraintSet.END, margin16dp)
- }
- DOCK_STATE_BOTTOM -> {
- constrainWidth(R.id.color_palette, ConstraintSet.MATCH_CONSTRAINT)
- constrainHeight(R.id.color_palette, ConstraintSet.WRAP_CONTENT)
- connect(
- R.id.color_palette,
- ConstraintSet.START,
- R.id.scrollable_tool_tray_container,
- ConstraintSet.START,
- )
- connect(
- R.id.color_palette,
- ConstraintSet.END,
- R.id.scrollable_tool_tray_container,
- ConstraintSet.END,
- )
- connect(
- R.id.color_palette,
- ConstraintSet.BOTTOM,
- R.id.scrollable_tool_tray_container,
- ConstraintSet.TOP,
- )
- setMargin(R.id.color_palette, ConstraintSet.BOTTOM, margin16dp)
- }
- }
- }
-
- /** Applies the constraints for the `brush_size_selector` view. */
- private fun ConstraintSet.applyBrushSliderConstraints(
- @ToolbarDockState.DockState dockState: Int
- ) {
- clear(R.id.brush_size_selector, ConstraintSet.TOP)
- clear(R.id.brush_size_selector, ConstraintSet.BOTTOM)
- clear(R.id.brush_size_selector, ConstraintSet.START)
- clear(R.id.brush_size_selector, ConstraintSet.END)
- setVisibility(R.id.brush_size_selector, ConstraintSet.GONE)
-
- when (dockState) {
- DOCK_STATE_START -> {
- constrainWidth(R.id.brush_size_selector, ConstraintSet.WRAP_CONTENT)
- constrainHeight(R.id.brush_size_selector, ConstraintSet.MATCH_CONSTRAINT)
- connect(
- R.id.brush_size_selector,
- ConstraintSet.TOP,
- R.id.scrollable_tool_tray_container,
- ConstraintSet.TOP,
- )
- connect(
- R.id.brush_size_selector,
- ConstraintSet.BOTTOM,
- R.id.scrollable_tool_tray_container,
- ConstraintSet.BOTTOM,
- )
- connect(
- R.id.brush_size_selector,
- ConstraintSet.START,
- R.id.scrollable_tool_tray_container,
- ConstraintSet.END,
- )
- setMargin(R.id.brush_size_selector, ConstraintSet.START, margin16dp)
- }
- DOCK_STATE_END -> {
- constrainWidth(R.id.brush_size_selector, ConstraintSet.WRAP_CONTENT)
- constrainHeight(R.id.brush_size_selector, ConstraintSet.MATCH_CONSTRAINT)
- connect(
- R.id.brush_size_selector,
- ConstraintSet.TOP,
- R.id.scrollable_tool_tray_container,
- ConstraintSet.TOP,
- )
- connect(
- R.id.brush_size_selector,
- ConstraintSet.BOTTOM,
- R.id.scrollable_tool_tray_container,
- ConstraintSet.BOTTOM,
- )
- connect(
- R.id.brush_size_selector,
- ConstraintSet.END,
- R.id.scrollable_tool_tray_container,
- ConstraintSet.START,
- )
- setMargin(R.id.brush_size_selector, ConstraintSet.END, margin16dp)
- }
- DOCK_STATE_BOTTOM -> {
- constrainWidth(R.id.brush_size_selector, ConstraintSet.MATCH_CONSTRAINT)
- constrainHeight(R.id.brush_size_selector, ConstraintSet.WRAP_CONTENT)
- connect(
- R.id.brush_size_selector,
- ConstraintSet.START,
- R.id.scrollable_tool_tray_container,
- ConstraintSet.START,
- )
- connect(
- R.id.brush_size_selector,
- ConstraintSet.END,
- R.id.scrollable_tool_tray_container,
- ConstraintSet.END,
- )
- connect(
- R.id.brush_size_selector,
- ConstraintSet.BOTTOM,
- R.id.scrollable_tool_tray_container,
- ConstraintSet.TOP,
- )
- setMargin(R.id.brush_size_selector, ConstraintSet.BOTTOM, margin16dp)
- }
- }
- }
-}
diff --git a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/state/AnnotationToolbarState.kt b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/state/AnnotationToolbarState.kt
index a836d971ffaf7..166b5cd309e57 100644
--- a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/state/AnnotationToolbarState.kt
+++ b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/state/AnnotationToolbarState.kt
@@ -23,7 +23,6 @@ import androidx.core.os.ParcelCompat
import androidx.pdf.ink.view.brush.BrushSizeSelectorView
import androidx.pdf.ink.view.colorpalette.ColorPaletteView
import androidx.pdf.ink.view.colorpalette.model.PaletteItem
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState
import androidx.pdf.ink.view.tool.model.AnnotationToolsKey.HIGHLIGHTER
import androidx.pdf.ink.view.tool.model.AnnotationToolsKey.PEN
@@ -117,9 +116,6 @@ internal data class AnnotationToolbarState(
*/
val highlighterState: ToolAttributes,
- /** The current docking state of the toolbar. */
- @get:ToolbarDockState.DockState @param:ToolbarDockState.DockState val dockedState: Int,
-
/** Whether the toolbar is currently expanded. */
val isExpanded: Boolean,
) : Parcelable {
@@ -151,7 +147,6 @@ internal data class AnnotationToolbarState(
ToolAttributes::class.java,
)
),
- dockedState = parcel.readInt(),
isExpanded = parcel.readByte() != 0.toByte(),
)
@@ -165,7 +160,6 @@ internal data class AnnotationToolbarState(
parcel.writeByte(if (isColorPaletteVisible) 1 else 0)
parcel.writeParcelable(penState, flags)
parcel.writeParcelable(highlighterState, flags)
- parcel.writeInt(dockedState)
parcel.writeByte(if (isExpanded) 1 else 0)
}
diff --git a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/state/ToolbarInitializer.kt b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/state/ToolbarInitializer.kt
index 02642d1a38e40..04ac89f88304f 100644
--- a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/state/ToolbarInitializer.kt
+++ b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/state/ToolbarInitializer.kt
@@ -19,17 +19,11 @@ package androidx.pdf.ink.view.state
import android.content.Context
import androidx.pdf.ink.view.colorpalette.model.getHighlightPaletteItems
import androidx.pdf.ink.view.colorpalette.model.getPenPaletteItems
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_BOTTOM
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_END
import androidx.pdf.ink.view.tool.model.AnnotationToolsKey.PEN
/** Responsible for creating the fully-formed initial state for the annotation toolbar */
internal object ToolbarInitializer {
- // The smallest screen width threshold in density-independent pixels (dp) used to differentiate
- // between phone and tablet form factors.
- private const val TABLET_SMALLEST_SCREEN_WIDTH_DP = 600
-
/**
* Creates the default initial `AnnotationToolbarState`.
*
@@ -63,23 +57,7 @@ internal object ToolbarInitializer {
selectedColorIndex = defaultHighlighterColorIndex,
paletteItem = highlightPaletteItems[defaultHighlighterColorIndex],
),
- dockedState = getDefaultDockState(context),
isExpanded = true,
)
}
-
- /**
- * Determines the default dock state for the toolbar based on the device's screen size.
- * - On tablets (smallest width >= 600dp), it returns [DOCK_STATE_END] to place the toolbar
- * vertically on the side.
- * - On phones, it returns [DOCK_STATE_BOTTOM] to place the toolbar horizontally at the bottom.
- *
- * @param context The context used to access screen configuration.
- * @return The calculated default dock state.
- */
- private fun getDefaultDockState(context: Context): Int {
- val screenWidthDp = context.resources.configuration.smallestScreenWidthDp
- return if (screenWidthDp >= TABLET_SMALLEST_SCREEN_WIDTH_DP) DOCK_STATE_END
- else DOCK_STATE_BOTTOM
- }
}
diff --git a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/state/ToolbarIntentAndEffects.kt b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/state/ToolbarIntentAndEffects.kt
index d5054af05d52f..ab33f093ed837 100644
--- a/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/state/ToolbarIntentAndEffects.kt
+++ b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/state/ToolbarIntentAndEffects.kt
@@ -71,9 +71,6 @@ internal sealed interface ToolbarIntent {
/** Intent to dismiss any popups(brush slider, color palette) shown. */
object DismissPopups : ToolbarIntent
- /** Intent to update the docked state of the toolbar. */
- data class DockStateChanged(val dockedState: Int) : ToolbarIntent
-
/** Intent to expand the toolbar. */
object ExpandToolbar : ToolbarIntent
diff --git a/pdf/pdf-ink/src/main/res/drawable/anchor_background.xml b/pdf/pdf-ink/src/main/res/drawable/anchor_background.xml
deleted file mode 100644
index cbca86fb9ad8a..0000000000000
--- a/pdf/pdf-ink/src/main/res/drawable/anchor_background.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/pdf/pdf-ink/src/main/res/layout/annotation_toolbar.xml b/pdf/pdf-ink/src/main/res/layout/annotation_toolbar.xml
index 7a097226249c9..9e9216d383f96 100644
--- a/pdf/pdf-ink/src/main/res/layout/annotation_toolbar.xml
+++ b/pdf/pdf-ink/src/main/res/layout/annotation_toolbar.xml
@@ -21,29 +21,21 @@
android:animateLayoutChanges="true"
android:padding="@dimen/padding_8dp">
-
-
-
-
-
-
-
-
+
+
+
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
diff --git a/pdf/pdf-ink/src/main/res/layout/toolbar_coordinator.xml b/pdf/pdf-ink/src/main/res/layout/toolbar_coordinator.xml
deleted file mode 100644
index ceaf701c43def..0000000000000
--- a/pdf/pdf-ink/src/main/res/layout/toolbar_coordinator.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/pdf/pdf-ink/src/main/res/values/dimens.xml b/pdf/pdf-ink/src/main/res/values/dimens.xml
index eb827e2153320..4a40ef94a3ce2 100644
--- a/pdf/pdf-ink/src/main/res/values/dimens.xml
+++ b/pdf/pdf-ink/src/main/res/values/dimens.xml
@@ -22,7 +22,6 @@
12dp
16dp
8dp
- 176dp
48dp
@@ -41,8 +40,5 @@
40dp
1dp
28dp
-
- 388dp
- 72dp
\ No newline at end of file
diff --git a/pdf/pdf-ink/src/main/res/values/ids.xml b/pdf/pdf-ink/src/main/res/values/ids.xml
index 7189b7f9a811e..42f4ee8f27cfd 100644
--- a/pdf/pdf-ink/src/main/res/values/ids.xml
+++ b/pdf/pdf-ink/src/main/res/values/ids.xml
@@ -15,7 +15,5 @@
-->
-
-
\ No newline at end of file
diff --git a/pdf/pdf-ink/src/test/kotlin/androidx/pdf/ink/view/ViewExtensionsTest.kt b/pdf/pdf-ink/src/test/kotlin/androidx/pdf/ink/view/ViewExtensionsTest.kt
deleted file mode 100644
index 56fdfcbea1b65..0000000000000
--- a/pdf/pdf-ink/src/test/kotlin/androidx/pdf/ink/view/ViewExtensionsTest.kt
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright 2026 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.ink.view
-
-import android.content.Context
-import android.view.MotionEvent
-import android.view.View
-import androidx.test.core.app.ApplicationProvider
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.RobolectricTestRunner
-import org.robolectric.annotation.Config
-
-@RunWith(RobolectricTestRunner::class)
-@Config(sdk = [Config.TARGET_SDK])
-class ViewExtensionsTest {
-
- private lateinit var context: Context
- private lateinit var view: View
-
- @Before
- fun setUp() {
- context = ApplicationProvider.getApplicationContext()
- view = View(context)
- // Define the view's bounds for testing. Let's assume it's at (100, 100)
- // with a size of 200x100. So its rect is (100, 100, 300, 200).
- view.layout(100, 100, 300, 200)
- }
-
- @Test
- fun isTouchInView_whenTouchIsInside_returnsTrue() {
- // Create a touch event in the middle of the view.
- val touchEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 150f, 150f, 0)
-
- assertThat(view.isTouchInView(touchEvent)).isTrue()
- }
-
- @Test
- fun isTouchInView_whenTouchIsOutside_returnsFalse() {
- // Create a touch event far outside the view's bounds.
- val touchEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 500f, 500f, 0)
-
- assertThat(view.isTouchInView(touchEvent)).isFalse()
- }
-
- @Test
- fun isTouchInView_whenTouchIsOnLeftEdge_returnsTrue() {
- // Create a touch event exactly on the left edge.
- val touchEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 100f, 150f, 0)
-
- assertThat(view.isTouchInView(touchEvent)).isTrue()
- }
-
- @Test
- fun isTouchInView_whenTouchIsJustOutsideRightEdge_returnsFalse() {
- // The rect's right is 300, so a touch at 300.0f is outside because
- // Rect.contains checks for left <= x < right.
- val touchEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 300f, 150f, 0)
-
- assertThat(view.isTouchInView(touchEvent)).isFalse()
- }
-
- @Test
- fun isTouchInView_whenTouchIsOnTopEdge_returnsTrue() {
- // Create a touch event exactly on the top edge.
- val touchEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 150f, 100f, 0)
-
- assertThat(view.isTouchInView(touchEvent)).isTrue()
- }
-
- @Test
- fun isTouchInView_whenTouchIsJustOutsideBottomEdge_returnsFalse() {
- // The rect's bottom is 200, so a touch at 200.0f is outside.
- val touchEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 150f, 200f, 0)
-
- assertThat(view.isTouchInView(touchEvent)).isFalse()
- }
-}
diff --git a/pdf/pdf-ink/src/test/kotlin/androidx/pdf/ink/view/draganddrop/AnchorManagerTest.kt b/pdf/pdf-ink/src/test/kotlin/androidx/pdf/ink/view/draganddrop/AnchorManagerTest.kt
deleted file mode 100644
index 23d4fce3f4e98..0000000000000
--- a/pdf/pdf-ink/src/test/kotlin/androidx/pdf/ink/view/draganddrop/AnchorManagerTest.kt
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright 2026 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.ink.view.draganddrop
-
-import android.content.Context
-import android.view.View
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_BOTTOM
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_END
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_START
-import androidx.test.core.app.ApplicationProvider
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.RobolectricTestRunner
-
-@RunWith(RobolectricTestRunner::class)
-@org.robolectric.annotation.Config(sdk = [org.robolectric.annotation.Config.TARGET_SDK])
-class AnchorManagerTest {
-
- private lateinit var context: Context
- private lateinit var leftAnchor: View
- private lateinit var rightAnchor: View
- private lateinit var bottomAnchor: View
- private lateinit var anchorManager: AnchorManager
-
- private val ALPHA_ACTIVE = 0.8f
- private val ALPHA_INACTIVE = 0.3f
-
- @Before
- fun setUp() {
- context = ApplicationProvider.getApplicationContext()
- leftAnchor = View(context)
- rightAnchor = View(context)
- bottomAnchor = View(context)
-
- // Give anchors some size and position for distance calculations
- // Left: (0, 500), Right: (1000, 500), Bottom: (500, 1000)
- leftAnchor.layout(0, 450, 100, 550)
- rightAnchor.layout(900, 450, 1000, 550)
- bottomAnchor.layout(450, 950, 550, 1050)
-
- anchorManager = AnchorManager(leftAnchor, rightAnchor, bottomAnchor)
- }
-
- @Test
- fun showAnchors_makesAllAnchorsVisibleAndInactive() {
- anchorManager.showAnchors()
-
- assertThat(leftAnchor.visibility).isEqualTo(View.VISIBLE)
- assertThat(rightAnchor.visibility).isEqualTo(View.VISIBLE)
- assertThat(bottomAnchor.visibility).isEqualTo(View.VISIBLE)
-
- assertThat(leftAnchor.alpha).isEqualTo(ALPHA_INACTIVE)
- assertThat(rightAnchor.alpha).isEqualTo(ALPHA_INACTIVE)
- assertThat(bottomAnchor.alpha).isEqualTo(ALPHA_INACTIVE)
- }
-
- @Test
- fun hideAnchors_makesAllAnchorsGone() {
- anchorManager.showAnchors() // First make them visible
- anchorManager.hideAnchors()
-
- assertThat(leftAnchor.visibility).isEqualTo(View.GONE)
- assertThat(rightAnchor.visibility).isEqualTo(View.GONE)
- assertThat(bottomAnchor.visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun updateHighlighting_nearLeft_highlightsLeftAndReturnsStart() {
- // Position "toolbar" center near left anchor (50, 500)
- val state =
- anchorManager.updateHighlightingAndGetClosest(
- currentX = 0f,
- currentY = 450f,
- viewWidth = 100,
- viewHeight = 100,
- )
-
- assertThat(state).isEqualTo(DOCK_STATE_START)
- assertThat(leftAnchor.alpha).isEqualTo(ALPHA_ACTIVE)
- assertThat(rightAnchor.alpha).isEqualTo(ALPHA_INACTIVE)
- assertThat(bottomAnchor.alpha).isEqualTo(ALPHA_INACTIVE)
- }
-
- @Test
- fun updateHighlighting_nearBottom_highlightsBottomAndReturnsBottom() {
- // Position "toolbar" center near bottom anchor (500, 1000)
- val state =
- anchorManager.updateHighlightingAndGetClosest(
- currentX = 450f,
- currentY = 950f,
- viewWidth = 100,
- viewHeight = 100,
- )
-
- assertThat(state).isEqualTo(DOCK_STATE_BOTTOM)
- assertThat(bottomAnchor.alpha).isEqualTo(ALPHA_ACTIVE)
- assertThat(leftAnchor.alpha).isEqualTo(ALPHA_INACTIVE)
- }
-
- @Test
- fun getAnchorView_returnsCorrectViewForState() {
- assertThat(anchorManager.getAnchorView(DOCK_STATE_START)).isEqualTo(leftAnchor)
- assertThat(anchorManager.getAnchorView(DOCK_STATE_END)).isEqualTo(rightAnchor)
- assertThat(anchorManager.getAnchorView(DOCK_STATE_BOTTOM)).isEqualTo(bottomAnchor)
- }
-}
diff --git a/pdf/pdf-ink/src/test/kotlin/androidx/pdf/ink/view/draganddrop/AnnotationToolbarTouchHandlerTest.kt b/pdf/pdf-ink/src/test/kotlin/androidx/pdf/ink/view/draganddrop/AnnotationToolbarTouchHandlerTest.kt
deleted file mode 100644
index 03b1455066a26..0000000000000
--- a/pdf/pdf-ink/src/test/kotlin/androidx/pdf/ink/view/draganddrop/AnnotationToolbarTouchHandlerTest.kt
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright 2026 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.ink.view.draganddrop
-
-import android.content.Context
-import android.os.SystemClock
-import android.view.MotionEvent
-import android.view.View
-import android.view.ViewConfiguration
-import android.widget.FrameLayout
-import androidx.test.core.app.ApplicationProvider
-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.Mock
-import org.mockito.Mockito.never
-import org.mockito.MockitoAnnotations
-import org.mockito.kotlin.any
-import org.mockito.kotlin.spy
-import org.mockito.kotlin.verify
-import org.robolectric.RobolectricTestRunner
-import org.robolectric.Shadows.shadowOf
-import org.robolectric.shadows.ShadowLooper
-
-@RunWith(RobolectricTestRunner::class)
-@org.robolectric.annotation.Config(sdk = [org.robolectric.annotation.Config.TARGET_SDK])
-class AnnotationToolbarTouchHandlerTest {
-
- private val context = ApplicationProvider.getApplicationContext()
-
- private lateinit var dummyToolbar: View
- private lateinit var parentContainer: FrameLayout // Acts as the parent
-
- @Mock private lateinit var dragListener: ToolbarDragListener
-
- private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
-
- @Before
- fun setup() {
- // Initialize @Mock annotations
- MockitoAnnotations.openMocks(this)
-
- parentContainer = spy(FrameLayout(context))
-
- dummyToolbar = View(context, null)
-
- parentContainer.addView(dummyToolbar)
- }
-
- @Test
- fun test_onTouchEvent_onInteractiveChild_ignoresDrag() {
- val handler = AnnotationToolbarTouchHandler(dummyToolbar) { true }
- handler.setOnDragListener(dragListener)
- val event = obtainEvent(MotionEvent.ACTION_DOWN, 0f, 0f)
-
- val result = handler.onTouchEvent(event)
-
- assertFalse(result)
- verify(dragListener, never()).onDragStart(any())
- }
-
- @Test
- fun test_touchHandler_longPress_startDrag() {
- val handler = AnnotationToolbarTouchHandler(dummyToolbar) { false }
- handler.setOnDragListener(dragListener)
- val event = obtainEvent(MotionEvent.ACTION_DOWN, 0f, 0f)
-
- handler.onTouchEvent(event)
-
- // fast-forward time
- ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
-
- val shadowParent = shadowOf(parentContainer)
- // Check the actual state of the view
- assertTrue(shadowParent.disallowInterceptTouchEvent)
-
- verify(dragListener).onDragStart(any())
- }
-
- @Test
- fun test_touchHandler_continuous_actionMove_doesNotStartDrag() {
- val handler = AnnotationToolbarTouchHandler(dummyToolbar) { false }
- handler.setOnDragListener(dragListener)
-
- val downEvent = obtainEvent(MotionEvent.ACTION_DOWN, 0f, 0f)
- val moveEvent = obtainEvent(MotionEvent.ACTION_MOVE, 0f, (touchSlop + 10).toFloat())
-
- handler.onTouchEvent(downEvent)
- handler.onTouchEvent(moveEvent)
-
- ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
-
- verify(dragListener, never()).onDragStart(any())
- }
-
- @Test
- fun test_touchHandler_actionUp_endsDrag() {
- val handler = AnnotationToolbarTouchHandler(dummyToolbar) { false }
- handler.setOnDragListener(dragListener)
- val downEvent = obtainEvent(MotionEvent.ACTION_DOWN, 0f, 0f)
- val upEvent = obtainEvent(MotionEvent.ACTION_UP, 0f, 0f)
-
- handler.onTouchEvent(downEvent)
- ShadowLooper.runUiThreadTasksIncludingDelayedTasks() // Trigger Drag
- handler.onTouchEvent(upEvent)
-
- verify(dragListener).onDragEnd()
-
- val shadowParent = shadowOf(parentContainer)
- // Assert parent's disallowInterceptTouchEvent state
- assertFalse(shadowParent.disallowInterceptTouchEvent)
- }
-
- private fun obtainEvent(action: Int, x: Float, y: Float): MotionEvent {
- return MotionEvent.obtain(
- SystemClock.uptimeMillis(),
- SystemClock.uptimeMillis(),
- action,
- x,
- y,
- 0,
- )
- }
-}
diff --git a/pdf/pdf-ink/src/test/kotlin/androidx/pdf/ink/view/state/AnnotationToolbarViewModelTest.kt b/pdf/pdf-ink/src/test/kotlin/androidx/pdf/ink/view/state/AnnotationToolbarViewModelTest.kt
index 37a6966f7f280..24725af20f27b 100644
--- a/pdf/pdf-ink/src/test/kotlin/androidx/pdf/ink/view/state/AnnotationToolbarViewModelTest.kt
+++ b/pdf/pdf-ink/src/test/kotlin/androidx/pdf/ink/view/state/AnnotationToolbarViewModelTest.kt
@@ -19,7 +19,6 @@ package androidx.pdf.ink.view.state
import androidx.pdf.ink.view.AnnotationToolbarViewModel
import androidx.pdf.ink.view.brush.model.BrushSizes
import androidx.pdf.ink.view.colorpalette.model.Color
-import androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_BOTTOM
import androidx.pdf.ink.view.tool.Eraser
import androidx.pdf.ink.view.tool.Highlighter
import androidx.pdf.ink.view.tool.Pen
@@ -51,7 +50,6 @@ class AnnotationToolbarViewModelTest {
canRedo = false,
highlighterState = ToolAttributes(1, 2, Color(123, 4, 5, "blue color")),
penState = ToolAttributes(2, 3, Color(123, 4, 5, "red color")),
- dockedState = DOCK_STATE_BOTTOM,
isExpanded = true,
)
@@ -327,7 +325,6 @@ class AnnotationToolbarViewModelTest {
canRedo = false,
highlighterState = ToolAttributes(1, 2, Color(123, 4, 5, "blue color")),
penState = ToolAttributes(2, 3, Color(123, 4, 5, "red color")),
- dockedState = DOCK_STATE_BOTTOM,
isExpanded = true,
)
val collectedEffects = mutableListOf()
@@ -373,18 +370,6 @@ class AnnotationToolbarViewModelTest {
assertThat(viewmodel.state.value.isBrushSizeSliderVisible).isFalse()
}
- @Test
- fun onAction_DockedStateChanged_updatesState() {
- val viewmodel = createViewModel()
- // Assuming the initial state is DOCK_STATE_BOTTOM, change it to DOCK_STATE_END
- val newDockState =
- androidx.pdf.ink.view.draganddrop.ToolbarDockState.Companion.DOCK_STATE_END
-
- viewmodel.onAction(ToolbarIntent.DockStateChanged(newDockState))
-
- assertThat(viewmodel.state.value.dockedState).isEqualTo(newDockState)
- }
-
private fun CoroutineScope.collectInto(flow: Flow, destination: MutableList): Job {
// We launch on the receiver CoroutineScope. Dispatchers.Unconfined is good for eager tests.
return launch(Dispatchers.Unconfined) { flow.collect { destination.add(it) } }
diff --git a/pdf/pdf-viewer-fragment/src/androidTest/kotlin/androidx/pdf/viewer/document/FakePdfDocument.kt b/pdf/pdf-viewer-fragment/src/androidTest/kotlin/androidx/pdf/viewer/document/FakePdfDocument.kt
index b10808e16efb4..36d0bcbabb0dc 100644
--- a/pdf/pdf-viewer-fragment/src/androidTest/kotlin/androidx/pdf/viewer/document/FakePdfDocument.kt
+++ b/pdf/pdf-viewer-fragment/src/androidTest/kotlin/androidx/pdf/viewer/document/FakePdfDocument.kt
@@ -34,6 +34,7 @@ import androidx.pdf.content.PageMatchBounds
import androidx.pdf.content.PageSelection
import androidx.pdf.content.SelectionBoundary
import androidx.pdf.models.FormWidgetInfo
+import java.util.concurrent.Executor
import kotlin.random.Random
/**
@@ -76,7 +77,8 @@ internal open class FakePdfDocument(
}
override fun addOnPdfContentInvalidatedListener(
- listener: PdfDocument.OnPdfContentInvalidatedListener
+ executor: Executor,
+ listener: PdfDocument.OnPdfContentInvalidatedListener,
) {
TODO("Not yet implemented")
}
diff --git a/pdf/pdf-viewer-fragment/src/androidTest/kotlin/androidx/pdf/viewer/fragment/PdfDocumentViewModelTest.kt b/pdf/pdf-viewer-fragment/src/androidTest/kotlin/androidx/pdf/viewer/fragment/PdfDocumentViewModelTest.kt
index 634e5f6af38ab..1495b0f971f94 100644
--- a/pdf/pdf-viewer-fragment/src/androidTest/kotlin/androidx/pdf/viewer/fragment/PdfDocumentViewModelTest.kt
+++ b/pdf/pdf-viewer-fragment/src/androidTest/kotlin/androidx/pdf/viewer/fragment/PdfDocumentViewModelTest.kt
@@ -303,7 +303,7 @@ class PdfDocumentViewModelTest {
latch.countDown()
}
}
- document.addOnPdfContentInvalidatedListener(listener)
+ document.addOnPdfContentInvalidatedListener({ command -> command.run() }, listener)
// Bounds in content coordinates of the widget on which the edit is applied
val widgetArea = Rect(135, 70, 155, 90)
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 800ba81eccbb3..24fc245072927 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
@@ -40,6 +40,7 @@ import androidx.pdf.content.SelectionBoundary
import androidx.pdf.models.FormEditInfo
import androidx.pdf.models.FormWidgetInfo
import androidx.pdf.models.ListItem
+import java.util.concurrent.Executor
import kotlin.math.roundToInt
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
@@ -123,7 +124,8 @@ internal open class FakePdfDocument(
}
override fun addOnPdfContentInvalidatedListener(
- listener: PdfDocument.OnPdfContentInvalidatedListener
+ executor: Executor,
+ listener: PdfDocument.OnPdfContentInvalidatedListener,
) {
return
}
diff --git a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/PdfDocument.kt b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/PdfDocument.kt
index 2768eb0200307..eb9d2264cd7f7 100644
--- a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/PdfDocument.kt
+++ b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/PdfDocument.kt
@@ -33,6 +33,7 @@ import androidx.pdf.content.PdfPageLinkContent
import androidx.pdf.content.PdfPageTextContent
import androidx.pdf.models.FormWidgetInfo
import java.io.Closeable
+import java.util.concurrent.Executor
import kotlinx.coroutines.CancellationException
/** Represents a PDF document and provides methods to interact with its content. */
@@ -216,9 +217,26 @@ public interface PdfDocument : Closeable {
}
@RestrictTo(RestrictTo.Scope.LIBRARY)
- public fun addOnPdfContentInvalidatedListener(listener: OnPdfContentInvalidatedListener)
+ /**
+ * Adds a listener to receive notifications when some regions of the pdf content are
+ * invalidated.
+ *
+ * @param executor The executor on which the listener's methods will be called.
+ * @param listener The listener to add.
+ * @see [OnPdfContentInvalidatedListener]
+ */
+ public fun addOnPdfContentInvalidatedListener(
+ executor: Executor,
+ listener: OnPdfContentInvalidatedListener,
+ )
@RestrictTo(RestrictTo.Scope.LIBRARY)
+ /**
+ * Removes the listener from the list of listeners which are notified when some regions of the
+ * pdf content are invalidated.
+ *
+ * @param listener The listener to remove.
+ */
public fun removeOnPdfContentInvalidatedListener(listener: OnPdfContentInvalidatedListener)
/**
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 ea00491295748..94d6ffebd68ed 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
@@ -507,8 +507,8 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
}
/**
- * Adds the specified listener to the list of listeners that is notified when any form widget is
- * updated due to an edit action on the widget e.g. click on a radio button.
+ * Removes the specified listener from the list of listeners that is notified when any form
+ * widget is updated due to an edit action on the widget e.g. click on a radio button.
*
* @param listener The listener to remove
*/
@@ -1828,7 +1828,10 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
this.formFillingEditText = formFillingEditText
}
- localPdfDocument.addOnPdfContentInvalidatedListener(onPdfContentInvalidatedListener)
+ localPdfDocument.addOnPdfContentInvalidatedListener(
+ context.mainExecutor,
+ onPdfContentInvalidatedListener,
+ )
val fastScrollCalculator = FastScrollCalculator(context)
val fastScrollDrawer =
diff --git a/pdf/pdf-viewer/src/test/kotlin/androidx/pdf/FakePdfDocument.kt b/pdf/pdf-viewer/src/test/kotlin/androidx/pdf/FakePdfDocument.kt
index e1afc8b67927c..e0c0e92784f0b 100644
--- a/pdf/pdf-viewer/src/test/kotlin/androidx/pdf/FakePdfDocument.kt
+++ b/pdf/pdf-viewer/src/test/kotlin/androidx/pdf/FakePdfDocument.kt
@@ -38,6 +38,7 @@ import androidx.pdf.content.SelectionBoundary
import androidx.pdf.models.FormEditInfo
import androidx.pdf.models.FormWidgetInfo
import androidx.pdf.models.ListItem
+import java.util.concurrent.Executor
import kotlin.math.roundToInt
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
@@ -101,7 +102,8 @@ internal open class FakePdfDocument(
}
override fun addOnPdfContentInvalidatedListener(
- listener: PdfDocument.OnPdfContentInvalidatedListener
+ executor: Executor,
+ listener: PdfDocument.OnPdfContentInvalidatedListener,
) {
return
}
diff --git a/tracing/tracing-perfetto-handshake/api/current.ignore b/tracing/tracing-perfetto-handshake/api/current.ignore
index 503437f812227..7aa50845299be 100644
--- a/tracing/tracing-perfetto-handshake/api/current.ignore
+++ b/tracing/tracing-perfetto-handshake/api/current.ignore
@@ -5,6 +5,22 @@ AddedMethod: androidx.tracing.perfetto.handshake.PerfettoSdkHandshake#enableTrac
Added method androidx.tracing.perfetto.handshake.PerfettoSdkHandshake.enableTracingImmediate$default(androidx.tracing.perfetto.handshake.PerfettoSdkHandshake,androidx.tracing.perfetto.handshake.PerfettoSdkHandshake.LibrarySource,int,Object)
+AddedProperty: androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_ALREADY_ENABLED:
+ Added property androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_ALREADY_ENABLED
+AddedProperty: androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_CANCELLED:
+ Added property androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_CANCELLED
+AddedProperty: androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_ERROR_BINARY_MISSING:
+ Added property androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_ERROR_BINARY_MISSING
+AddedProperty: androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_ERROR_BINARY_VERIFICATION_ERROR:
+ Added property androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_ERROR_BINARY_VERIFICATION_ERROR
+AddedProperty: androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_ERROR_BINARY_VERSION_MISMATCH:
+ Added property androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_ERROR_BINARY_VERSION_MISMATCH
+AddedProperty: androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_ERROR_OTHER:
+ Added property androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_ERROR_OTHER
+AddedProperty: androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_SUCCESS:
+ Added property androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_SUCCESS
+
+
RemovedFromKotlin: androidx.tracing.perfetto.handshake.protocol.Response#getMessage():
Source breaking change: method androidx.tracing.perfetto.handshake.protocol.Response.getMessage() can no longer be resolved from Kotlin source
RemovedFromKotlin: androidx.tracing.perfetto.handshake.protocol.Response#getRequiredVersion():
diff --git a/tracing/tracing-perfetto-handshake/api/restricted_current.ignore b/tracing/tracing-perfetto-handshake/api/restricted_current.ignore
index 503437f812227..7aa50845299be 100644
--- a/tracing/tracing-perfetto-handshake/api/restricted_current.ignore
+++ b/tracing/tracing-perfetto-handshake/api/restricted_current.ignore
@@ -5,6 +5,22 @@ AddedMethod: androidx.tracing.perfetto.handshake.PerfettoSdkHandshake#enableTrac
Added method androidx.tracing.perfetto.handshake.PerfettoSdkHandshake.enableTracingImmediate$default(androidx.tracing.perfetto.handshake.PerfettoSdkHandshake,androidx.tracing.perfetto.handshake.PerfettoSdkHandshake.LibrarySource,int,Object)
+AddedProperty: androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_ALREADY_ENABLED:
+ Added property androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_ALREADY_ENABLED
+AddedProperty: androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_CANCELLED:
+ Added property androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_CANCELLED
+AddedProperty: androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_ERROR_BINARY_MISSING:
+ Added property androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_ERROR_BINARY_MISSING
+AddedProperty: androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_ERROR_BINARY_VERIFICATION_ERROR:
+ Added property androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_ERROR_BINARY_VERIFICATION_ERROR
+AddedProperty: androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_ERROR_BINARY_VERSION_MISMATCH:
+ Added property androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_ERROR_BINARY_VERSION_MISMATCH
+AddedProperty: androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_ERROR_OTHER:
+ Added property androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_ERROR_OTHER
+AddedProperty: androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_SUCCESS:
+ Added property androidx.tracing.perfetto.handshake.protocol.ResponseResultCodes#RESULT_CODE_SUCCESS
+
+
RemovedFromKotlin: androidx.tracing.perfetto.handshake.protocol.Response#getMessage():
Source breaking change: method androidx.tracing.perfetto.handshake.protocol.Response.getMessage() can no longer be resolved from Kotlin source
RemovedFromKotlin: androidx.tracing.perfetto.handshake.protocol.Response#getRequiredVersion():
diff --git a/wear/compose/compose-navigation3/src/androidTest/kotlin/androidx/wear/compose/navigation3/SwipeDismissableSceneStrategyTest.kt b/wear/compose/compose-navigation3/src/androidTest/kotlin/androidx/wear/compose/navigation3/SwipeDismissableSceneStrategyTest.kt
index 859528e4f957a..3eb8d9e947042 100644
--- a/wear/compose/compose-navigation3/src/androidTest/kotlin/androidx/wear/compose/navigation3/SwipeDismissableSceneStrategyTest.kt
+++ b/wear/compose/compose-navigation3/src/androidTest/kotlin/androidx/wear/compose/navigation3/SwipeDismissableSceneStrategyTest.kt
@@ -84,6 +84,7 @@ import androidx.wear.compose.material3.Text
import com.google.common.truth.Truth.assertThat
import kotlin.String
import kotlinx.coroutines.test.StandardTestDispatcher
+import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
@@ -227,6 +228,27 @@ class SwipeDismissableSceneStrategyTest {
assertThat(lifecycleState.currentState).isEqualTo(Lifecycle.State.DESTROYED)
}
+ @Test
+ @SdkSuppress(minSdkVersion = 36)
+ fun does_not_intercept_back_pressed_with_single_item_backstack() {
+ rule.setContentWithBackPressedDispatcher { TestNavDisplay() }
+ // If NavDisplay is handling back, 'hasEnabledCallbacks' would be true.
+ // For a single item, it should be false so the system (Activity) can handle it.
+ assertFalse(backPressedDispatcher.hasEnabledCallbacks())
+ }
+
+ @Test
+ fun does_not_crash_when_swiping_back_with_single_item_backstack() {
+ rule.setContentWithBackPressedDispatcher { TestNavDisplay() }
+
+ val result = runCatching {
+ rule.swipeRight()
+ rule.waitForIdle()
+ }
+
+ assertTrue("Caught exception while swiping back.", result.isSuccess)
+ }
+
@Test
fun displays_previous_screen_during_swipe_gesture() {
rule.setContentWithBackPressedDispatcher { WithTouchSlop(0f) { TestNavDisplay() } }
@@ -511,9 +533,8 @@ class SwipeDismissableSceneStrategyTest {
@Test
@SdkSuppress(minSdkVersion = 36)
fun press_back_during_animation_does_not_reset_animation() {
- rule.setContentWithBackPressedDispatcher { TestNavDisplay() }
- // Click to move to next destination then swipe back.
- rule.onNodeWithText(FIRST_SCREEN).performClick()
+ val backStack = mutableStateListOf(FIRST_KEY, SECOND_KEY, THIRD_KEY)
+ rule.setContentWithBackPressedDispatcher { TestNavDisplay(backStack = backStack) }
rule.waitForIdle()
@@ -527,7 +548,7 @@ class SwipeDismissableSceneStrategyTest {
// fast-forward few frames.
repeat(3) { rule.mainClock.advanceTimeByFrame() }
var previousLeft =
- rule.onNodeWithTag(TEST_TAG_SECOND_CONTAINER).fetchSemanticsNode().boundsInWindow.left
+ rule.onNodeWithTag(TEST_TAG_THIRD_CONTAINER).fetchSemanticsNode().boundsInWindow.left
// Press back while animation is running
rule.runOnIdle { backPressedDispatcher.onBackPressed() }
@@ -538,7 +559,7 @@ class SwipeDismissableSceneStrategyTest {
val currentLeft =
rule
- .onNodeWithTag(TEST_TAG_SECOND_CONTAINER)
+ .onNodeWithTag(TEST_TAG_THIRD_CONTAINER)
.fetchSemanticsNode()
.boundsInWindow
.left
@@ -718,12 +739,25 @@ class SwipeDismissableSceneStrategyTest {
}
}
}
+ entry(THIRD_KEY) {
+ Box(
+ modifier = Modifier.fillMaxSize().testTag(TEST_TAG_THIRD_CONTAINER),
+ contentAlignment = Alignment.Center,
+ ) {
+ ScalingLazyColumn(modifier = Modifier.testTag(THIRD_SCREEN)) {
+ item { Text(THIRD_SCREEN) }
+ }
+ }
+ }
}
val FIRST_KEY = "First"
val FIRST_SCREEN = "FirstTag"
val SECOND_KEY = "Second"
val SECOND_SCREEN = "SecondTag"
+ val THIRD_KEY = "Third"
+ val THIRD_SCREEN = "ThirdTag"
val SCENE_TAG = "SceneModifierTag"
val TEST_TAG_SECOND_CONTAINER = "SecondContainerTag"
+ val TEST_TAG_THIRD_CONTAINER = "ThirdContainerTag"
}
diff --git a/wear/compose/compose-navigation3/src/main/kotlin/androidx/wear/compose/navigation3/SwipeDismissableSceneStrategy.kt b/wear/compose/compose-navigation3/src/main/kotlin/androidx/wear/compose/navigation3/SwipeDismissableSceneStrategy.kt
index 51c66370fa903..3073e359d8140 100644
--- a/wear/compose/compose-navigation3/src/main/kotlin/androidx/wear/compose/navigation3/SwipeDismissableSceneStrategy.kt
+++ b/wear/compose/compose-navigation3/src/main/kotlin/androidx/wear/compose/navigation3/SwipeDismissableSceneStrategy.kt
@@ -117,7 +117,6 @@ public class SwipeDismissableSceneStrategy(
val currentEntry = entries.last()
val previousEntries = entries.dropLast(1)
val background = previousEntries.lastOrNull()
- val backEnabled = isUserSwipeEnabled && background != null
return if (Build.VERSION.SDK_INT > Build.VERSION_CODES.VANILLA_ICE_CREAM) {
// api 36+, support predictive back
@@ -125,7 +124,7 @@ public class SwipeDismissableSceneStrategy(
modifier = modifier,
currentEntry = currentEntry,
previousEntries = previousEntries,
- backEnabled = backEnabled,
+ backEnabled = isUserSwipeEnabled,
)
} else {
val swipeToDismissBoxState = state.swipeToDismissBoxState
@@ -140,7 +139,7 @@ public class SwipeDismissableSceneStrategy(
currentBackStack = entries,
previousEntries = previousEntries,
swipeToDismissBoxState = swipeToDismissBoxState,
- backEnabled = backEnabled,
+ backEnabled = isUserSwipeEnabled && background != null,
)
}
}
diff --git a/wear/tiles/tiles-tooling-preview/api/current.ignore b/wear/tiles/tiles-tooling-preview/api/current.ignore
index 7aaac90094310..9a16325187757 100644
--- a/wear/tiles/tiles-tooling-preview/api/current.ignore
+++ b/wear/tiles/tiles-tooling-preview/api/current.ignore
@@ -15,3 +15,15 @@ RemovedFromKotlin: androidx.wear.tiles.tooling.preview.TilePreviewData#getOnTile
Source breaking change: method androidx.wear.tiles.tooling.preview.TilePreviewData.getOnTileResourceRequest() can no longer be resolved from Kotlin source
RemovedFromKotlin: androidx.wear.tiles.tooling.preview.TilePreviewData#getPlatformDataValues():
Source breaking change: method androidx.wear.tiles.tooling.preview.TilePreviewData.getPlatformDataValues() can no longer be resolved from Kotlin source
+
+
+RemovedProperty: androidx.wear.tiles.tooling.preview.Preview.Container#device:
+ Source breaking change: Removed property androidx.wear.tiles.tooling.preview.Preview.Container#device
+RemovedProperty: androidx.wear.tiles.tooling.preview.Preview.Container#fontScale:
+ Source breaking change: Removed property androidx.wear.tiles.tooling.preview.Preview.Container#fontScale
+RemovedProperty: androidx.wear.tiles.tooling.preview.Preview.Container#group:
+ Source breaking change: Removed property androidx.wear.tiles.tooling.preview.Preview.Container#group
+RemovedProperty: androidx.wear.tiles.tooling.preview.Preview.Container#locale:
+ Source breaking change: Removed property androidx.wear.tiles.tooling.preview.Preview.Container#locale
+RemovedProperty: androidx.wear.tiles.tooling.preview.Preview.Container#name:
+ Source breaking change: Removed property androidx.wear.tiles.tooling.preview.Preview.Container#name
diff --git a/wear/tiles/tiles-tooling-preview/api/restricted_current.ignore b/wear/tiles/tiles-tooling-preview/api/restricted_current.ignore
index 7aaac90094310..9a16325187757 100644
--- a/wear/tiles/tiles-tooling-preview/api/restricted_current.ignore
+++ b/wear/tiles/tiles-tooling-preview/api/restricted_current.ignore
@@ -15,3 +15,15 @@ RemovedFromKotlin: androidx.wear.tiles.tooling.preview.TilePreviewData#getOnTile
Source breaking change: method androidx.wear.tiles.tooling.preview.TilePreviewData.getOnTileResourceRequest() can no longer be resolved from Kotlin source
RemovedFromKotlin: androidx.wear.tiles.tooling.preview.TilePreviewData#getPlatformDataValues():
Source breaking change: method androidx.wear.tiles.tooling.preview.TilePreviewData.getPlatformDataValues() can no longer be resolved from Kotlin source
+
+
+RemovedProperty: androidx.wear.tiles.tooling.preview.Preview.Container#device:
+ Source breaking change: Removed property androidx.wear.tiles.tooling.preview.Preview.Container#device
+RemovedProperty: androidx.wear.tiles.tooling.preview.Preview.Container#fontScale:
+ Source breaking change: Removed property androidx.wear.tiles.tooling.preview.Preview.Container#fontScale
+RemovedProperty: androidx.wear.tiles.tooling.preview.Preview.Container#group:
+ Source breaking change: Removed property androidx.wear.tiles.tooling.preview.Preview.Container#group
+RemovedProperty: androidx.wear.tiles.tooling.preview.Preview.Container#locale:
+ Source breaking change: Removed property androidx.wear.tiles.tooling.preview.Preview.Container#locale
+RemovedProperty: androidx.wear.tiles.tooling.preview.Preview.Container#name:
+ Source breaking change: Removed property androidx.wear.tiles.tooling.preview.Preview.Container#name
diff --git a/wear/tiles/tiles/api/current.txt b/wear/tiles/tiles/api/current.txt
index c43997feee225..a74f61b6aceb3 100644
--- a/wear/tiles/tiles/api/current.txt
+++ b/wear/tiles/tiles/api/current.txt
@@ -782,6 +782,8 @@ package androidx.wear.tiles {
public abstract class Material3TileService extends androidx.wear.tiles.TileService {
ctor public Material3TileService();
+ ctor public Material3TileService(optional boolean allowDynamicTheme);
+ ctor public Material3TileService(optional boolean allowDynamicTheme, optional androidx.wear.protolayout.material3.ColorScheme defaultColorScheme);
ctor public Material3TileService(optional boolean allowDynamicTheme, optional androidx.wear.protolayout.material3.ColorScheme defaultColorScheme, optional kotlinx.coroutines.CoroutineScope? serviceScope);
ctor @BytecodeOnly public Material3TileService(boolean, androidx.wear.protolayout.material3.ColorScheme!, kotlinx.coroutines.CoroutineScope!, int, kotlin.jvm.internal.DefaultConstructorMarker!);
method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) protected final void onTileEnterEvent(androidx.wear.tiles.EventBuilders.TileEnterEvent requestParams);
diff --git a/wear/tiles/tiles/api/restricted_current.txt b/wear/tiles/tiles/api/restricted_current.txt
index c43997feee225..a74f61b6aceb3 100644
--- a/wear/tiles/tiles/api/restricted_current.txt
+++ b/wear/tiles/tiles/api/restricted_current.txt
@@ -782,6 +782,8 @@ package androidx.wear.tiles {
public abstract class Material3TileService extends androidx.wear.tiles.TileService {
ctor public Material3TileService();
+ ctor public Material3TileService(optional boolean allowDynamicTheme);
+ ctor public Material3TileService(optional boolean allowDynamicTheme, optional androidx.wear.protolayout.material3.ColorScheme defaultColorScheme);
ctor public Material3TileService(optional boolean allowDynamicTheme, optional androidx.wear.protolayout.material3.ColorScheme defaultColorScheme, optional kotlinx.coroutines.CoroutineScope? serviceScope);
ctor @BytecodeOnly public Material3TileService(boolean, androidx.wear.protolayout.material3.ColorScheme!, kotlinx.coroutines.CoroutineScope!, int, kotlin.jvm.internal.DefaultConstructorMarker!);
method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) protected final void onTileEnterEvent(androidx.wear.tiles.EventBuilders.TileEnterEvent requestParams);
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/Material3TileService.kt b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/Material3TileService.kt
index 787a4a276e64e..88aa7ef86a3e0 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/Material3TileService.kt
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/Material3TileService.kt
@@ -60,7 +60,9 @@ import kotlinx.coroutines.CoroutineScope
* a main thread will be used.
* @sample androidx.wear.tiles.snippet_samples.material3TileServiceHelloWorld
*/
-public abstract class Material3TileService(
+public abstract class Material3TileService
+@JvmOverloads // For hilt support
+public constructor(
private val allowDynamicTheme: Boolean = true,
private val defaultColorScheme: ColorScheme = ColorScheme(),
private val serviceScope: CoroutineScope? = null,
diff --git a/xr/arcore/integration-tests/testapp/src/main/kotlin/androidx/xr/arcore/testapp/persistentanchors/PersistentAnchorsActivity.kt b/xr/arcore/integration-tests/testapp/src/main/kotlin/androidx/xr/arcore/testapp/persistentanchors/PersistentAnchorsActivity.kt
index a3ab157052ca1..d0d1effd30609 100644
--- a/xr/arcore/integration-tests/testapp/src/main/kotlin/androidx/xr/arcore/testapp/persistentanchors/PersistentAnchorsActivity.kt
+++ b/xr/arcore/integration-tests/testapp/src/main/kotlin/androidx/xr/arcore/testapp/persistentanchors/PersistentAnchorsActivity.kt
@@ -97,6 +97,7 @@ class PersistentAnchorsActivity : ComponentActivity() {
private val movableEntityOffset = Pose(Vector3(0f, 0.75f, -1.3f))
private val uuids = MutableStateFlow>(emptyList())
private var anchorOffset = MutableStateFlow(0f)
+ private lateinit var arDevice: ArDevice
private lateinit var renderViewpoints: List
private val panelInViewStatus = MutableStateFlow>>(emptyList())
@@ -112,6 +113,7 @@ class PersistentAnchorsActivity : ComponentActivity() {
),
onSessionAvailable = { session ->
this.session = session
+ this.arDevice = ArDevice.getInstance(session)
this.renderViewpoints = buildList {
RenderViewpoint.left(session)?.let { add(it) }
RenderViewpoint.right(session)?.let { add(it) }
@@ -134,9 +136,12 @@ class PersistentAnchorsActivity : ComponentActivity() {
startPanelInViewStatusUpdates()
lifecycleScope.launch {
- ArDevice.getInstance(session).state.collect { arDeviceState ->
- updatePlaneEntity(arDeviceState)
- }
+ arDevice.state.collect { arDeviceState -> updatePanelEntity(arDeviceState) }
+ }
+
+ session.scene.activitySpace.addOnSpaceUpdatedListener {
+ updatePanelEntity(arDevice.state.value)
+ updatePanelInViewStatusUpdates(renderViewpoints.map { it.state.value })
}
},
)
@@ -148,38 +153,42 @@ class PersistentAnchorsActivity : ComponentActivity() {
lifecycleScope.launch {
combine(cameraStateFlows) { cameraStates ->
- val mainPanelEntity = session.scene.mainPanelEntity
- val panelPoseInActivitySpace = mainPanelEntity.getPose()
- val panelPoseInPerceptionSpace =
- session.scene.activitySpace.transformPoseTo(
- panelPoseInActivitySpace,
- session.scene.perceptionSpace,
- )
- val panelSizeInMeters = mainPanelEntity.size
- val newStatus =
- cameraStates.mapIndexed { index, cameraState ->
- val isInView =
- isPanelInView(
- cameraPoseInPerceptionSpace = cameraState.pose,
- cameraFov = cameraState.fieldOfView,
- panelPoseInPerceptionSpace = panelPoseInPerceptionSpace,
- panelSizeInMeters = panelSizeInMeters,
- )
- val cameraName =
- when {
- renderViewpoints.size == 1 -> "CameraView"
- index == 0 -> "Left Eye CameraView"
- index == 1 -> "Right Eye CameraView"
- else -> "CameraView ${index + 1}"
- }
- cameraName to isInView
- }
- panelInViewStatus.value = newStatus
+ updatePanelInViewStatusUpdates(cameraStates.asList())
}
.collect {}
}
}
+ private fun updatePanelInViewStatusUpdates(cameraStates: List) {
+ val mainPanelEntity = session.scene.mainPanelEntity
+ val panelPoseInActivitySpace = mainPanelEntity.getPose()
+ val panelPoseInPerceptionSpace =
+ session.scene.activitySpace.transformPoseTo(
+ panelPoseInActivitySpace,
+ session.scene.perceptionSpace,
+ )
+ val panelSizeInMeters = mainPanelEntity.size
+ val newStatus =
+ cameraStates.mapIndexed { index, cameraState ->
+ val isInView =
+ isPanelInView(
+ cameraPoseInPerceptionSpace = cameraState.pose,
+ cameraFov = cameraState.fieldOfView,
+ panelPoseInPerceptionSpace = panelPoseInPerceptionSpace,
+ panelSizeInMeters = panelSizeInMeters,
+ )
+ val cameraName =
+ when {
+ renderViewpoints.size == 1 -> "CameraView"
+ index == 0 -> "Left Eye CameraView"
+ index == 1 -> "Right Eye CameraView"
+ else -> "CameraView ${index + 1}"
+ }
+ cameraName to isInView
+ }
+ panelInViewStatus.value = newStatus
+ }
+
private fun createTargetPanel() {
val composeView = ComposeView(this)
composeView.setContent { TargetPanel() }
@@ -195,7 +204,7 @@ class PersistentAnchorsActivity : ComponentActivity() {
configureComposeView(composeView, this)
}
- private fun updatePlaneEntity(arDeviceState: ArDevice.State) {
+ private fun updatePanelEntity(arDeviceState: ArDevice.State) {
arDeviceState.devicePose.let { headPose ->
val headScenePose =
session.scene.perceptionSpace.getScenePoseFromPerceptionPose(headPose)
diff --git a/xr/compose/compose/api/restricted_current.txt b/xr/compose/compose/api/restricted_current.txt
index d4adda068cf2e..237f9dca12e2e 100644
--- a/xr/compose/compose/api/restricted_current.txt
+++ b/xr/compose/compose/api/restricted_current.txt
@@ -331,12 +331,6 @@ package androidx.xr.compose.subspace {
field public static final androidx.xr.compose.subspace.SpatialCurvedRowDefaults INSTANCE;
}
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class SpatialExternalSurfaceDefaults {
- method @BytecodeOnly public float getSphereRadius-D9Ej5fM();
- property public androidx.compose.ui.unit.Dp sphereRadius;
- field public static final androidx.xr.compose.subspace.SpatialExternalSurfaceDefaults INSTANCE;
- }
-
public final class SpatialExternalSurfaceKt {
method @KotlinOnly @androidx.compose.runtime.Composable @androidx.xr.compose.subspace.SubspaceComposable public static void SpatialExternalSurface(androidx.xr.compose.subspace.StereoMode stereoMode, optional androidx.xr.compose.subspace.layout.SubspaceModifier modifier, optional androidx.xr.compose.subspace.layout.SpatialFeatheringEffect featheringEffect, optional androidx.xr.compose.subspace.SurfaceProtection surfaceProtection, optional androidx.xr.compose.subspace.DragPolicy? dragPolicy, optional androidx.xr.compose.subspace.ResizePolicy? resizePolicy, optional androidx.xr.compose.subspace.layout.InteractionPolicy? interactionPolicy, kotlin.jvm.functions.Function1 content);
method @BytecodeOnly @Deprecated @androidx.compose.runtime.Composable @androidx.xr.compose.subspace.SubspaceComposable public static void SpatialExternalSurface-3SPOwJ0(androidx.xr.scenecore.SurfaceEntity.StereoMode!, androidx.xr.compose.subspace.layout.SubspaceModifier!, androidx.xr.compose.subspace.layout.SpatialFeatheringEffect!, androidx.xr.scenecore.SurfaceEntity.SurfaceProtection!, androidx.xr.compose.subspace.DragPolicy!, androidx.xr.compose.subspace.ResizePolicy!, androidx.xr.compose.subspace.layout.InteractionPolicy!, kotlin.jvm.functions.Function3!, androidx.compose.runtime.Composer!, int, int);
diff --git a/xr/compose/compose/src/main/kotlin/androidx/xr/compose/subspace/SpatialExternalSurface.kt b/xr/compose/compose/src/main/kotlin/androidx/xr/compose/subspace/SpatialExternalSurface.kt
index e60b5dc71e45d..be004c4c453ea 100644
--- a/xr/compose/compose/src/main/kotlin/androidx/xr/compose/subspace/SpatialExternalSurface.kt
+++ b/xr/compose/compose/src/main/kotlin/androidx/xr/compose/subspace/SpatialExternalSurface.kt
@@ -17,7 +17,6 @@
package androidx.xr.compose.subspace
import android.view.Surface
-import androidx.annotation.RestrictTo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
@@ -46,11 +45,10 @@ import androidx.xr.scenecore.SurfaceEntity
import androidx.xr.scenecore.scene
/** Contains default values used by SpatialExternalSurface. */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-public object SpatialExternalSurfaceDefaults {
+internal object SpatialExternalSurfaceDefaults {
/** Default radius for spheres. */
- public val sphereRadius: Dp = Meter(15f).toDp()
+ internal val sphereRadius: Dp = Meter(15f).toDp()
}
/**
diff --git a/xr/compose/integration-tests/testapp/src/main/kotlin/androidx/xr/compose/testapp/common/MediaHelper.kt b/xr/compose/integration-tests/testapp/src/main/kotlin/androidx/xr/compose/testapp/common/MediaHelper.kt
index a15c18604707d..5591b7ff34c88 100644
--- a/xr/compose/integration-tests/testapp/src/main/kotlin/androidx/xr/compose/testapp/common/MediaHelper.kt
+++ b/xr/compose/integration-tests/testapp/src/main/kotlin/androidx/xr/compose/testapp/common/MediaHelper.kt
@@ -16,8 +16,10 @@
package androidx.xr.compose.testapp.common
+import android.media.MediaCodecInfo
import android.media.MediaCodecList
import androidx.annotation.OptIn
+import androidx.media3.common.C
import androidx.media3.common.MimeTypes
import androidx.media3.common.util.UnstableApi
@@ -50,3 +52,50 @@ fun isMvHevcSupported(): Boolean {
// No decoder found that supports the MV-HEVC profile.
return false
}
+
+@OptIn(UnstableApi::class) // For MimeTypes like VP9
+/**
+ * Checks if the device supports Widevine DRM and has a secure decoder.
+ *
+ * This prevents crashes on devices that might report partial DRM support but lack the secure
+ * rendering path required for SurfaceEntity.SurfaceProtection.PROTECTED.
+ */
+public fun isDrmSupported(): Boolean {
+ // 1. Check if the Widevine scheme is supported by the device.
+ if (!android.media.MediaDrm.isCryptoSchemeSupported(C.WIDEVINE_UUID)) {
+ return false
+ }
+
+ // 2. Check if a secure decoder is available.
+ // For example, the emulator might support the scheme (L3) but fail to create a protected
+ // surface if no secure decoder is present.
+ val mimeTypesToCheck =
+ listOf(
+ MimeTypes.VIDEO_H264,
+ MimeTypes.VIDEO_H265,
+ MimeTypes.VIDEO_VP9,
+ MimeTypes.VIDEO_AV1,
+ MimeTypes.VIDEO_MP4,
+ )
+ val mediaCodecList = MediaCodecList(MediaCodecList.ALL_CODECS)
+
+ for (mimeType in mimeTypesToCheck) {
+ for (info in mediaCodecList.codecInfos) {
+ if (info.isEncoder) continue
+ try {
+ val caps = info.getCapabilitiesForType(mimeType)
+ if (
+ caps != null &&
+ caps.isFeatureSupported(
+ MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback
+ )
+ ) {
+ return true
+ }
+ } catch (e: IllegalArgumentException) {
+ // MIME type not supported by this codec, continue searching.
+ }
+ }
+ }
+ return false
+}
diff --git a/xr/compose/integration-tests/testapp/src/main/kotlin/androidx/xr/compose/testapp/fragments/VideoPlayerFragment.kt b/xr/compose/integration-tests/testapp/src/main/kotlin/androidx/xr/compose/testapp/fragments/VideoPlayerFragment.kt
index d05a1da0db757..4872e84bcaabe 100644
--- a/xr/compose/integration-tests/testapp/src/main/kotlin/androidx/xr/compose/testapp/fragments/VideoPlayerFragment.kt
+++ b/xr/compose/integration-tests/testapp/src/main/kotlin/androidx/xr/compose/testapp/fragments/VideoPlayerFragment.kt
@@ -22,6 +22,8 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -30,6 +32,8 @@ 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.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.unit.dp
@@ -56,6 +60,7 @@ import androidx.xr.compose.subspace.layout.fillMaxSize
import androidx.xr.compose.subspace.layout.height
import androidx.xr.compose.subspace.layout.offset
import androidx.xr.compose.subspace.layout.width
+import androidx.xr.compose.testapp.common.isDrmSupported
/** A Fragment using spatial UI. */
class VideoPlayerFragment : Fragment() {
@@ -92,6 +97,7 @@ class VideoPlayerFragment : Fragment() {
private fun VideoInSpatialExternalSurface(stereoMode: StereoMode) {
var videoWidth by remember { mutableStateOf(600.dp) }
var videoHeight by remember { mutableStateOf(600.dp) }
+ val isDrmSupported = remember { isDrmSupported() }
SpatialExternalSurface(
modifier =
SubspaceModifier.width(
@@ -156,8 +162,16 @@ class VideoPlayerFragment : Fragment() {
}
Orbiter(position = ContentEdge.Bottom, offset = 48.dp) {
- Button(onClick = { useDrmState.value = !useDrmState.value }) {
- Text(text = if (useDrmState.value) "Use non-drm video" else "Use drm video")
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Button(onClick = { useDrmState.value = !useDrmState.value }) {
+ Text(text = if (useDrmState.value) "Use non-drm video" else "Use drm video")
+ }
+ if (!isDrmSupported) {
+ Text(
+ text = "DRM is not supported on this device",
+ modifier = Modifier.padding(start = 16.dp),
+ )
+ }
}
}
}
diff --git a/xr/compose/integration-tests/testapp/src/main/kotlin/androidx/xr/compose/testapp/spatialcompose/SpatialComposeVideoPlayer.kt b/xr/compose/integration-tests/testapp/src/main/kotlin/androidx/xr/compose/testapp/spatialcompose/SpatialComposeVideoPlayer.kt
index 5b98569e13ff9..7a567b2d3f0c8 100644
--- a/xr/compose/integration-tests/testapp/src/main/kotlin/androidx/xr/compose/testapp/spatialcompose/SpatialComposeVideoPlayer.kt
+++ b/xr/compose/integration-tests/testapp/src/main/kotlin/androidx/xr/compose/testapp/spatialcompose/SpatialComposeVideoPlayer.kt
@@ -82,7 +82,6 @@ import androidx.xr.compose.subspace.SpatialColumn
import androidx.xr.compose.subspace.SpatialExternalSurface
import androidx.xr.compose.subspace.SpatialExternalSurface180Hemisphere
import androidx.xr.compose.subspace.SpatialExternalSurface360Sphere
-import androidx.xr.compose.subspace.SpatialExternalSurfaceDefaults
import androidx.xr.compose.subspace.SpatialMainPanel
import androidx.xr.compose.subspace.SpatialPanel
import androidx.xr.compose.subspace.SpatialSpacer
@@ -99,8 +98,10 @@ import androidx.xr.compose.subspace.layout.height
import androidx.xr.compose.subspace.layout.offset
import androidx.xr.compose.subspace.layout.onPointSourceParamsAvailable
import androidx.xr.compose.subspace.layout.width
+import androidx.xr.compose.testapp.common.isDrmSupported
import androidx.xr.compose.testapp.common.isMvHevcSupported
import androidx.xr.compose.testapp.ui.components.CommonTestScaffold
+import androidx.xr.compose.unit.Meter
import androidx.xr.runtime.Config
import androidx.xr.runtime.Session
import androidx.xr.runtime.SessionCreateSuccess
@@ -238,7 +239,7 @@ class SpatialComposeVideoPlayer : ComponentActivity() {
val animatedOffset = remember { Animatable(initialValue = -1000f) }
LaunchedEffect(Unit) {
animatedRadius.animateTo(
- targetValue = SpatialExternalSurfaceDefaults.sphereRadius.value,
+ targetValue = Meter(15f).toDp().value,
animationSpec = tween(durationMillis = 2000, easing = FastOutLinearInEasing),
)
}
@@ -451,6 +452,7 @@ class SpatialComposeVideoPlayer : ComponentActivity() {
VideoMenuState.VIDEO_IN_SPATIAL_EXTERNAL_SURFACE -> {
val scrollState = rememberScrollState()
+ val isDrmSupported = remember { isDrmSupported() }
Column(
modifier =
Modifier.verticalScroll(scrollState).padding(24.dp)
@@ -474,13 +476,21 @@ class SpatialComposeVideoPlayer : ComponentActivity() {
}
}
- Button(
- onClick = { useDrmState.value = !useDrmState.value }
- ) {
- if (useDrmState.value) {
- Text("Use picker video uri")
- } else {
- Text("Use drm video uri")
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Button(
+ onClick = { useDrmState.value = !useDrmState.value }
+ ) {
+ if (useDrmState.value) {
+ Text("Use picker video uri")
+ } else {
+ Text("Use drm video uri")
+ }
+ }
+ if (!isDrmSupported) {
+ Text(
+ "Drm is not supported on this device.",
+ modifier = Modifier.padding(start = 16.dp),
+ )
}
}
@@ -751,6 +761,7 @@ class SpatialComposeVideoPlayer : ComponentActivity() {
SpatialBox(modifier = SubspaceModifier.fillMaxSize()) {
val interactionSource = remember { MutableInteractionSource() }
val isPanelHovered by interactionSource.collectIsHoveredAsState()
+ val isDrmSupported = remember { isDrmSupported() }
// Having an alpha helps reduce depth perception issues with stereo video.
SpatialPanel(
@@ -780,8 +791,16 @@ class SpatialComposeVideoPlayer : ComponentActivity() {
Text("Play/Pause")
}
Spacer(modifier = Modifier.weight(1f))
- Button(onClick = { useDrmState.value = !useDrmState.value }) {
- Text(text = if (useDrmState.value) "Use non-drm video" else "Use drm video")
+ Column {
+ Button(onClick = { useDrmState.value = !useDrmState.value }) {
+ Text(
+ text =
+ if (useDrmState.value) "Use non-drm video" else "Use drm video"
+ )
+ }
+ if (!isDrmSupported) {
+ Text("Drm is not supported on this device.", color = Color.White)
+ }
}
Spacer(modifier = Modifier.weight(1f))
Button(onClick = { videoPlayingState.value = false }) { Text("End Video") }
@@ -1022,6 +1041,7 @@ class SpatialComposeVideoPlayer : ComponentActivity() {
fun SurfaceEntityUI(session: Session) {
val movableComponentMP = remember { mutableStateOf(null) }
val videoPaused = remember { mutableStateOf(false) }
+ val isDrmSupported = remember { isDrmSupported() }
movableComponentMP.value = MovableComponent.createSystemMovable(session)
@Suppress("UNUSED_VARIABLE")
val unused = session.scene.mainPanelEntity.addComponent(movableComponentMP.value!!)
@@ -1037,11 +1057,19 @@ class SpatialComposeVideoPlayer : ComponentActivity() {
Button(onClick = { menuState.value = VideoMenuState.HOME }) {
Text("Main Menu")
}
- Button(onClick = { useDrmState.value = !useDrmState.value }) {
- if (useDrmState.value) {
- Text("Use picker video uri")
- } else {
- Text("Use drm video uri")
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Button(onClick = { useDrmState.value = !useDrmState.value }) {
+ if (useDrmState.value) {
+ Text("Use picker video uri")
+ } else {
+ Text("Use drm video uri")
+ }
+ }
+ if (!isDrmSupported) {
+ Text(
+ "Drm is not supported on this device.",
+ modifier = Modifier.padding(start = 16.dp),
+ )
}
}
LaunchSurfaceEntityButton()
diff --git a/xr/compose/integration-tests/testapp/src/main/kotlin/androidx/xr/compose/testapp/videoplayer/VideoPlayerActivity.kt b/xr/compose/integration-tests/testapp/src/main/kotlin/androidx/xr/compose/testapp/videoplayer/VideoPlayerActivity.kt
index 8d2bd2fdb27da..694091b40bc92 100644
--- a/xr/compose/integration-tests/testapp/src/main/kotlin/androidx/xr/compose/testapp/videoplayer/VideoPlayerActivity.kt
+++ b/xr/compose/integration-tests/testapp/src/main/kotlin/androidx/xr/compose/testapp/videoplayer/VideoPlayerActivity.kt
@@ -80,6 +80,7 @@ import androidx.xr.compose.subspace.SpatialPanel
import androidx.xr.compose.subspace.layout.SubspaceModifier
import androidx.xr.compose.subspace.layout.size
import androidx.xr.compose.testapp.R
+import androidx.xr.compose.testapp.common.isDrmSupported
import androidx.xr.compose.testapp.common.isMvHevcSupported
import androidx.xr.compose.testapp.ui.components.CommonTestScaffold
import androidx.xr.compose.unit.DpVolumeSize
@@ -453,6 +454,9 @@ class VideoPlayerActivity : ComponentActivity() {
if (!isMvHevcSupported()) {
ApiText(text = "MV-HEVC is not supported on this device")
}
+ if (!isDrmSupported()) {
+ ApiText(text = "DRM is not supported on this device")
+ }
}
}
}
diff --git a/xr/scenecore/integration-tests/testapp/src/main/kotlin/androidx/xr/scenecore/testapp/common/MediaHelper.kt b/xr/scenecore/integration-tests/testapp/src/main/kotlin/androidx/xr/scenecore/testapp/common/MediaHelper.kt
index 5daf3b4d76881..d225fef1175d9 100644
--- a/xr/scenecore/integration-tests/testapp/src/main/kotlin/androidx/xr/scenecore/testapp/common/MediaHelper.kt
+++ b/xr/scenecore/integration-tests/testapp/src/main/kotlin/androidx/xr/scenecore/testapp/common/MediaHelper.kt
@@ -16,8 +16,10 @@
package androidx.xr.scenecore.testapp.common
+import android.media.MediaCodecInfo
import android.media.MediaCodecList
import androidx.annotation.OptIn
+import androidx.media3.common.C
import androidx.media3.common.MimeTypes
import androidx.media3.common.util.UnstableApi
@@ -50,3 +52,50 @@ fun isMvHevcSupported(): Boolean {
// No decoder found that supports the MV-HEVC profile.
return false
}
+
+@OptIn(UnstableApi::class) // For MimeTypes like VP9
+/**
+ * Checks if the device supports Widevine DRM and has a secure decoder.
+ *
+ * This prevents crashes on devices that might report partial DRM support but lack the secure
+ * rendering path required for SurfaceEntity.SurfaceProtection.PROTECTED.
+ */
+public fun isDrmSupported(): Boolean {
+ // 1. Check if the Widevine scheme is supported by the device.
+ if (!android.media.MediaDrm.isCryptoSchemeSupported(C.WIDEVINE_UUID)) {
+ return false
+ }
+
+ // 2. Check if a secure decoder is available.
+ // For example, the emulator might support the scheme (L3) but fail to create a protected
+ // surface if no secure decoder is present.
+ val mimeTypesToCheck =
+ listOf(
+ MimeTypes.VIDEO_H264,
+ MimeTypes.VIDEO_H265,
+ MimeTypes.VIDEO_VP9,
+ MimeTypes.VIDEO_AV1,
+ MimeTypes.VIDEO_MP4,
+ )
+ val mediaCodecList = MediaCodecList(MediaCodecList.ALL_CODECS)
+
+ for (mimeType in mimeTypesToCheck) {
+ for (info in mediaCodecList.codecInfos) {
+ if (info.isEncoder) continue
+ try {
+ val caps = info.getCapabilitiesForType(mimeType)
+ if (
+ caps != null &&
+ caps.isFeatureSupported(
+ MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback
+ )
+ ) {
+ return true
+ }
+ } catch (e: IllegalArgumentException) {
+ // MIME type not supported by this codec, continue searching.
+ }
+ }
+ }
+ return false
+}
diff --git a/xr/scenecore/integration-tests/testapp/src/main/kotlin/androidx/xr/scenecore/testapp/surfaceplayback/SurfaceEntityPlaybackActivity.kt b/xr/scenecore/integration-tests/testapp/src/main/kotlin/androidx/xr/scenecore/testapp/surfaceplayback/SurfaceEntityPlaybackActivity.kt
index db0f256a568ae..f0f62a4c53d32 100644
--- a/xr/scenecore/integration-tests/testapp/src/main/kotlin/androidx/xr/scenecore/testapp/surfaceplayback/SurfaceEntityPlaybackActivity.kt
+++ b/xr/scenecore/integration-tests/testapp/src/main/kotlin/androidx/xr/scenecore/testapp/surfaceplayback/SurfaceEntityPlaybackActivity.kt
@@ -94,6 +94,7 @@ import androidx.xr.scenecore.PanelEntity
import androidx.xr.scenecore.SurfaceEntity
import androidx.xr.scenecore.Texture
import androidx.xr.scenecore.scene
+import androidx.xr.scenecore.testapp.common.isDrmSupported
import androidx.xr.scenecore.testapp.common.isMvHevcSupported
import java.io.File
import java.nio.ByteBuffer
@@ -992,7 +993,7 @@ class SurfaceEntityPlaybackActivity : ComponentActivity() {
shape = SurfaceEntity.Shape.Quad(FloatSize2d(1.0f, 1.0f)),
buttonText = "[DRM] Play MVHEVC Left Primary",
buttonColor = VideoButtonColors.DRM,
- enabled = enabled,
+ enabled = isDrmSupported(),
loop = loop,
protected = true,
)
@@ -1144,6 +1145,13 @@ class SurfaceEntityPlaybackActivity : ComponentActivity() {
activity,
isMvHevcSupported(),
)
+ if (!isDrmSupported()) {
+ Text(
+ text = "DRM is not supported on this device.",
+ fontSize = 18.sp,
+ color = Color.Red,
+ )
+ }
HDRVideoPlaybackButton(session, arDevice, activity)
SingleViewRotated270HalfWidthButton(session, arDevice, activity)
MVHEVCLeftPrimaryRotated180Button(
diff --git a/xr/scenecore/integration-tests/videoplayerdrmtest/src/main/kotlin/androidx/xr/scenecore/samples/videoplayerdrmtest/VideoPlayerDrmTestActivity.kt b/xr/scenecore/integration-tests/videoplayerdrmtest/src/main/kotlin/androidx/xr/scenecore/samples/videoplayerdrmtest/VideoPlayerDrmTestActivity.kt
index f902b471bb21c..620e332a5eeb1 100644
--- a/xr/scenecore/integration-tests/videoplayerdrmtest/src/main/kotlin/androidx/xr/scenecore/samples/videoplayerdrmtest/VideoPlayerDrmTestActivity.kt
+++ b/xr/scenecore/integration-tests/videoplayerdrmtest/src/main/kotlin/androidx/xr/scenecore/samples/videoplayerdrmtest/VideoPlayerDrmTestActivity.kt
@@ -21,6 +21,9 @@ import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
+import android.media.MediaCodecInfo
+import android.media.MediaCodecList
+import android.media.MediaDrm
import android.os.Bundle
import android.os.Environment
import android.util.Log
@@ -29,6 +32,7 @@ import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.annotation.OptIn
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -58,9 +62,11 @@ import androidx.lifecycle.setViewTreeViewModelStoreOwner
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaItem.DrmConfiguration
+import androidx.media3.common.MimeTypes
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.VideoSize
+import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.savedstate.SavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
@@ -507,6 +513,7 @@ class VideoPlayerDrmTestActivity : ComponentActivity() {
@Composable
fun DrmVideoButton(session: Session, activity: Activity) {
+ val isDrmSupported: Boolean = remember { isDrmSupported() }
val videoUri =
Environment.getExternalStorageDirectory().getPath() +
"/Download/sdr_singleview_protected.mp4"
@@ -527,9 +534,9 @@ class VideoPlayerDrmTestActivity : ComponentActivity() {
videoUri = videoUri,
buttonText =
if (!videoPlaying) {
- "Play Drm Video"
+ if (!isDrmSupported) "Drm Not Supported (Play anyway)" else "Play Drm Video"
} else {
- "Queue Drm Video"
+ if (!isDrmSupported) "Drm Not Supported (Queue anyway)" else "Queue Drm Video"
},
onClick = {
if (!videoPlaying) {
@@ -636,3 +643,51 @@ class VideoPlayerDrmTestActivity : ComponentActivity() {
}
}
}
+
+// TODO: b/473040355 - deduplicate this logic and the other MediaHelper.kt files.
+@OptIn(UnstableApi::class) // For MimeTypes like VP9
+/**
+ * Checks if the device supports Widevine DRM and has a secure decoder.
+ *
+ * This prevents crashes on devices that might report partial DRM support but lack the secure
+ * rendering path required for SurfaceEntity.SurfaceProtection.PROTECTED.
+ */
+private fun isDrmSupported(): Boolean {
+ // 1. Check if the Widevine scheme is supported by the device.
+ if (!android.media.MediaDrm.isCryptoSchemeSupported(C.WIDEVINE_UUID)) {
+ return false
+ }
+
+ // 2. Check if a secure decoder is available.
+ // For example, the emulator might support the scheme (L3) but fail to create a protected
+ // surface if no secure decoder is present.
+ val mimeTypesToCheck =
+ listOf(
+ MimeTypes.VIDEO_H264,
+ MimeTypes.VIDEO_H265,
+ MimeTypes.VIDEO_VP9,
+ MimeTypes.VIDEO_AV1,
+ MimeTypes.VIDEO_MP4,
+ )
+ val mediaCodecList = MediaCodecList(MediaCodecList.ALL_CODECS)
+
+ for (mimeType in mimeTypesToCheck) {
+ for (info in mediaCodecList.codecInfos) {
+ if (info.isEncoder) continue
+ try {
+ val caps = info.getCapabilitiesForType(mimeType)
+ if (
+ caps != null &&
+ caps.isFeatureSupported(
+ MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback
+ )
+ ) {
+ return true
+ }
+ } catch (e: IllegalArgumentException) {
+ // MIME type not supported by this codec, continue searching.
+ }
+ }
+ }
+ return false
+}