-
Notifications
You must be signed in to change notification settings - Fork 153
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
247 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 23 additions & 0 deletions
23
kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/bluetooth/Bluetooth.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.kaspersky.kaspresso.device.bluetooth | ||
|
||
/** | ||
* The interface to work with bluetooth settings. | ||
* | ||
* 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" | ||
* Methods demanding to use AdbServer in the default implementation of this interface are marked. | ||
* But nobody can't deprecate you to write implementation that doesn't require AdbServer. | ||
*/ | ||
interface Bluetooth { | ||
|
||
/** | ||
* Enables Bluetooth on the device using adb. | ||
*/ | ||
fun enable() | ||
|
||
/** | ||
* Disables Bluetooth on the device using adb. | ||
*/ | ||
fun disable() | ||
} |
116 changes: 116 additions & 0 deletions
116
kaspresso/src/main/kotlin/com/kaspersky/kaspresso/device/bluetooth/BluetoothImpl.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package com.kaspersky.kaspresso.device.bluetooth | ||
|
||
import android.content.Context | ||
import com.kaspersky.components.kautomator.system.UiSystem | ||
import com.kaspersky.kaspresso.device.server.AdbServer | ||
import com.kaspersky.kaspresso.flakysafety.algorithm.FlakySafetyAlgorithm | ||
import com.kaspersky.kaspresso.internal.exceptions.AdbServerException | ||
import com.kaspersky.kaspresso.internal.systemscreen.NotificationsFullScreen | ||
import com.kaspersky.kaspresso.internal.systemscreen.NotificationsShortScreen | ||
|
||
import com.kaspersky.kaspresso.logger.UiTestLogger | ||
import com.kaspersky.kaspresso.params.FlakySafetyParams | ||
|
||
/** | ||
* The implementation of the [Bluetooth] interface. | ||
*/ | ||
class BluetoothImpl( | ||
private val logger: UiTestLogger, | ||
private val targetContext: Context, | ||
private val adbServer: AdbServer | ||
) : Bluetooth { | ||
|
||
companion object { | ||
private const val CMD_STATE_ENABLE = "enable" | ||
private const val CMD_STATE_DISABLE = "disable" | ||
private const val BLUETOOTH_STATE_CHANGE_CMD = "svc bluetooth" | ||
private const val BLUETOOTH_STATE_CHANGE_ROOT_CMD = "su 0 svc bluetooth" | ||
private const val BLUETOOTH_STATE_CHECK_CMD = "settings get global bluetooth_on" | ||
private const val BLUETOOTH_STATE_CHECK_RESULT_ENABLED = "1" | ||
private const val BLUETOOTH_STATE_CHECK_RESULT_DISABLED = "0" | ||
private val ADB_RESULT_REGEX = Regex("exitCode=(\\d+), message=(.+)") | ||
} | ||
|
||
private val flakySafetyAlgorithm = FlakySafetyAlgorithm(logger) | ||
private val flakySafetyParams: FlakySafetyParams | ||
get() = FlakySafetyParams( | ||
timeoutMs = 1000, | ||
intervalMs = 100, | ||
allowedExceptions = setOf(AdbServerException::class.java) | ||
) | ||
|
||
override fun enable() { | ||
toggleBluetooth(enable = true) | ||
logger.i("Enable bluetooth") | ||
} | ||
|
||
override fun disable() { | ||
toggleBluetooth(enable = false) | ||
logger.i("Disable bluetooth") | ||
} | ||
|
||
/** | ||
* Toggles Bluetooth state | ||
* Tries, first and foremost, to send ADB command. If this attempt fails, | ||
* opens Android Settings screen and tries to switch Bluetooth setting thumb. | ||
*/ | ||
private fun toggleBluetooth(enable: Boolean) { | ||
if (!changeBluetoothStateUsingAdbServer(enable, BLUETOOTH_STATE_CHANGE_ROOT_CMD) && | ||
!changeBluetoothStateUsingAdbServer(enable, BLUETOOTH_STATE_CHANGE_CMD) | ||
) { | ||
toggleBluetoothUsingAndroidSettings(enable) | ||
logger.i("Bluetooth ${if (enable) "en" else "dis"}abled") | ||
} | ||
} | ||
|
||
/** | ||
* Tries to change Bluetooth state using AdbServer if it is available | ||
* @return true if Bluetooth state changed or false otherwise | ||
*/ | ||
private fun changeBluetoothStateUsingAdbServer(isEnabled: Boolean, changeCommand: String): Boolean = | ||
try { | ||
val (state, expectedResult) = when (isEnabled) { | ||
true -> CMD_STATE_ENABLE to BLUETOOTH_STATE_CHECK_RESULT_ENABLED | ||
false -> CMD_STATE_DISABLE to BLUETOOTH_STATE_CHECK_RESULT_DISABLED | ||
} | ||
adbServer.performShell("$changeCommand $state") | ||
flakySafetyAlgorithm.invokeFlakySafely(flakySafetyParams) { | ||
val result = adbServer.performShell(BLUETOOTH_STATE_CHECK_CMD) | ||
if (parseAdbResponse(result)?.trim() == expectedResult) true else | ||
throw AdbServerException("Failed to change Bluetooth state using ABD") | ||
} | ||
} catch (e: AdbServerException) { | ||
false | ||
} | ||
|
||
@Suppress("MagicNumber") | ||
private fun toggleBluetoothUsingAndroidSettings(enable: Boolean) { | ||
val height = targetContext.resources.displayMetrics.heightPixels | ||
val width = targetContext.resources.displayMetrics.widthPixels | ||
|
||
UiSystem { | ||
drag(width / 2, 0, width / 2, (height * 0.67).toInt(), 50) | ||
} | ||
|
||
UiSystem { | ||
drag(width / 2, 0, width / 2, (height * 0.67).toInt(), 50) | ||
} | ||
|
||
NotificationsShortScreen { | ||
mainNotification.click() | ||
} | ||
NotificationsFullScreen { | ||
bluetoothSwitch.setChecked(enable) | ||
} | ||
UiSystem { | ||
drag(width / 2, height, width / 2, 0, 50) | ||
} | ||
} | ||
|
||
private fun parseAdbResponse(response: List<String>): String? { | ||
val result = response.firstOrNull()?.lineSequence()?.first() ?: return null | ||
val match = ADB_RESULT_REGEX.find(result) ?: return null | ||
val (_, message) = match.destructured | ||
return message | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
.../androidTest/kotlin/com/kaspersky/kaspressample/device_tests/DeviceBluetoothSampleTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package com.kaspersky.kaspressample.device_tests | ||
|
||
import android.Manifest | ||
import android.bluetooth.BluetoothManager | ||
import android.content.ActivityNotFoundException | ||
import android.content.Context | ||
import android.os.Build | ||
import androidx.test.ext.junit.rules.activityScenarioRule | ||
import androidx.test.rule.GrantPermissionRule | ||
import com.kaspersky.kaspressample.device.DeviceSampleActivity | ||
import com.kaspersky.kaspressample.utils.SafeAssert.assertFalseSafely | ||
import com.kaspersky.kaspressample.utils.SafeAssert.assertTrueSafely | ||
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase | ||
import com.kaspersky.kaspresso.testcases.core.testcontext.BaseTestContext | ||
import org.junit.Rule | ||
import org.junit.Test | ||
|
||
class DeviceBluetoothSampleTest : TestCase() { | ||
|
||
@get:Rule | ||
val runtimePermissionRule: GrantPermissionRule = GrantPermissionRule.grant( | ||
Manifest.permission.WRITE_EXTERNAL_STORAGE, | ||
Manifest.permission.READ_EXTERNAL_STORAGE | ||
) | ||
|
||
@get:Rule | ||
val activityRule = activityScenarioRule<DeviceSampleActivity>() | ||
|
||
private val currentOsVersion = Build.VERSION.SDK_INT | ||
|
||
@Test | ||
fun bluetoothSampleTest() { | ||
before { | ||
tryToggleBluetooth(shouldEnable = true) | ||
}.after { | ||
tryToggleBluetooth(shouldEnable = true) | ||
}.run { | ||
|
||
step("Disable bluetooth") { | ||
tryToggleBluetooth(shouldEnable = false) | ||
checkBluetooth(shouldBeEnabled = false) | ||
} | ||
|
||
step("Enable bluetooth") { | ||
tryToggleBluetooth(shouldEnable = true) | ||
checkBluetooth(shouldBeEnabled = true) | ||
} | ||
} | ||
} | ||
|
||
private fun tryToggleBluetooth(shouldEnable: Boolean) { | ||
try { | ||
if (shouldEnable) { | ||
device.bluetooth.enable() | ||
} else { | ||
device.bluetooth.disable() | ||
} | ||
} catch (ex: ActivityNotFoundException) { // There's no Bluetooth activity on AVD with API < 30 | ||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) return | ||
throw ex | ||
} | ||
} | ||
|
||
private fun BaseTestContext.checkBluetooth(shouldBeEnabled: Boolean) { | ||
try { | ||
if (shouldBeEnabled) assertTrueSafely { isBluetoothEnabled() } else assertFalseSafely { isBluetoothEnabled() } | ||
} catch (assertionError: AssertionError) { | ||
// There is no mind to check bluetooth in Android emulators before Android 11 because | ||
// these simulators don't have a simulated bluetooth access point | ||
// that's why we just skip the bluetooth check on Android below 11 | ||
if (currentOsVersion < Build.VERSION_CODES.R) return | ||
else throw assertionError | ||
} | ||
} | ||
|
||
private fun BaseTestContext.isBluetoothEnabled(): Boolean = | ||
(device.targetContext.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager)?.adapter?.isEnabled | ||
?: throw IllegalStateException("BluetoothManager is unavailable") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters