Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/Wiki/Working_with_Android_OS.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
1 change: 1 addition & 0 deletions docs/Wiki/Working_with_Android_OS.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
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
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.
* @param uiSelectorsOverride optional override for default UI selectors used in Settings UI interaction.
*/
open class NfcDefault(
private val logger: UiTestLogger,
private val targetContext: Context,
private val instrumentalDependencyProvider: InstrumentalDependencyProvider,
private val adbServer: AdbServer,
private val uiSelectorsOverride: List<BySelector>? = null,
) : Nfc {

companion object {
private const val NFC_STATE_CHECK_CMD = "settings get global nfc_adapter_state"
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

val manager = targetContext.getSystemService(Context.NFC_SERVICE) as NfcManager
val adapter = manager.defaultAdapter ?: return false

return adapter.isEnabled
}

/**
* 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.
*/
@Suppress("MagicNumber")
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 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()
}
} 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>): String? =
lines.firstOrNull()?.lineSequence()?.firstOrNull()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.NfcDefault
import com.kaspersky.kaspresso.device.permissions.HackPermissions
import com.kaspersky.kaspresso.device.permissions.HackPermissionsImpl
import com.kaspersky.kaspresso.device.permissions.Permissions
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -790,6 +800,12 @@ data class Kaspresso(
instrumentation.targetContext,
adbServer
)
if (!::nfc.isInitialized) nfc = NfcDefault(
libLogger,
instrumentation.targetContext,
instrumentalDependencyProviderFactory.getComponentProvider<NfcDefault>(instrumentation),
adbServer
)
if (!::phone.isInitialized) phone = PhoneImpl(libLogger, adbServer)
if (!::location.isInitialized) location = LocationImpl(libLogger, adbServer)
if (!::keyboard.isInitialized) keyboard = KeyboardImpl(libLogger, adbServer)
Expand Down Expand Up @@ -1044,6 +1060,7 @@ data class Kaspresso(
activities = activities,
files = files,
network = network,
nfc = nfc,
phone = phone,
location = location,
keyboard = keyboard,
Expand Down
Loading
Loading