diff --git a/app/src/androidTest/java/com/google/jetpackcamera/DebugHideComponentsTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/DebugOverlayTest.kt similarity index 74% rename from app/src/androidTest/java/com/google/jetpackcamera/DebugHideComponentsTest.kt rename to app/src/androidTest/java/com/google/jetpackcamera/DebugOverlayTest.kt index ef29f97d0..835dffb4f 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/DebugHideComponentsTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/DebugOverlayTest.kt @@ -18,6 +18,7 @@ package com.google.jetpackcamera import androidx.compose.ui.test.junit4.createEmptyComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTextInput import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.GrantPermissionRule @@ -30,6 +31,9 @@ import com.google.jetpackcamera.ui.components.capture.FLIP_CAMERA_BUTTON import com.google.jetpackcamera.ui.components.capture.ZOOM_BUTTON_ROW_TAG import com.google.jetpackcamera.ui.debug.BTN_DEBUG_HIDE_COMPONENTS_TAG import com.google.jetpackcamera.ui.debug.DEBUG_OVERLAY_BUTTON +import com.google.jetpackcamera.ui.debug.DEBUG_OVERLAY_SET_ZOOM_RATIO_BUTTON +import com.google.jetpackcamera.ui.debug.DEBUG_OVERLAY_SET_ZOOM_RATIO_SET_BUTTON +import com.google.jetpackcamera.ui.debug.DEBUG_OVERLAY_SET_ZOOM_RATIO_TEXT_FIELD import com.google.jetpackcamera.ui.debug.LOGICAL_CAMERA_ID_TAG import com.google.jetpackcamera.ui.debug.PHYSICAL_CAMERA_ID_TAG import com.google.jetpackcamera.ui.debug.ZOOM_RATIO_TAG @@ -45,7 +49,7 @@ import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -class DebugHideComponentsTest { +class DebugOverlayTest { @get:Rule val permissionsRule: GrantPermissionRule = GrantPermissionRule.grant(*(TEST_REQUIRED_PERMISSIONS).toTypedArray()) @@ -93,4 +97,35 @@ class DebugHideComponentsTest { composeTestRule.onNodeWithTag(ZOOM_RATIO_TAG).assertExists() } } + + @Test + fun setZoomRatio_viaDebugOverlay() { + runMainActivityScenarioTest(debugExtra) { + composeTestRule.waitForCaptureButton() + + // Open debug menu + composeTestRule.onNodeWithTag(DEBUG_OVERLAY_BUTTON).performClick() + + // Click "Set Zoom Ratio" button + composeTestRule.waitForNodeWithTag(DEBUG_OVERLAY_SET_ZOOM_RATIO_BUTTON) + composeTestRule.onNodeWithTag(DEBUG_OVERLAY_SET_ZOOM_RATIO_BUTTON).performClick() + + // Find text field and enter value + composeTestRule.waitForNodeWithTag(DEBUG_OVERLAY_SET_ZOOM_RATIO_TEXT_FIELD) + composeTestRule.onNodeWithTag( + DEBUG_OVERLAY_SET_ZOOM_RATIO_TEXT_FIELD + ).performTextInput("1.5") + + // Click "Confirm" + composeTestRule.onNodeWithTag(DEBUG_OVERLAY_SET_ZOOM_RATIO_SET_BUTTON).performClick() + + // Verify dialog closed (text field should not exist anymore) + composeTestRule.onNodeWithTag( + DEBUG_OVERLAY_SET_ZOOM_RATIO_TEXT_FIELD + ).assertDoesNotExist() + + // Verify zoom ratio text exists + composeTestRule.onNodeWithTag(ZOOM_RATIO_TAG).assertExists() + } + } } diff --git a/app/src/androidTest/java/com/google/jetpackcamera/ExternalAutomationCompatibilityTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/ExternalAutomationCompatibilityTest.kt new file mode 100644 index 000000000..87ad1de13 --- /dev/null +++ b/app/src/androidTest/java/com/google/jetpackcamera/ExternalAutomationCompatibilityTest.kt @@ -0,0 +1,132 @@ +/* + * Copyright (C) 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 com.google.jetpackcamera + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.rule.GrantPermissionRule +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until +import com.google.common.truth.Truth.assertWithMessage +import com.google.jetpackcamera.ui.components.capture.CAPTURE_BUTTON +import com.google.jetpackcamera.ui.components.capture.FLIP_CAMERA_BUTTON +import com.google.jetpackcamera.ui.debug.DEBUG_OVERLAY_BUTTON +import com.google.jetpackcamera.ui.debug.DEBUG_OVERLAY_SET_ZOOM_RATIO_BUTTON +import com.google.jetpackcamera.ui.debug.DEBUG_OVERLAY_SET_ZOOM_RATIO_SET_BUTTON +import com.google.jetpackcamera.ui.debug.DEBUG_OVERLAY_SET_ZOOM_RATIO_TEXT_FIELD +import com.google.jetpackcamera.utils.APP_START_TIMEOUT_MILLIS +import com.google.jetpackcamera.utils.DEFAULT_TIMEOUT_MILLIS +import com.google.jetpackcamera.utils.TEST_REQUIRED_PERMISSIONS +import com.google.jetpackcamera.utils.debugExtra +import com.google.jetpackcamera.utils.runMainActivityScenarioTest +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 ExternalAutomationCompatibilityTest { + + @get:Rule + val permissionsRule: GrantPermissionRule = + GrantPermissionRule.grant(*(TEST_REQUIRED_PERMISSIONS).toTypedArray()) + + private lateinit var device: UiDevice + + @Before + fun setUp() { + device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + } + + @After + fun tearDown() { + device.pressBack() + device.pressHome() + } + + @Test + fun verifyUiAutomator_canFindCaptureControls() { + runMainActivityScenarioTest { + // Wait for the capture button to be visible to UI Automator via Resource ID + val captureButton = + device.wait( + Until.findObject(By.res(CAPTURE_BUTTON)), + APP_START_TIMEOUT_MILLIS + ) + assertWithMessage("Capture button not found by UI Automator via Resource ID") + .that(captureButton) + .isNotNull() + + // Verify flip camera button is visible via Resource ID + val flipButton = device.findObject(By.res(FLIP_CAMERA_BUTTON)) + assertWithMessage("Flip camera button not found by UI Automator via Resource ID") + .that(flipButton) + .isNotNull() + } + } + + @Test + fun verifyUiAutomator_canFindDebugOverlayControls() { + runMainActivityScenarioTest(debugExtra) { + // Verify debug overlay button is visible via Resource ID + val debugButton = device.wait( + Until.findObject(By.res(DEBUG_OVERLAY_BUTTON)), + APP_START_TIMEOUT_MILLIS + ) + assertWithMessage("Debug overlay button not found by UI Automator via Resource ID") + .that(debugButton) + .isNotNull() + } + } + + @Test + fun verifyUiAutomator_canFindDebugZoomDialogControls() { + runMainActivityScenarioTest(debugExtra) { + // Open debug menu using UI Automator + val debugButton = device.wait( + Until.findObject(By.res(DEBUG_OVERLAY_BUTTON)), + APP_START_TIMEOUT_MILLIS + ) + assertWithMessage("Debug overlay button not found").that(debugButton).isNotNull() + debugButton.click() + + // Click "Set Zoom Ratio" button using UI Automator + val setZoomButton = device.wait( + Until.findObject(By.res(DEBUG_OVERLAY_SET_ZOOM_RATIO_BUTTON)), + DEFAULT_TIMEOUT_MILLIS + ) + assertWithMessage("Set Zoom Ratio button not found").that(setZoomButton).isNotNull() + setZoomButton.click() + + // Verify the zoom dialog text field is visible to UI Automator + val textField = device.wait( + Until.findObject(By.res(DEBUG_OVERLAY_SET_ZOOM_RATIO_TEXT_FIELD)), + DEFAULT_TIMEOUT_MILLIS + ) + assertWithMessage( + "Zoom text field not found by UI Automator" + ).that(textField).isNotNull() + + // Verify the confirm button is visible to UI Automator + val confirmButton = device.findObject(By.res(DEBUG_OVERLAY_SET_ZOOM_RATIO_SET_BUTTON)) + assertWithMessage( + "Confirm button not found by UI Automator" + ).that(confirmButton).isNotNull() + } + } +} diff --git a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/CaptureButtonComponents.kt b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/CaptureButtonComponents.kt index de67c1c54..820d16678 100644 --- a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/CaptureButtonComponents.kt +++ b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/CaptureButtonComponents.kt @@ -34,6 +34,7 @@ import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset @@ -607,7 +608,10 @@ private fun LockSwitchCaptureButtonNucleus( .align(Alignment.CenterStart) .padding(start = 8.dp) .offset(x = -(switchWidth - pressedNucleusSize)) - .clickable(indication = null, interactionSource = null) { + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { onToggleSwitchPosition() }, tint = Color.White, diff --git a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/ui/QuickSettingsComponents.kt b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/ui/QuickSettingsComponents.kt index 491347eed..115e2010d 100644 --- a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/ui/QuickSettingsComponents.kt +++ b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/ui/QuickSettingsComponents.kt @@ -17,6 +17,7 @@ package com.google.jetpackcamera.ui.components.capture.quicksettings.ui import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -49,6 +50,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -813,7 +815,7 @@ private fun TopBarQuickSettingIcon( modifier = modifier .size(IconButtonDefaults.smallIconSize) .clickable( - interactionSource = null, + interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = onClick, enabled = enabled diff --git a/ui/debug/src/main/java/com/google/jetpackcamera/ui/debug/DebugOverlayComponents.kt b/ui/debug/src/main/java/com/google/jetpackcamera/ui/debug/DebugOverlayComponents.kt index 5b8fbfd04..ad3cfdad7 100644 --- a/ui/debug/src/main/java/com/google/jetpackcamera/ui/debug/DebugOverlayComponents.kt +++ b/ui/debug/src/main/java/com/google/jetpackcamera/ui/debug/DebugOverlayComponents.kt @@ -23,6 +23,7 @@ import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -458,7 +459,7 @@ private fun SetTestPatternDialog( @Composable private fun Modifier.noIndicationClickable(onClick: () -> Unit): Modifier = this.clickable( - interactionSource = null, + interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = onClick )