From 88328ab37afa1a762b6529166a6dcc16bc08b382 Mon Sep 17 00:00:00 2001 From: Nikita Evdokimov Date: Fri, 17 Apr 2026 17:28:22 +0300 Subject: [PATCH 1/3] TECH: NFC control --- docs/Wiki/Working_with_Android_OS.en.md | 1 + docs/Wiki/Working_with_Android_OS.ru.md | 1 + .../com/kaspersky/kaspresso/device/Device.kt | 12 ++ .../com/kaspersky/kaspresso/device/nfc/Nfc.kt | 76 ++++++++++ .../kaspersky/kaspresso/device/nfc/NfcImpl.kt | 139 ++++++++++++++++++ .../kaspresso/kaspresso/Kaspresso.kt | 17 +++ .../device_tests/DeviceNfcSampleTest.kt | 90 ++++++++++++ 7 files changed, 336 insertions(+) create mode 100644 kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/nfc/Nfc.kt create mode 100644 kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/nfc/NfcImpl.kt create mode 100644 samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/device_tests/DeviceNfcSampleTest.kt diff --git a/docs/Wiki/Working_with_Android_OS.en.md b/docs/Wiki/Working_with_Android_OS.en.md index 434239334..299f28023 100644 --- a/docs/Wiki/Working_with_Android_OS.en.md +++ b/docs/Wiki/Working_with_Android_OS.en.md @@ -11,6 +11,7 @@ All examples are located in [device_tests](https://github.com/KasperskyLab/Kaspr 2. `activities` is an interface to work with currently resumed Activities. AdbServer not required. See the example [DeviceActivitiesSampleTest](https://github.com/KasperskyLab/Kaspresso/blob/master/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/device_tests/DeviceActivitiesSampleTest.kt). 3. `files` provides the possibility of pushing or removing files from the device. Uses `adb push` and `adb rm` commands and does not require `android.permission.WRITE_EXTERNAL_STORAGE` permission. See the example [DeviceFilesSampleTest](https://github.com/KasperskyLab/Kaspresso/blob/master/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/device_tests/DeviceFilesSampleTest.kt). 4. `internet` allows toggling WiFi and network data transfer settings. Be careful of using this interface, WiFi settings changes could not work with some Android versions. See the example [DeviceNetworkSampleTest](https://github.com/KasperskyLab/Kaspresso/blob/master/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/device_tests/DeviceNetworkSampleTest.kt). +4a. `nfc` allows enabling or disabling the NFC adapter. **Works only on physical devices — Android emulators do not have NFC hardware.** Requires AdbServer for the primary strategy; falls back to the Android Settings UI. Methods: `enable()`, `disable()`, `isEnabled()`. 5. `keyboard` is an interface to send key events via adb. Use it only when Espresso or UiAutomator are not appropriate (e.g. screen is locked). See the example [DeviceKeyboardSampleTest](https://github.com/KasperskyLab/Kaspresso/blob/master/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/device_tests/DeviceKeyboardSampleTest.kt). 6. `location` emulates fake location and allows to toggle GPS setting. See the example [DeviceLocationSampleTest](https://github.com/KasperskyLab/Kaspresso/blob/master/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/device_tests/DeviceLocationSampleTest.kt). 7. `phone` allows to emulate incoming calls and receive SMS messages. Works only on emulators since uses `adb emu` commands. See the example [DevicePhoneSampleTest](https://github.com/KasperskyLab/Kaspresso/blob/master/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/device_tests/DevicePhoneSampleTest.kt). diff --git a/docs/Wiki/Working_with_Android_OS.ru.md b/docs/Wiki/Working_with_Android_OS.ru.md index 98da86d32..edd955f27 100644 --- a/docs/Wiki/Working_with_Android_OS.ru.md +++ b/docs/Wiki/Working_with_Android_OS.ru.md @@ -11,6 +11,7 @@ 2. `activities` — это интерфейс для работы с отображаемыми в данный момент Activity. AdbServer не требуется. См. пример [DeviceActivitiesSampleTest](https://github.com/KasperskyLab/Kaspresso/blob/master/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/device_tests/DeviceActivitiesSampleTest.kt). 3. `files` обеспечивает возможность загрузки или удаления файлов с устройства. Использует команды `adb push` и `adb rm` и не требует разрешения `android.permission.WRITE_EXTERNAL_STORAGE`. См. пример [DeviceFilesSampleTest](https://github.com/KasperskyLab/Kaspresso/blob/master/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/device_tests/DeviceFilesSampleTest.kt). 4. `internet` позволяет переключать настройки Wi-Fi и передачи данных по сети. Будьте осторожны при использовании этого интерфейса, изменения настроек Wi-Fi могут не работать с некоторыми версиями Android. См. пример [DeviceNetworkSampleTest](https://github.com/KasperskyLab/Kaspresso/blob/master/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/device_tests/DeviceNetworkSampleTest.kt). +4a. `nfc` позволяет включать и отключать NFC-адаптер. **Работает только на реальных устройствах — эмуляторы Android не поддерживают NFC-оборудование.** Для основной стратегии требует AdbServer; в качестве запасного варианта открывает экран настроек Android. Методы: `enable()`, `disable()`, `isEnabled()`. 5. `keyboard` — это интерфейс для отправки событий клавиатуры через adb. Используйте его только тогда, когда Espresso или UiAutomator не подходят (например, экран заблокирован). См. пример [DeviceKeyboardSampleTest](https://github.com/KasperskyLab/Kaspresso/blob/master/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/device_tests/DeviceKeyboardSampleTest.kt). 6. `location` имитирует поддельное местоположение и позволяет переключать настройки GPS. См. пример [DeviceLocationSampleTest](https://github.com/KasperskyLab/Kaspresso/blob/master/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/device_tests/DeviceLocationSampleTest.kt). 7. `phone` позволяет эмулировать входящие звонки и принимать SMS-сообщения. Работает только на эмуляторах, так как использует команды `adb emu`. См. пример [DevicePhoneSampleTest](https://github.com/KasperskyLab/Kaspresso/blob/master/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/device_tests/DevicePhoneSampleTest.kt). diff --git a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/Device.kt b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/Device.kt index 3fb740281..3065a6108 100644 --- a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/Device.kt +++ b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/Device.kt @@ -13,6 +13,7 @@ import com.kaspersky.kaspresso.device.languages.Language import com.kaspersky.kaspresso.device.location.Location import com.kaspersky.kaspresso.device.logcat.Logcat import com.kaspersky.kaspresso.device.network.Network +import com.kaspersky.kaspresso.device.nfc.Nfc import com.kaspersky.kaspresso.device.permissions.HackPermissions import com.kaspersky.kaspresso.device.permissions.Permissions import com.kaspersky.kaspresso.device.phone.Phone @@ -75,6 +76,17 @@ data class Device( */ val network: Network, + /** + * Holds the reference to the implementation of [Nfc] interface. + * + * **Important:** Works only on physical devices. Android emulators do not have NFC hardware. + * + * Required: Started AdbServer (for the primary toggle strategy) + * 1. Download a file "kaspresso/artifacts/adbserver-desktop.jar" + * 2. Start AdbServer => input in cmd "java -jar path_to_file/adbserver-desktop.jar" + */ + val nfc: Nfc, + /** * Holds the reference to the implementation of [Phone] interface. * diff --git a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/nfc/Nfc.kt b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/nfc/Nfc.kt new file mode 100644 index 000000000..c91af4168 --- /dev/null +++ b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/nfc/Nfc.kt @@ -0,0 +1,76 @@ +package com.kaspersky.kaspresso.device.nfc + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/** + * The interface to control NFC adapter state on the device. + * + * **Important:** NFC is not supported by Android emulators. All methods in this interface + * work only on physical devices that have NFC hardware. + * + * Typical use cases: + * - Verify that the app shows the "NFC disabled" banner when NFC is off. + * - Restore NFC state after a test that turns it off. + * + * Required: Started AdbServer + * 1. Download a file "kaspresso/artifacts/adbserver-desktop.jar" + * 2. Start AdbServer => input in cmd "java -jar path_to_file/adbserver-desktop.jar" + */ +interface Nfc { + + /** + * Enables NFC on the device. + * + * Tries in order: + * 1. `svc nfc enable` via AdbServer — works on most devices since API 17. + * 2. Android Settings UI as a last resort (navigates to NFC settings screen and taps the toggle). + * + * **Note:** Works only on physical devices. Android emulators do not support NFC hardware. + * + * @throws RuntimeException if NFC hardware is not present on the device. + */ + fun enable() + + /** + * Disables NFC on the device. + * + * Tries in order: + * 1. `svc nfc disable` via AdbServer — works on most devices since API 17. + * 2. Android Settings UI as a last resort (navigates to NFC settings screen and taps the toggle). + * + * **Note:** Works only on physical devices. Android emulators do not support NFC hardware. + * + * @throws RuntimeException if NFC hardware is not present on the device. + */ + fun disable() + + /** + * Returns `true` if NFC is currently enabled on the device, `false` otherwise. + * + * Uses `settings get global nfc_adapter_state` under the hood: + * - `3` → enabled + * - `1` or anything else → disabled + * + * **Note:** Works only on physical devices. Android emulators do not support NFC hardware. + * + * @return `true` if NFC adapter is enabled, `false` if it is disabled or NFC hardware is absent. + */ + fun isEnabled(): Boolean +} diff --git a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/nfc/NfcImpl.kt b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/nfc/NfcImpl.kt new file mode 100644 index 000000000..2414bba85 --- /dev/null +++ b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/nfc/NfcImpl.kt @@ -0,0 +1,139 @@ +package com.kaspersky.kaspresso.device.nfc + +import android.content.Context +import android.content.Intent +import android.nfc.NfcAdapter +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import com.kaspersky.kaspresso.device.server.AdbServer +import com.kaspersky.kaspresso.instrumental.InstrumentalDependencyProvider +import com.kaspersky.kaspresso.internal.exceptions.AdbServerException +import com.kaspersky.kaspresso.logger.UiTestLogger + +/** + * The implementation of the [Nfc] interface. + * + * Toggle strategy (tried in order): + * 1. `svc nfc enable|disable` via AdbServer — works on most devices since API 17. + * 2. Android Settings NFC screen as a last resort. + * + * @param logger logger instance provided by the framework. + * @param targetContext target application context, used to check NFC hardware presence + * and to open Settings UI as a fallback. + * @param instrumentalDependencyProvider provides [androidx.test.uiautomator.UiDevice] + * for Settings UI interaction fallback. + * @param adbServer AdbServer instance to run shell commands. + */ +internal class NfcImpl( + private val logger: UiTestLogger, + private val targetContext: Context, + private val instrumentalDependencyProvider: InstrumentalDependencyProvider, + private val adbServer: AdbServer +) : Nfc { + + companion object { + private const val NFC_STATE_CHECK_CMD = "settings get global nfc_adapter_state" + private const val NFC_STATE_ENABLED = "3" + private const val NFC_STATE_CHANGE_CMD = "svc nfc" + private const val NFC_SETTINGS_ACTION = "android.settings.NFC_SETTINGS" + private const val SETTINGS_TIMEOUT_MS = 3_000L + } + + override fun enable() { + logger.i("Enable NFC") + toggle(enable = true) + } + + override fun disable() { + logger.i("Disable NFC") + toggle(enable = false) + } + + override fun isEnabled(): Boolean { + if (!isNfcHardwarePresent()) return false + return try { + val result = adbServer.performShell(NFC_STATE_CHECK_CMD) + parseAdbResult(result)?.trim() == NFC_STATE_ENABLED + } catch (e: AdbServerException) { + NfcAdapter.getDefaultAdapter(targetContext)?.isEnabled ?: false + } + } + + /** + * Toggles NFC state. + * Tries `svc nfc` via AdbServer first; falls back to Settings UI if that fails. + */ + private fun toggle(enable: Boolean) { + if (!isNfcHardwarePresent()) { + logger.i("NFC hardware is not present on this device") + return + } + + if (enable == isEnabled()) { + logger.i("NFC is already in the required state") + return + } + + if (!changeNfcStateUsingAdbServer(enable)) { + changeNfcStateUsingSettings(enable) + } + + logger.i(if (isEnabled()) "NFC enabled" else "NFC disabled") + } + + /** + * Tries `svc nfc enable|disable` via AdbServer. + * @return `true` if the state changed to the desired value, `false` on any failure. + */ + private fun changeNfcStateUsingAdbServer(enable: Boolean): Boolean { + val cmd = if (enable) "enable" else "disable" + return try { + adbServer.performShell("$NFC_STATE_CHANGE_CMD $cmd") + // Give the adapter a moment to apply the change, then verify + Thread.sleep(500) + isEnabled() == enable + } catch (e: AdbServerException) { + logger.i("svc nfc $cmd failed: ${e.message}") + false + } + } + + /** + * Opens the NFC settings screen and taps the NFC toggle via UiAutomator. + * Used as a last resort when AdbServer is unavailable or the `svc` command fails. + */ + private fun changeNfcStateUsingSettings(enable: Boolean) { + logger.i("Falling back to Settings UI to toggle NFC") + val uiDevice = instrumentalDependencyProvider.uiDevice + + val intent = Intent(NFC_SETTINGS_ACTION).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + targetContext.startActivity(intent) + + // Try to find an NFC toggle by common resource-id patterns across OEMs + val toggle = uiDevice.wait( + Until.findObject(By.checkable(true).textContains("NFC")), + SETTINGS_TIMEOUT_MS + ) ?: uiDevice.wait( + Until.findObject(By.checkable(true).descContains("NFC")), + SETTINGS_TIMEOUT_MS + ) + + if (toggle != null) { + if (toggle.isChecked != enable) { + toggle.click() + } + } else { + logger.i("Could not find NFC toggle in Settings UI") + } + + uiDevice.pressBack() + } + + private fun isNfcHardwarePresent(): Boolean = + NfcAdapter.getDefaultAdapter(targetContext) != null + + private fun parseAdbResult(lines: List): String? = + lines.firstOrNull()?.lineSequence()?.firstOrNull() +} diff --git a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/kaspresso/Kaspresso.kt b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/kaspresso/Kaspresso.kt index 3bc339069..d0721de9f 100644 --- a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/kaspresso/Kaspresso.kt +++ b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/kaspresso/Kaspresso.kt @@ -33,6 +33,8 @@ import com.kaspersky.kaspresso.device.logcat.dumper.LogcatDumper import com.kaspersky.kaspresso.device.logcat.dumper.LogcatDumperImpl import com.kaspersky.kaspresso.device.network.Network import com.kaspersky.kaspresso.device.network.NetworkImpl +import com.kaspersky.kaspresso.device.nfc.Nfc +import com.kaspersky.kaspresso.device.nfc.NfcImpl import com.kaspersky.kaspresso.device.permissions.HackPermissions import com.kaspersky.kaspresso.device.permissions.HackPermissionsImpl import com.kaspersky.kaspresso.device.permissions.Permissions @@ -402,6 +404,14 @@ data class Kaspresso( */ lateinit var network: Network + /** + * Holds an implementation of [Nfc] interface. If it was not specified, the default implementation is used. + * + * **Important:** The default implementation works only on physical devices. + * Android emulators do not have NFC hardware. + */ + lateinit var nfc: Nfc + /** * Holds an implementation of [Phone] interface. If it was not specified, the default implementation is used. */ @@ -790,6 +800,12 @@ data class Kaspresso( instrumentation.targetContext, adbServer ) + if (!::nfc.isInitialized) nfc = NfcImpl( + libLogger, + instrumentation.targetContext, + instrumentalDependencyProviderFactory.getComponentProvider(instrumentation), + adbServer + ) if (!::phone.isInitialized) phone = PhoneImpl(libLogger, adbServer) if (!::location.isInitialized) location = LocationImpl(libLogger, adbServer) if (!::keyboard.isInitialized) keyboard = KeyboardImpl(libLogger, adbServer) @@ -1044,6 +1060,7 @@ data class Kaspresso( activities = activities, files = files, network = network, + nfc = nfc, phone = phone, location = location, keyboard = keyboard, diff --git a/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/device_tests/DeviceNfcSampleTest.kt b/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/device_tests/DeviceNfcSampleTest.kt new file mode 100644 index 000000000..dccdcd573 --- /dev/null +++ b/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/device_tests/DeviceNfcSampleTest.kt @@ -0,0 +1,90 @@ +package com.kaspersky.kaspressample.device_tests + +import android.nfc.NfcAdapter +import androidx.test.ext.junit.rules.activityScenarioRule +import com.kaspersky.kaspressample.MainActivity +import com.kaspersky.kaspresso.testcases.api.testcase.TestCase +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Assume.assumeNotNull +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/** + * Sample test demonstrating [com.kaspersky.kaspresso.device.nfc.Nfc] device API. + * + * **Important:** NFC is not supported by Android emulators. This test must be run + * on a physical device that has NFC hardware. On a device without NFC the test is + * skipped automatically via [assumeNotNull]. + * + * Requires AdbServer to be running: + * ``` + * java -jar artifacts/adbserver-desktop.jar + * ``` + */ +@Ignore("Works only on physical devices with NFC hardware. Run manually on a real device.") +class DeviceNfcSampleTest : TestCase() { + + companion object { + private const val NFC_TOGGLE_DELAY_MS = 1_000L + } + + @get:Rule + val activityRule = activityScenarioRule() + + @Test + fun nfcSampleTest() { + before { + // Skip the test automatically on devices without NFC hardware (e.g. emulators) + assumeNotNull(NfcAdapter.getDefaultAdapter(device.targetContext)) + + device.nfc.enable() + }.after { + // Restore NFC to enabled state so the device is left in a clean condition + if (NfcAdapter.getDefaultAdapter(device.targetContext) != null) { + device.nfc.enable() + } + }.run { + + step("Check NFC hardware is present") { + val adapter = NfcAdapter.getDefaultAdapter(device.targetContext) + assertNotNull("NFC hardware not found — run this test on a physical device", adapter) + } + + step("Disable NFC and verify") { + device.nfc.disable() + flakySafely(timeoutMs = NFC_TOGGLE_DELAY_MS) { + assertFalse("NFC should be disabled", device.nfc.isEnabled()) + } + } + + step("Enable NFC and verify") { + device.nfc.enable() + flakySafely(timeoutMs = NFC_TOGGLE_DELAY_MS) { + assertTrue("NFC should be enabled", device.nfc.isEnabled()) + } + } + } + } +} From 37b2e93b6a4a4b606d8024fb58ef43fcc0ca013e Mon Sep 17 00:00:00 2001 From: Nikita Evdokimov Date: Fri, 17 Apr 2026 17:45:56 +0300 Subject: [PATCH 2/3] TECH: Detekt --- .../main/kotlin/com/kaspersky/kaspresso/device/nfc/NfcImpl.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/nfc/NfcImpl.kt b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/nfc/NfcImpl.kt index 2414bba85..c3de0537e 100644 --- a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/nfc/NfcImpl.kt +++ b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/nfc/NfcImpl.kt @@ -85,6 +85,7 @@ internal class NfcImpl( * Tries `svc nfc enable|disable` via AdbServer. * @return `true` if the state changed to the desired value, `false` on any failure. */ + @Suppress("MagicNumber") private fun changeNfcStateUsingAdbServer(enable: Boolean): Boolean { val cmd = if (enable) "enable" else "disable" return try { From efb669dd75a187c0eefb0cd3ccb9c00c27389849 Mon Sep 17 00:00:00 2001 From: Nikita Evdokimov Date: Tue, 21 Apr 2026 19:28:09 +0300 Subject: [PATCH 3/3] TECH: Make default implementation open and more customizable --- .../device/nfc/{NfcImpl.kt => NfcDefault.kt} | 35 +++++++++++-------- .../kaspresso/kaspresso/Kaspresso.kt | 6 ++-- 2 files changed, 23 insertions(+), 18 deletions(-) rename kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/nfc/{NfcImpl.kt => NfcDefault.kt} (80%) diff --git a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/nfc/NfcImpl.kt b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/nfc/NfcDefault.kt similarity index 80% rename from kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/nfc/NfcImpl.kt rename to kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/nfc/NfcDefault.kt index c3de0537e..5439382b5 100644 --- a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/nfc/NfcImpl.kt +++ b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/nfc/NfcDefault.kt @@ -3,7 +3,9 @@ package com.kaspersky.kaspresso.device.nfc import android.content.Context import android.content.Intent import android.nfc.NfcAdapter +import android.nfc.NfcManager import androidx.test.uiautomator.By +import androidx.test.uiautomator.BySelector import androidx.test.uiautomator.Until import com.kaspersky.kaspresso.device.server.AdbServer import com.kaspersky.kaspresso.instrumental.InstrumentalDependencyProvider @@ -23,17 +25,18 @@ import com.kaspersky.kaspresso.logger.UiTestLogger * @param instrumentalDependencyProvider provides [androidx.test.uiautomator.UiDevice] * for Settings UI interaction fallback. * @param adbServer AdbServer instance to run shell commands. + * @param uiSelectorsOverride optional override for default UI selectors used in Settings UI interaction. */ -internal class NfcImpl( +open class NfcDefault( private val logger: UiTestLogger, private val targetContext: Context, private val instrumentalDependencyProvider: InstrumentalDependencyProvider, - private val adbServer: AdbServer + private val adbServer: AdbServer, + private val uiSelectorsOverride: List? = null, ) : Nfc { companion object { private const val NFC_STATE_CHECK_CMD = "settings get global nfc_adapter_state" - private const val NFC_STATE_ENABLED = "3" private const val NFC_STATE_CHANGE_CMD = "svc nfc" private const val NFC_SETTINGS_ACTION = "android.settings.NFC_SETTINGS" private const val SETTINGS_TIMEOUT_MS = 3_000L @@ -51,12 +54,11 @@ internal class NfcImpl( override fun isEnabled(): Boolean { if (!isNfcHardwarePresent()) return false - return try { - val result = adbServer.performShell(NFC_STATE_CHECK_CMD) - parseAdbResult(result)?.trim() == NFC_STATE_ENABLED - } catch (e: AdbServerException) { - NfcAdapter.getDefaultAdapter(targetContext)?.isEnabled ?: false - } + + val manager = targetContext.getSystemService(Context.NFC_SERVICE) as NfcManager + val adapter = manager.defaultAdapter ?: return false + + return adapter.isEnabled } /** @@ -113,14 +115,17 @@ internal class NfcImpl( targetContext.startActivity(intent) // Try to find an NFC toggle by common resource-id patterns across OEMs - val toggle = uiDevice.wait( - Until.findObject(By.checkable(true).textContains("NFC")), - SETTINGS_TIMEOUT_MS - ) ?: uiDevice.wait( - Until.findObject(By.checkable(true).descContains("NFC")), - SETTINGS_TIMEOUT_MS + val toggleSelectors = uiSelectorsOverride ?: listOf( + By.checkable(true).res("android:id/switch_widget").pkg("com.android.settings"), + By.checkable(true).res("com.android.settings:id/switchWidget"), + By.checkable(true).res("com.android.settings:id/sesl_switchbar_switch"), // samsung + By.checkable(true).res("com.android.settings:id/settingslib_main_switch_bar"), // xiaomi ) + val toggle = toggleSelectors.firstNotNullOfOrNull { selector -> + uiDevice.wait(Until.findObject(selector), SETTINGS_TIMEOUT_MS) + } + if (toggle != null) { if (toggle.isChecked != enable) { toggle.click() diff --git a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/kaspresso/Kaspresso.kt b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/kaspresso/Kaspresso.kt index d0721de9f..92aa74ffc 100644 --- a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/kaspresso/Kaspresso.kt +++ b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/kaspresso/Kaspresso.kt @@ -34,7 +34,7 @@ import com.kaspersky.kaspresso.device.logcat.dumper.LogcatDumperImpl import com.kaspersky.kaspresso.device.network.Network import com.kaspersky.kaspresso.device.network.NetworkImpl import com.kaspersky.kaspresso.device.nfc.Nfc -import com.kaspersky.kaspresso.device.nfc.NfcImpl +import com.kaspersky.kaspresso.device.nfc.NfcDefault import com.kaspersky.kaspresso.device.permissions.HackPermissions import com.kaspersky.kaspresso.device.permissions.HackPermissionsImpl import com.kaspersky.kaspresso.device.permissions.Permissions @@ -800,10 +800,10 @@ data class Kaspresso( instrumentation.targetContext, adbServer ) - if (!::nfc.isInitialized) nfc = NfcImpl( + if (!::nfc.isInitialized) nfc = NfcDefault( libLogger, instrumentation.targetContext, - instrumentalDependencyProviderFactory.getComponentProvider(instrumentation), + instrumentalDependencyProviderFactory.getComponentProvider(instrumentation), adbServer ) if (!::phone.isInitialized) phone = PhoneImpl(libLogger, adbServer)