From 96e3707ae145b701f48f0b90742207d5e097d9b5 Mon Sep 17 00:00:00 2001 From: Tahsin Masrur Date: Mon, 5 Jan 2026 09:09:35 +0000 Subject: [PATCH 1/2] Avoid unnecessary torch reset in TorchControl Avoid calling setTorchAsync(false) in TorchControl.reset() if the torch mode has not been set before. This avoids a small amount of redundant work and, more importantly, prevents unnecessary error logs in scenarios where the camera is being reset or closed multiple times quickly. Such error logs may be flagged as unexpected error logs and lead to test failures in some strict environments, this CL addresses such issues properly. Bug: 475380959 Test: ./gradlew :camera:camera-camera2:test Change-Id: I56d3dafcee8c1b9c97cfe7cace67b69d796eeaa8 --- .../camera/camera2/pipe/integration/impl/TorchControl.kt | 8 +++++--- .../java/androidx/camera/camera2/impl/TorchControl.kt | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/TorchControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/TorchControl.kt index 5af1ef3b51e03..f0d4220ce6682 100644 --- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/TorchControl.kt +++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/TorchControl.kt @@ -68,10 +68,12 @@ constructor( } override fun reset() { - updateTorchState(TorchMode.OFF) stopRunningTaskInternal() - setTorchAsync(false) - torchMode = null + if (torchMode != null) { + updateTorchState(TorchMode.OFF) + setTorchAsync(false) + torchMode = null + } } private val hasFlashUnit: Boolean = cameraProperties.isFlashAvailable() diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/TorchControl.kt b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/TorchControl.kt index 1aae839a2bdef..e31085eb58039 100644 --- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/TorchControl.kt +++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/TorchControl.kt @@ -68,10 +68,12 @@ constructor( } override fun reset() { - updateTorchState(TorchMode.OFF) stopRunningTaskInternal() - setTorchAsync(false) - torchMode = null + if (torchMode != null) { + updateTorchState(TorchMode.OFF) + setTorchAsync(false) + torchMode = null + } } private val hasFlashUnit: Boolean = cameraProperties.isFlashAvailable() From 804e76f8b52d119b80afb2ecd916a65109ae42f2 Mon Sep 17 00:00:00 2001 From: faizannaikwade Date: Mon, 12 Jan 2026 08:06:41 +0000 Subject: [PATCH 2/2] [ObjectEraser] Fix Highlight Object Eraser Bug. The AnnotationSelectionTouchHandler was incorrectly returning after evaluating only the first path object of an annotation. This caused the Object Eraser to fail when a touch intersected any part of the highlight other than its initial path object. This change ensures that all path objects within an annotation are checked for intersection with the touch region, allowing the eraser to function correctly across the entire highlight. Bug: 475101877 Test: ./gradlew :pdf:integration-tests:connectedAndroidTest Change-Id: Ib0623439a5d37c482546f44c228894581b3b63ae --- .../AnnotationSelectionTouchHandlerTest.kt | 87 ++++++++++++------- .../AnnotationSelectionTouchHandler.kt | 4 +- 2 files changed, 60 insertions(+), 31 deletions(-) diff --git a/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/annotation/AnnotationSelectionTouchHandlerTest.kt b/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/annotation/AnnotationSelectionTouchHandlerTest.kt index 20d5517bed671..1ea451cf8bc78 100644 --- a/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/annotation/AnnotationSelectionTouchHandlerTest.kt +++ b/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/annotation/AnnotationSelectionTouchHandlerTest.kt @@ -67,10 +67,9 @@ class AnnotationSelectionTouchHandlerTest { viewX: Float, viewY: Float, ): PageInfoProvider.PageInfo { - val pageBounds = RectF(0f, 0f, PAGE_WIDTH, PAGE_HEIGHT) return PageInfoProvider.PageInfo( pageNum = 0, - pageBounds = pageBounds, + pageBounds = RectF(0f, 0f, PAGE_WIDTH, PAGE_HEIGHT), pageToViewTransform = Matrix(), viewToPageTransform = Matrix(), ) @@ -91,13 +90,12 @@ class AnnotationSelectionTouchHandlerTest { @Test fun handleTouchEvent_hitDetected_notifiesListener() { ActivityScenario.launch(PdfViewTestActivity::class.java).use { scenario -> - val annotationBounds = RectF(100f, 100f, 200f, 200f) - val annotation = createStampAnnotation(annotationBounds) + val annotation = createStampAnnotation() scenario.onActivity { setupAnnotationsOnView(listOf(annotation)) - // Simulate Touch inside bounds + // Touch at (150, 150) is inside the first path (125, 125 to 175, 175) val event = obtainMotionEvent(MotionEvent.ACTION_DOWN, 150f, 150f) val consumed = selectionTouchHandler.handleTouch(annotationsView, event) @@ -110,13 +108,12 @@ class AnnotationSelectionTouchHandlerTest { @Test fun handleTouchEvent_outsideBounds_returnsFalse() { ActivityScenario.launch(PdfViewTestActivity::class.java).use { scenario -> - val annotationBounds = RectF(100f, 100f, 200f, 200f) - val annotation = createStampAnnotation(annotationBounds) + val annotation = createStampAnnotation() scenario.onActivity { setupAnnotationsOnView(listOf(annotation)) - // Touch outside at (300, 300) + // Touch at (300, 300) is outside the 100-200 bounds val event = obtainMotionEvent(MotionEvent.ACTION_DOWN, 300f, 300f) val consumed = selectionTouchHandler.handleTouch(annotationsView, event) @@ -129,8 +126,7 @@ class AnnotationSelectionTouchHandlerTest { @Test fun handleTouchEvent_swipeGesture_hitDetected() { ActivityScenario.launch(PdfViewTestActivity::class.java).use { scenario -> - val annotationBounds = RectF(100f, 100f, 200f, 200f) - val annotation = createStampAnnotation(annotationBounds) + val annotation = createStampAnnotation() scenario.onActivity { setupAnnotationsOnView(listOf(annotation)) @@ -148,11 +144,8 @@ class AnnotationSelectionTouchHandlerTest { @Test fun handleTouchEvent_overlappingAnnotations_selectsTopMost() { ActivityScenario.launch(PdfViewTestActivity::class.java).use { scenario -> - // Both annotations share the same bounds - val bounds = RectF(100f, 100f, 200f, 200f) - val bounds1 = RectF(125f, 125f, 175f, 175f) - val bottomAnnotation = createStampAnnotation(bounds) - val topAnnotation = createStampAnnotation(bounds1) + val bottomAnnotation = createStampAnnotation() + val topAnnotation = createStampAnnotation(10f) scenario.onActivity { // The list order represents Z-order: index 0 is bottom, last index is top @@ -170,7 +163,24 @@ class AnnotationSelectionTouchHandlerTest { } } - // Updated helper to support multiple annotations + @Test + fun handleTouchEvent_hitDetectedOnSecondPath_notifiesListener() { + ActivityScenario.launch(PdfViewTestActivity::class.java).use { scenario -> + val annotation = createStampAnnotation() + + scenario.onActivity { + setupAnnotationsOnView(listOf(annotation)) + + // Touch at (150, 190) is inside the second path (125, 180 to 175, 195) + val event = obtainMotionEvent(MotionEvent.ACTION_DOWN, 150f, 190f) + val consumed = selectionTouchHandler.handleTouch(annotationsView, event) + + assertThat(consumed).isTrue() + assertThat(testListener.lastSelectedAnnotation).isEqualTo(annotation) + } + } + } + private fun setupAnnotationsOnView(annotations: List) { val keyedAnnotations = annotations.map { KeyedPdfAnnotation(key = UUID.randomUUID().toString(), it) } @@ -180,22 +190,39 @@ class AnnotationSelectionTouchHandlerTest { annotationsView.annotations = sparseArray } - private fun createStampAnnotation(bounds: RectF): StampAnnotation { - val width = bounds.width() - val height = bounds.height() - - // Mock a simple rectangular path slightly inset from bounds - val pathInputs = - listOf( - PathPdfObject.PathInput(bounds.left + width / 4, bounds.top + height / 4), - PathPdfObject.PathInput(bounds.right - width / 4, bounds.top + height / 4), - PathPdfObject.PathInput(bounds.right - width / 4, bounds.bottom - height / 4), - PathPdfObject.PathInput(bounds.left + width / 4, bounds.bottom - height / 4), - PathPdfObject.PathInput(bounds.left + width / 4, bounds.top + height / 4), + /** + * Creates a StampAnnotation with hard-coded bounds and paths for predictable testing. Overall + * Bounds: (100, 100, 200, 200) Path 1 (Square): (125, 125) to (175, 175) Path 2 (Rectangle): + * (125, 180) to (175, 195) + */ + private fun createStampAnnotation(offset: Float = 0f): StampAnnotation { + val bounds = RectF(100f + offset, 100f + offset, 200f + offset, 200f + offset) + + val path1 = + PathPdfObject( + Color.RED, + 0f, + listOf( + PathPdfObject.PathInput(125f, 125f), + PathPdfObject.PathInput(175f, 125f), + PathPdfObject.PathInput(175f, 175f), + PathPdfObject.PathInput(125f, 175f), + ), + ) + + val path2 = + PathPdfObject( + Color.BLUE, + 0f, + listOf( + PathPdfObject.PathInput(125f, 180f), + PathPdfObject.PathInput(175f, 180f), + PathPdfObject.PathInput(175f, 195f), + PathPdfObject.PathInput(125f, 195f), + ), ) - val pathObject = PathPdfObject(Color.RED, 10f, pathInputs) - return StampAnnotation(0, bounds, listOf(pathObject)) + return StampAnnotation(0, bounds, listOf(path1, path2)) } private fun obtainMotionEvent(action: Int, x: Float, y: Float): MotionEvent { diff --git a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/annotation/AnnotationSelectionTouchHandler.kt b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/annotation/AnnotationSelectionTouchHandler.kt index 8489d52067ce4..02f104fbb111f 100644 --- a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/annotation/AnnotationSelectionTouchHandler.kt +++ b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/annotation/AnnotationSelectionTouchHandler.kt @@ -122,7 +122,9 @@ internal class AnnotationSelectionTouchHandler() { pathRegion.setPath(path, clip) // Intersect the path region with our touch square region. - return pathRegion.op(touchRegion, Region.Op.INTERSECT) + if (pathRegion.op(touchRegion, Region.Op.INTERSECT) == true) { + return true + } } false }