From 3ba8b0c209ea57a3c35211a7cddd7293bc72b0be Mon Sep 17 00:00:00 2001 From: Kumar Ankit Date: Wed, 7 Jan 2026 11:21:39 +0530 Subject: [PATCH 01/19] Update addOnPdfContentInvalidatedListener to take executor as a param. - Addresses API feedback to add an executor to ensure thread-safe callbacks. Doc: go/form-filling-processing-layer Bug: 471094578 Test: PdfFormFillingTest Change-Id: I351534e2d56a0f824e2240fbf1a561be157185f0 --- .../androidx/pdf/compose/FakePdfDocument.kt | 4 +- .../kotlin/androidx/pdf/PdfFormFillingTest.kt | 41 ++++++++++++++++++- .../androidx/pdf/SandboxedPdfDocument.kt | 14 ++++--- .../androidx/pdf/FakeEditablePdfDocument.kt | 4 +- .../androidx/pdf/FakeEditablePdfDocument.kt | 6 ++- .../pdf/viewer/document/FakePdfDocument.kt | 4 +- .../fragment/PdfDocumentViewModelTest.kt | 2 +- .../androidx/pdf/view/FakePdfDocument.kt | 4 +- .../main/kotlin/androidx/pdf/PdfDocument.kt | 20 ++++++++- .../main/kotlin/androidx/pdf/view/PdfView.kt | 9 ++-- .../kotlin/androidx/pdf/FakePdfDocument.kt | 4 +- 11 files changed, 93 insertions(+), 19 deletions(-) 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/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-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 } From 7bcee4ca8c1916bd21c22495e113421fc6e9c069 Mon Sep 17 00:00:00 2001 From: Igor Akimenko Date: Thu, 18 Dec 2025 14:02:03 -0800 Subject: [PATCH 02/19] Make SpatialExternalSurfaceDefaults internal Bug: 470135123 Test: Default value works as intended Relnote: Make SpatialExternalSurfaceDefaults internal Change-Id: I61b535792a663dc09fc755c4eb46d9a7b93ee186 --- xr/compose/compose/api/restricted_current.txt | 6 ------ .../androidx/xr/compose/subspace/SpatialExternalSurface.kt | 6 ++---- .../testapp/spatialcompose/SpatialComposeVideoPlayer.kt | 4 ++-- 3 files changed, 4 insertions(+), 12 deletions(-) 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/spatialcompose/SpatialComposeVideoPlayer.kt b/xr/compose/integration-tests/testapp/src/main/kotlin/androidx/xr/compose/testapp/spatialcompose/SpatialComposeVideoPlayer.kt index 5b98569e13ff9..887968275f3ba 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 @@ -101,6 +100,7 @@ import androidx.xr.compose.subspace.layout.onPointSourceParamsAvailable import androidx.xr.compose.subspace.layout.width 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 +238,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), ) } From 7c7ea65c7abb3fba6934b3075d2ee2535af96f81 Mon Sep 17 00:00:00 2001 From: samratdebroy Date: Fri, 2 Jan 2026 13:37:56 -0500 Subject: [PATCH 03/19] Add DRM support detection to test apps We also display some warning text in the Test app UI when DRM is not supported. Buttons are not disabled, but there are clear warnings that it's not supported. Bug: b/466393414 Test: Tested all the test apps on both emulator and GalaxyXR Change-Id: I3fe5f27e4d8f44698cd658e072cdf908d7b23931 --- .../xr/compose/testapp/common/MediaHelper.kt | 49 +++++++++++++++ .../testapp/fragments/VideoPlayerFragment.kt | 18 +++++- .../SpatialComposeVideoPlayer.kt | 56 +++++++++++++----- .../videoplayer/VideoPlayerActivity.kt | 4 ++ .../scenecore/testapp/common/MediaHelper.kt | 49 +++++++++++++++ .../SurfaceEntityPlaybackActivity.kt | 10 +++- .../VideoPlayerDrmTestActivity.kt | 59 ++++++++++++++++++- 7 files changed, 226 insertions(+), 19 deletions(-) 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..c5b4743360a1d 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 @@ -99,6 +99,7 @@ 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.runtime.Config @@ -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 +} From 539803be871ffd51b83e29b175cac4cb02b5e761 Mon Sep 17 00:00:00 2001 From: Julia McClellan Date: Thu, 8 Jan 2026 12:25:34 -0500 Subject: [PATCH 04/19] Suppress existing property compatibility issues Bug: 300126192 Test: ./gradlew checkApi Change-Id: Ib2f63c0f5678fe58aca279f53a279b7e27cf9ce3 --- .../material3/material3/api/current.ignore | 4 + .../material3/api/restricted_current.ignore | 4 + .../api/current.ignore | 4 + .../api/restricted_current.ignore | 6 + graphics/graphics-core/api/current.ignore | 162 +++++++++++++++++ .../api/restricted_current.ignore | 164 ++++++++++++++++++ .../navigation-runtime/api/current.ignore | 12 +- .../api/restricted_current.ignore | 12 +- .../api/current.ignore | 16 ++ .../api/restricted_current.ignore | 16 ++ .../tiles-tooling-preview/api/current.ignore | 12 ++ .../api/restricted_current.ignore | 12 ++ 12 files changed, 422 insertions(+), 2 deletions(-) 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) 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) 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/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): 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): 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/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/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 From 922b30969a975e8db949278740fb0b462cb2cf40 Mon Sep 17 00:00:00 2001 From: Phil Unger Date: Sat, 10 Jan 2026 00:01:43 +0000 Subject: [PATCH 05/19] Add space-updated listener in PersistentAnchors activity Add an onSpaceUpdatedListener to the PersistentAnchors activity in the ARCore testapp. On emulator, the ArDevice and RenderViewpoint poses do not update until panning across the screen or zooming in/out. On the first emitted value for these ARDevice objects, the Session's ActivitySpace has not been updated with the OpenXR reference space pose. When this eventually happens, the ScenePose's that were used to position the "Add Anchor" panel and calculate whether or not the main panel is in view become invalid, and these calculations need to be re-performed. These re-calculations are performed in the onSpaceUpdatedListener. Bug: 464064122 Test: On Moohan device and Moohan emulator Change-Id: I2f7aeae5d9d09a72ea472f2da887ae2997453073 --- .../PersistentAnchorsActivity.kt | 71 +++++++++++-------- 1 file changed, 40 insertions(+), 31 deletions(-) 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) From 5065ad823937351457d9cfe4dd139332bb9a9f26 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Sun, 11 Jan 2026 17:31:24 -0800 Subject: [PATCH 06/19] Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import OFF_PEAK_PRESUBMIT: true Low-Coverage-Reason: LSC Change-Id: I9dcec53293ce7462c60c8578fb7aa2412c09ead9 --- biometric/biometric/src/main/res/values-bs/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From 884ef62b190f30cfa28e9acfd20f55b50068b780 Mon Sep 17 00:00:00 2001 From: Jason Le Date: Fri, 9 Jan 2026 23:22:43 +0000 Subject: [PATCH 07/19] Allow swipe back when backstack has one item Removed the API 36+ background check in PredictiveBackScene. This ensures the scene doesn't intercept or block the system back gesture when only one item remains, allowing the user to exit the app or return to the previous activity. Bug: 469700240 Bug: 471847847 Test: SwipeDismissableSceneStrategyTest RelNote: "Fix SwipeDismissableSceneStrategy blocking system back on API 36+" Change-Id: Icf20b2217dc07efeb44223fd2c797d03ecaf40fa --- .../SwipeDismissableSceneStrategyTest.kt | 44 ++++++++++++++++--- .../SwipeDismissableSceneStrategy.kt | 5 +-- 2 files changed, 41 insertions(+), 8 deletions(-) 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, ) } } From 31a6ecd35b54028697648cd306462c954f3c18f7 Mon Sep 17 00:00:00 2001 From: Azat Abdullin Date: Thu, 8 Jan 2026 17:34:13 +0100 Subject: [PATCH 08/19] FunctionKeyMeta: Revert AnnotationRetention to 'AnnotationRetention.BINARY' FunctionKeyMeta annotation retention was changed to `RUNTIME` after it introduced compatibility issues for Compose Hot Reload and Kotlin 2.2 ([CL](https://android-review.googlesource.com/c/platform/frameworks/support/+/3662394)). Those issues were resolved in Kotlin 2.2.20, so the retention of FunctionKeyMeta can be changed back to binary. Test: api dump included Relnote: Change AnnotationRetention of the 'FunctionKeyMeta' annotation to 'BINARY' Change-Id: I53495e8be92aa7ff3da40474ff21342f05820d7a --- compose/runtime/runtime/api/current.ignore | 5 +++++ compose/runtime/runtime/api/current.txt | 4 ++-- compose/runtime/runtime/api/restricted_current.ignore | 5 +++++ compose/runtime/runtime/api/restricted_current.txt | 4 ++-- .../androidx/compose/runtime/internal/FunctionKeyMeta.kt | 2 +- 5 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 compose/runtime/runtime/api/current.ignore create mode 100644 compose/runtime/runtime/api/restricted_current.ignore 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) From dad30418e66280a680e362f73cfe279d2ad0923d Mon Sep 17 00:00:00 2001 From: Neda Topoljanac Date: Mon, 12 Jan 2026 06:29:25 -0800 Subject: [PATCH 09/19] Add @JvmOverloads to Material3TileService ctor This is in order for Material3TileService customization via ctor parameters to work well with Hilt injection dependency. Bug: 470048768 Change-Id: I65b01bb15bf96c8a464d24848e0ec5da6612dbe7 --- wear/tiles/tiles/api/current.txt | 2 ++ wear/tiles/tiles/api/restricted_current.txt | 2 ++ .../src/main/java/androidx/wear/tiles/Material3TileService.kt | 4 +++- 3 files changed, 7 insertions(+), 1 deletion(-) 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, From 5034d4facbe08fb69fb49dc8b8cd461adcab2c7e Mon Sep 17 00:00:00 2001 From: Omar Ismail Date: Mon, 12 Jan 2026 16:14:29 +0000 Subject: [PATCH 10/19] Use faster URL to download Studio Change-Id: Iefef323f1ed7c628c74d1a7603b1f04cf521a5c6 --- .../src/main/kotlin/androidx/build/studio/StudioTask.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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") From bd1201f33a195614ebfe927e29c521d9c9c24bbb Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Mon, 12 Jan 2026 15:58:34 +0000 Subject: [PATCH 11/19] Apply insets padding to PagerOfLazyGrid to ensure that screen content is interactable Fixes: 471481654 Change-Id: I234f670708af04e038f3e4c7ced1e2ac22d5539f --- .../macrobenchmark/target/PagerOfLazyGridActivity.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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) } From 123d168ad73ba3da5484981e1ec22f74cf6ee1f3 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Mon, 12 Jan 2026 16:38:30 +0000 Subject: [PATCH 12/19] Reduce the amount of notifications for SysUiSceneTransitionLayoutStartupHeroBenchmark 300 notifications take >1000ms for TTI on sargo devices. Change-Id: Ia0b4af4dae394d055a885d1650b7999950de38e3 --- .../SysUiSceneTransitionLayoutStartupHeroBenchmark.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) } } From 32914a2be8bbee412ab294bc8b10c9c0893d7060 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Mon, 12 Jan 2026 16:56:56 +0000 Subject: [PATCH 13/19] Lower iteration count in SysUi hero benchmarks 10 iterations should be enough for deterministic results. Change-Id: I83b8bc19409ca286d2874cce1172f2c782ab6e9f --- .../macrobenchmark/SysUiSceneTransitionLayoutHeroBenchmark.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 88ae84306a2e1a2974f970f6add30bee3453edcb Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Mon, 12 Jan 2026 17:00:33 +0000 Subject: [PATCH 14/19] Add ashikov@ and jossiwolf@ as owners of Compose macrobenchmarks module Also removes andreykulikov@ since he is no longer with the team. Change-Id: Idf0a056b198600a4b24914d68eae78ebd106ef0f --- compose/integration-tests/macrobenchmark-target/OWNERS | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 From d2a6b2b473a85489385beba69e2ec2b1b4253881 Mon Sep 17 00:00:00 2001 From: Aurimas Liutikas Date: Mon, 12 Jan 2026 10:04:56 -0800 Subject: [PATCH 15/19] Revert "Fix aggregation for activity intensity and mindfulness." This reverts commit 8091b84f2a84a0b669e62e3828fb98359f3a0d24. Reason for revert: Added broken tests Bug: 474614101 Change-Id: I12321707821070380336cf345f625d1599dd62b6 --- health/connect/connect-client/api/current.txt | 4 +- .../connect-client/api/restricted_current.txt | 4 +- .../connect/connect-client/lint-baseline.xml | 20 +-- .../health/connect/client/AssumeRule.kt | 35 ----- .../records/ActivityIntensityRecordTest.kt | 137 ------------------ .../records/MindfulnessSessionRecordsTest.kt | 127 ---------------- .../platform/aggregate/AggregationMappings.kt | 56 +------ .../platform/request/RequestConverters.kt | 2 - .../platform/response/ResponseConverters.kt | 17 +-- .../client/records/ActivityIntensityRecord.kt | 6 +- .../health/connect/client/records/Utils.kt | 14 +- .../aggregate/AggregationMappingsTest.kt | 84 ----------- 12 files changed, 23 insertions(+), 483 deletions(-) delete mode 100644 health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/AssumeRule.kt delete mode 100644 health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/ActivityIntensityRecordTest.kt delete mode 100644 health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/MindfulnessSessionRecordsTest.kt delete mode 100644 health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/platform/aggregate/AggregationMappingsTest.kt 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) - } -} From b6da101861ba38800833861cf4093cf9e13a58a1 Mon Sep 17 00:00:00 2001 From: Aurimas Liutikas Date: Mon, 12 Jan 2026 10:20:09 -0800 Subject: [PATCH 16/19] Revert "feat(fragment): Integrate ToolbarCoordinator in Editable..." Revert submission 3907980-drag-and-drop Reason for revert: Added broken tests Bug: 475234286 Reverted changes: /q/submissionid:3907980-drag-and-drop Bug: b/472376277 Change-Id: Ife081b94934fb3b45b6f418eee383bd9d75913c1 --- pdf/pdf-ink/build.gradle | 2 - .../src/androidTest/assets/sample_form.pdf | Bin 16254 -> 0 bytes .../EditablePdfViewerFragmentTests.kt | 230 ------------------ .../fragment/TestEditablePdfViewerFragment.kt | 108 -------- .../androidx/pdf/util/FragmentTestUtils.kt | 59 ----- .../androidx/pdf/util/PdfIdlingResource.kt | 47 ---- .../kotlin/androidx/pdf/util/TestUtils.kt | 34 --- .../androidx/pdf/util/ToolbarMatchers.kt | 65 ----- .../pdf/ink/EditablePdfViewerFragment.kt | 45 +--- pdf/pdf-ink/src/main/res/values/ids.xml | 1 - 10 files changed, 4 insertions(+), 587 deletions(-) delete mode 100755 pdf/pdf-ink/src/androidTest/assets/sample_form.pdf delete mode 100644 pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/fragment/EditablePdfViewerFragmentTests.kt delete mode 100644 pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/fragment/TestEditablePdfViewerFragment.kt delete mode 100644 pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/util/FragmentTestUtils.kt delete mode 100644 pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/util/PdfIdlingResource.kt delete mode 100644 pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/util/TestUtils.kt delete mode 100644 pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/util/ToolbarMatchers.kt diff --git a/pdf/pdf-ink/build.gradle b/pdf/pdf-ink/build.gradle index 750da8b131832..dd4a53a08a940 100644 --- a/pdf/pdf-ink/build.gradle +++ b/pdf/pdf-ink/build.gradle @@ -71,9 +71,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 ab9460180429c6eb07bfc43d5efc3e83bfccce54..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16254 zcmeHO?{3>h693Mp*e?On0-^Q~_a9Ich}}9@v`tc5cfCW<4~i^1&dJhMR@(HQ=bqqj zPjG#)`^}JCQY4kAu4DBA8M3$>&MaqVhQFO1t{y!9;^dLaefB{7`tSe#>(~EeN=UJM z`C5GTSti72{L4RPVst!NPv*-@B0Qg5&TfPT(ZBr1vYYI||26*=7<(ged4rO~jom|7SC)_@o+{{>gGWhChwrUxeZqxMo z&1`YGegz$rBA%P|YBssfe&nc;g7lPPdYv7O*#L0)_yWv%A;#Z`FULeLd6Wn?*Ft$I z#@FKE_|?^oxS%+iLgC5P;^q@Eel5nIc7%#ZLe{d-^wn(o_m|7J@Y-Z?E>@HCtEG7P zX1!i^#mN~k8|mWmQ0YQS|ICUjary*}+*=(BIz}gb5Ua~<0i)a83T-96Riw{qCXPPM zzQdq3O%fnIsUKFq>7K}R<0Me&v!nVt z8NO`Q>X-H@4X={rRb|Xl{kG{@;5uws>(@7mEjUIKRkb>xgwN4!q5qE63OFeefn!LhVb0cY8dyy1`Z%eIfj}x(N zXg*=BL2dM?*-1h4QEFXxj5E4p;;!_lwEm7}(+Z5Md6g#M2Di(3oM)Pi>EQdT^UE2} z5GR1_qxFKWo``}EfJ-_)7d-J$^K>@Fln6{DL}Vb=Uh)h&77sBMoIhL4e>tvj`4f^D z|9Q)W$It7@a`d!rf&XtnbH&L6qrC|J`E5upGh7eDp8$zcYA4A7#53j`eb1IEdH*CvPl@r{uM^|ZF^onKU!@91Pw2ce z-&EI*AB%?tnXc^ZFXfGKQu&oE@w@%1Nx@;iV(Os@R-5G4dY(=G+#tNekGjshk{@*) zN!9QM{HU9(30KMay3Ujup`IUm5u-a1)A$ix(&)GNku390Kh_g|%av)~Z@JKJxRCDI zxR4^Xabf$xyAVZG){uU!2kMkEnvGJr~8v+ z*G+`JRa(7+pAOjjuuigCljLT(>cc!vs(hXZdw1~D0eWdkGvKPB73yD~y?l)|CP7y! zSyFwO2y|qJmNm;%$i*VA$|>Jd#-;SCOyjCDW|ZA3<|WE-Pw?Mao1vhGd4ldkAC&Sg zQsEHHKMSf3Zd3?wxIoCbQp=WAMwLhDD7!*YG{oqs&S!FO3V;EMlU^Ga#5}`gTR4Nt zq0gOmR6=yQ@g|}U6>{l4)%<*Jqc&*N6jaPSG?&Ppsi7Q-k?70j&T_>GC0A3R+yR=* zqYV*79V!h5MDjd?gp46XVe%M*(GfJ4M~ylq)`Ph#3d_2ogetuWz@NNOAr@M=+<7fg zrPR490%)KVP~S&Tq|yPi+!TFbX(Ju<%>xEtys&xUF}4kgauQ4p&oqOjOfXb6tL2JZ z2WcEE)=-qz1(uhvZ`7dSFF16$2ObJjWF8bVM)d$=ap=nL{>HDtI?`e<4da;Y)KLcbOl%xMs> z4#8>QBs5&)$U)auMsG1>)q4!)*{kdV4^6L*#igHaf^@$y5oG6G$6caCf54mNfpQAF z-vI-n21lGnfhXO&!UG_IE}q_5bb%2}$i^eysk{#w!BmMe`#@A2!0`Z%2XF*FZv)4n z+THzPMc`6m>qT%0j(DQ)1}b{^$Tj=@P6*rZk@Ch09>X?xz8koH>NfrBbH%!ZHWaDz3&GqDlE&e>_E*E!NFGxEOfCFa&!(x zukl(@0;`vCCWGCj*}v-yVh4X zbly6}^DEQ+hOoCE#-IJ*9jdb4Z#rYtmce>WMWKL|Pg=FKszJF#zEl0bCxly^6veI6 zk35ivO0)Ot+$~s)s@q-owja*>uW%YwYxUDIy3EcsdE^W}GGiMdalv=~d#GN~GT+tDX}8_IYi-F_q7=&%*a4iR3?#|hMSo~rv;w*O*}^}~}^UAD$Ap=dkgA1x~*^=ye| zX#O$bj8d>(iLg)n$ia-ZVec8t%y0?!5aL)N>;elWQ)Dvz3G9}_zQ_xa zX!Ofh`V)&pQ}lC>E-eCfI<&&}`7Aa`b@;HCsmI4Vbh>;f8h2%}g{Rk_DX?#=BWohP zoS?sE90eGAv;sdx?ebycT9;-G{Z3}HKZ{?qc4;p3?Z-bX?Z^g2zr@+>FVN<+4lNeg z`Uv>tncclRDWzrTWqQA^oKVTz58ODRM+RK z(AXl@k+rnXEz#Di$<=(eDu3xUI$bW;C_j`xY#5zg{c}dKj4g*Z>&a@((T$%T#+W^L K@a40UEc+jdTCCLo 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/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/main/kotlin/androidx/pdf/ink/EditablePdfViewerFragment.kt b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/EditablePdfViewerFragment.kt index 5de0163c8445d..a63458f1601ab 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/res/values/ids.xml b/pdf/pdf-ink/src/main/res/values/ids.xml index 7189b7f9a811e..bfd6e5cc877da 100644 --- a/pdf/pdf-ink/src/main/res/values/ids.xml +++ b/pdf/pdf-ink/src/main/res/values/ids.xml @@ -15,7 +15,6 @@ --> - \ No newline at end of file From 6f3d9b5a48800f5ef22482f207688969500cfcf3 Mon Sep 17 00:00:00 2001 From: Aurimas Liutikas Date: Mon, 12 Jan 2026 10:20:09 -0800 Subject: [PATCH 17/19] Revert "feat(view): Add ToolbarCoordinator for drag-and-drop man..." Revert submission 3907980-drag-and-drop Reason for revert: Added broken tests Bug: 475234286 Reverted changes: /q/submissionid:3907980-drag-and-drop Bug: b/472376731 Change-Id: Id87a4acb33b80248ba6dc204a0b41cd87c94b8ab --- .../draganddrop/ToolbarCoordinatorTest.kt | 142 ----------- .../androidx/pdf/util/ToolbarViewActions.kt | 80 ------- .../pdf/ink/view/draganddrop/AnchorManager.kt | 94 -------- .../view/draganddrop/ToolbarCoordinator.kt | 221 ------------------ .../main/res/drawable/anchor_background.xml | 26 --- .../res/layout/annotation_toolbar_layout.xml | 4 +- .../main/res/layout/toolbar_coordinator.xml | 49 ---- pdf/pdf-ink/src/main/res/values/ids.xml | 1 - .../ink/view/draganddrop/AnchorManagerTest.kt | 122 ---------- 9 files changed, 3 insertions(+), 736 deletions(-) delete mode 100644 pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/ink/view/draganddrop/ToolbarCoordinatorTest.kt delete mode 100644 pdf/pdf-ink/src/androidTest/kotlin/androidx/pdf/util/ToolbarViewActions.kt delete mode 100644 pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/draganddrop/AnchorManager.kt delete mode 100644 pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/draganddrop/ToolbarCoordinator.kt delete mode 100644 pdf/pdf-ink/src/main/res/drawable/anchor_background.xml delete mode 100644 pdf/pdf-ink/src/main/res/layout/toolbar_coordinator.xml delete mode 100644 pdf/pdf-ink/src/test/kotlin/androidx/pdf/ink/view/draganddrop/AnchorManagerTest.kt 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/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/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/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/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_layout.xml b/pdf/pdf-ink/src/main/res/layout/annotation_toolbar_layout.xml index 225e8e5eb7f0b..bc977721ce6c0 100644 --- a/pdf/pdf-ink/src/main/res/layout/annotation_toolbar_layout.xml +++ b/pdf/pdf-ink/src/main/res/layout/annotation_toolbar_layout.xml @@ -25,4 +25,6 @@ android:focusable="true" android:elevation="@dimen/annotation_toolbar_elevation" android:padding="@dimen/padding_8dp" - android:layout_gravity="bottom|center_horizontal"/> + 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/ids.xml b/pdf/pdf-ink/src/main/res/values/ids.xml index bfd6e5cc877da..42f4ee8f27cfd 100644 --- a/pdf/pdf-ink/src/main/res/values/ids.xml +++ b/pdf/pdf-ink/src/main/res/values/ids.xml @@ -16,5 +16,4 @@ - \ No newline at end of file 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) - } -} From 0729b90cd452c7b5779c250c381726f34b05a8de Mon Sep 17 00:00:00 2001 From: Aurimas Liutikas Date: Mon, 12 Jan 2026 10:20:09 -0800 Subject: [PATCH 18/19] Revert "feat(view): Make AnnotationToolbar draggable and dockable" Revert submission 3907980-drag-and-drop Reason for revert: Added broken tests Bug: 475234286 Reverted changes: /q/submissionid:3907980-drag-and-drop Bug: b/472376731 Change-Id: Ib180387ed46b14148b5882e046da4819cd34e9b5 --- .../src/androidTest/kotlin/ScubaConstants.kt | 8 - .../pdf/ink/view/AnnotationToolbarTest.kt | 60 ---- .../pdf/ink/view/AnnotationToolbarUiTest.kt | 58 ---- .../ink/view/state/ToolbarInitializerTest.kt | 3 +- .../pdf/ink/view/AnnotationToolbar.kt | 101 +----- .../ink/view/AnnotationToolbarViewModel.kt | 2 - .../layout/AnnotationToolbarConstraintSet.kt | 288 ------------------ .../ink/view/state/AnnotationToolbarState.kt | 6 - .../pdf/ink/view/state/ToolbarInitializer.kt | 22 -- .../ink/view/state/ToolbarIntentAndEffects.kt | 3 - .../main/res/layout/annotation_toolbar.xml | 24 +- pdf/pdf-ink/src/main/res/values/dimens.xml | 4 - .../state/AnnotationToolbarViewModelTest.kt | 15 - 13 files changed, 19 insertions(+), 575 deletions(-) delete mode 100644 pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/layout/AnnotationToolbarConstraintSet.kt 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/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/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/main/kotlin/androidx/pdf/ink/view/AnnotationToolbar.kt b/pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/AnnotationToolbar.kt index 4abe678e446ef..6d5eb8005eb36 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 @@ -26,11 +26,9 @@ 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 +46,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 +64,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 +85,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 +142,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 +168,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 +216,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 +260,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 +460,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 +470,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 +484,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() /** 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/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/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"> - - - - - - - - + + + 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/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) } } From 73ff19b2754b2cac5927ab0e1036b32c659d78af Mon Sep 17 00:00:00 2001 From: Aurimas Liutikas Date: Mon, 12 Jan 2026 10:20:09 -0800 Subject: [PATCH 19/19] Revert "feat(view): Implement robust drag-and-drop touch handlin..." Revert submission 3907980-drag-and-drop Reason for revert: Added broken tests Bug: 475234286 Reverted changes: /q/submissionid:3907980-drag-and-drop Bug: b/472376731 Change-Id: I1224e760f53f6622b55429dc173fcc05e6e23bae --- pdf/pdf-ink/build.gradle | 2 - .../pdf/ink/view/AnnotationToolbar.kt | 10 -- .../AnnotationToolbarTouchHandler.kt | 122 --------------- .../ink/view/draganddrop/ToolbarDockState.kt | 59 -------- .../view/draganddrop/ToolbarDragListener.kt | 50 ------- .../pdf/ink/view/ViewExtensionsTest.kt | 94 ------------ .../AnnotationToolbarTouchHandlerTest.kt | 140 ------------------ 7 files changed, 477 deletions(-) delete mode 100644 pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/draganddrop/AnnotationToolbarTouchHandler.kt delete mode 100644 pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/draganddrop/ToolbarDockState.kt delete mode 100644 pdf/pdf-ink/src/main/kotlin/androidx/pdf/ink/view/draganddrop/ToolbarDragListener.kt delete mode 100644 pdf/pdf-ink/src/test/kotlin/androidx/pdf/ink/view/ViewExtensionsTest.kt delete mode 100644 pdf/pdf-ink/src/test/kotlin/androidx/pdf/ink/view/draganddrop/AnnotationToolbarTouchHandlerTest.kt diff --git a/pdf/pdf-ink/build.gradle b/pdf/pdf-ink/build.gradle index dd4a53a08a940..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) 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 6d5eb8005eb36..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,15 +17,12 @@ 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.animation.DecelerateInterpolator import android.widget.LinearLayout import android.widget.LinearLayout.HORIZONTAL @@ -506,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/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/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/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/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, - ) - } -}