Skip to content

Commit

Permalink
feat: allow airplane mode control
Browse files Browse the repository at this point in the history
  • Loading branch information
NyCodeGHG committed Feb 16, 2024
1 parent 4363b03 commit 77db04d
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 1 deletion.
4 changes: 4 additions & 0 deletions maestro-client/src/main/java/maestro/Driver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,8 @@ interface Driver {
fun setPermissions(appId: String, permissions: Map<String, String>)

fun addMedia(mediaFiles: List<File>)

fun isAirplaneModeEnabled(): Boolean

fun setAirplaneMode(enabled: Boolean)
}
2 changes: 2 additions & 0 deletions maestro-client/src/main/java/maestro/Errors.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ sealed class MaestroException(override val message: String) : RuntimeException(m
) : MaestroException(message)

class DeprecatedCommand(message: String) : MaestroException(message)

class NoRootAccess(message: String) : MaestroException(message)
}

sealed class MaestroDriverStartupException(override val message: String): RuntimeException() {
Expand Down
8 changes: 8 additions & 0 deletions maestro-client/src/main/java/maestro/Maestro.kt
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,14 @@ class Maestro(private val driver: Driver) : AutoCloseable {
return driver.isUnicodeInputSupported()
}

fun isAirplaneModeEnabled(): Boolean {
return driver.isAirplaneModeEnabled()
}

fun setAirplaneModeState(enabled: Boolean) {
driver.setAirplaneMode(enabled)
}

companion object {

private val LOGGER = LoggerFactory.getLogger(Maestro::class.java)
Expand Down
44 changes: 44 additions & 0 deletions maestro-client/src/main/java/maestro/drivers/AndroidDriver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,50 @@ class AndroidDriver(
LOGGER.info("[Done] Adding media files")
}

override fun isAirplaneModeEnabled(): Boolean {
return when (val result = shell("cmd connectivity airplane-mode").trim()) {
"No shell command implementation.", "" -> {
LOGGER.debug("Falling back to old airplane mode read method")
when (val fallbackResult = shell("settings get global airplane_mode_on").trim()) {
"0" -> false
"1" -> true
else -> throw IllegalStateException("Received invalid response from while trying to read airplane mode state: $fallbackResult")
}
}
"disabled" -> false
"enabled" -> true
else -> throw IllegalStateException("Received invalid response from while trying to read airplane mode state: $result")
}
}

override fun setAirplaneMode(enabled: Boolean) {
// fallback to old way on API < 28
if (getDeviceApiLevel() < 28) {
val num = if (enabled) 1 else 0
shell("settings put global airplane_mode_on $num")
// We need to broadcast the change to really apply it
broadcastAirplaneMode(enabled)
return
}
val value = if (enabled) "enable" else "disable"
shell("cmd connectivity airplane-mode $value")
}

private fun broadcastAirplaneMode(enabled: Boolean) {
val command = "am broadcast -a android.intent.action.AIRPLANE_MODE --ez state $enabled"
try {
shell(command)
} catch (e: IOException) {
if (e.message?.contains("Security exception: Permission Denial:") == true) {
try {
shell("su root $command")
} catch (e: IOException) {
throw MaestroException.NoRootAccess("Failed to broadcast Airplane mode change. Make sure to run an emulator with root access for API < 28")
}
}
}
}

fun setDeviceLocale(country: String, language: String): Int {
dadb.shell("pm grant dev.mobile.maestro android.permission.CHANGE_CONFIGURATION")
val response = dadb.shell("am broadcast -a dev.mobile.maestro.locale -n dev.mobile.maestro/.receivers.LocaleSettingReceiver --es lang $language --es country $country")
Expand Down
9 changes: 9 additions & 0 deletions maestro-client/src/main/java/maestro/drivers/IOSDriver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,15 @@ class IOSDriver(
LOGGER.info("[Done] Adding media files")
}

override fun isAirplaneModeEnabled(): Boolean {
LOGGER.warn("Airplane mode is not available on iOS simulators")
return false
}

override fun setAirplaneMode(enabled: Boolean) {
LOGGER.warn("Airplane mode is not available on iOS simulators")
}

private fun addMediaToDevice(mediaFile: File) {
val namedSource = NamedSource(
mediaFile.name,
Expand Down
8 changes: 8 additions & 0 deletions maestro-client/src/main/java/maestro/drivers/WebDriver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,14 @@ class WebDriver(val isStudio: Boolean) : Driver {
// noop for web
}

override fun isAirplaneModeEnabled(): Boolean {
TODO("Not yet implemented")
}

override fun setAirplaneMode(enabled: Boolean) {
TODO("Not yet implemented")
}

companion object {
private const val SCREENSHOT_DIFF_THRESHOLD = 0.005
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package maestro.orchestra

import com.fasterxml.jackson.annotation.JsonProperty
import maestro.KeyCode
import maestro.Point
import maestro.ScrollDirection
Expand Down Expand Up @@ -866,6 +867,28 @@ data class StopRecordingCommand(
}
}

enum class AirplaneValue {
@JsonProperty("enable")
Enable,
@JsonProperty("disable")
Disable,
}

data class SetAirplaneModeCommand(
val value: AirplaneValue,
) : Command {
override fun description(): String {
return when (value) {
AirplaneValue.Enable -> "Enable Airplane mode"
AirplaneValue.Disable -> "Disable Airplane mode"
}
}

override fun evaluateScripts(jsEngine: JsEngine): Command {
return this
}
}

internal fun tapOnDescription(isLongPress: Boolean?, repeat: TapRepeat?): String {
return if (isLongPress == true) "Long press"
else if (repeat != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ data class MaestroCommand(
val startRecordingCommand: StartRecordingCommand? = null,
val stopRecordingCommand: StopRecordingCommand? = null,
val addMediaCommand: AddMediaCommand? = null,
val setAirplaneModeCommand: SetAirplaneModeCommand? = null,
) {

constructor(command: Command) : this(
Expand Down Expand Up @@ -98,7 +99,8 @@ data class MaestroCommand(
travelCommand = command as? TravelCommand,
startRecordingCommand = command as? StartRecordingCommand,
stopRecordingCommand = command as? StopRecordingCommand,
addMediaCommand = command as? AddMediaCommand
addMediaCommand = command as? AddMediaCommand,
setAirplaneModeCommand = command as? SetAirplaneModeCommand,
)

fun asCommand(): Command? = when {
Expand Down Expand Up @@ -136,6 +138,7 @@ data class MaestroCommand(
startRecordingCommand != null -> startRecordingCommand
stopRecordingCommand != null -> stopRecordingCommand
addMediaCommand != null -> addMediaCommand
setAirplaneModeCommand != null -> setAirplaneModeCommand
else -> null
}

Expand Down
10 changes: 10 additions & 0 deletions maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ class Orchestra(
is StartRecordingCommand -> startRecordingCommand(command)
is StopRecordingCommand -> stopRecordingCommand()
is AddMediaCommand -> addMediaCommand(command.mediaPaths)
is SetAirplaneModeCommand -> setAirplaneMode(command)
else -> true
}.also { mutating ->
if (mutating) {
Expand All @@ -285,6 +286,15 @@ class Orchestra(
}
}

private fun setAirplaneMode(command: SetAirplaneModeCommand): Boolean {
when (command.value) {
AirplaneValue.Enable -> maestro.setAirplaneModeState(true)
AirplaneValue.Disable -> maestro.setAirplaneModeState(false)
}

return true
}

private fun travelCommand(command: TravelCommand): Boolean {
Traveller.travel(
maestro = maestro,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ data class YamlFluentCommand(
val startRecording: YamlStartRecording? = null,
val stopRecording: YamlStopRecording? = null,
val addMedia: YamlAddMedia? = null,
val setAirplaneMode: YamlSetAirplaneMode? = null,
) {

@SuppressWarnings("ComplexMethod")
Expand Down Expand Up @@ -211,6 +212,7 @@ data class YamlFluentCommand(
val tapRepeat = TapRepeat(2, delay)
listOf(tapCommand(doubleTapOn, tapRepeat = tapRepeat))
}
setAirplaneMode != null -> listOf(MaestroCommand(SetAirplaneModeCommand(setAirplaneMode.value)))
else -> throw SyntaxError("Invalid command: No mapping provided for $this")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package maestro.orchestra.yaml

import com.fasterxml.jackson.annotation.JsonCreator
import maestro.orchestra.AirplaneValue

data class YamlSetAirplaneMode(
val value: AirplaneValue,
) {
companion object {
@JvmStatic
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
fun parse(value: AirplaneValue): YamlSetAirplaneMode {
return YamlSetAirplaneMode(value)
}
}
}

0 comments on commit 77db04d

Please sign in to comment.