diff --git a/maestro-android/build.gradle b/maestro-android/build.gradle index 0e8d161e71..5722d9ce58 100644 --- a/maestro-android/build.gradle +++ b/maestro-android/build.gradle @@ -38,7 +38,7 @@ android { defaultConfig { applicationId "dev.mobile.maestro" - minSdk 21 + minSdk 24 targetSdk 31 versionCode 1 versionName "1.0" diff --git a/maestro-android/src/androidTest/java/dev/mobile/maestro/AccessibilityNodeInfoExt.kt b/maestro-android/src/androidTest/java/dev/mobile/maestro/AccessibilityNodeInfoExt.kt new file mode 100644 index 0000000000..74201d8e7d --- /dev/null +++ b/maestro-android/src/androidTest/java/dev/mobile/maestro/AccessibilityNodeInfoExt.kt @@ -0,0 +1,24 @@ +package dev.mobile.maestro + +import android.os.Build +import android.view.accessibility.AccessibilityNodeInfo + +object AccessibilityNodeInfoExt { + + /** + * Retrieves the hint text associated with this [android.view.accessibility.AccessibilityNodeInfo]. + * + * If the device API level is below 26 (Oreo) or the hint text is null, this function provides a fallback + * by returning an empty CharSequence instead. + * + * @return [CharSequence] representing the hint text or its fallback. + */ + fun AccessibilityNodeInfo.getHintOrFallback(): CharSequence { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this.hintText != null) { + this.hintText + } else { + "" + } + } + +} diff --git a/maestro-android/src/androidTest/java/dev/mobile/maestro/ViewHierarchy.kt b/maestro-android/src/androidTest/java/dev/mobile/maestro/ViewHierarchy.kt index 6632201b03..8979eec597 100644 --- a/maestro-android/src/androidTest/java/dev/mobile/maestro/ViewHierarchy.kt +++ b/maestro-android/src/androidTest/java/dev/mobile/maestro/ViewHierarchy.kt @@ -15,6 +15,7 @@ import android.widget.ListView import android.widget.TableLayout import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice +import dev.mobile.maestro.AccessibilityNodeInfoExt.getHintOrFallback import org.xmlpull.v1.XmlSerializer import java.io.IOException import java.io.OutputStream @@ -120,7 +121,7 @@ object ViewHierarchy { serializer.attribute("", "NAF", java.lang.Boolean.toString(true)) } serializer.attribute("", "index", Integer.toString(index)) - serializer.attribute("", "hintText", safeCharSeqToString(node.hintText)) + serializer.attribute("", "hintText", safeCharSeqToString(node.getHintOrFallback())) serializer.attribute("", "text", safeCharSeqToString(node.text)) serializer.attribute("", "resource-id", safeCharSeqToString(node.viewIdResourceName)) serializer.attribute("", "class", safeCharSeqToString(node.className)) @@ -276,4 +277,4 @@ object ViewHierarchy { } } -} \ No newline at end of file +} diff --git a/maestro-client/src/main/java/maestro/drivers/AndroidDriver.kt b/maestro-client/src/main/java/maestro/drivers/AndroidDriver.kt index b87c8ecc5c..79ac662cca 100644 --- a/maestro-client/src/main/java/maestro/drivers/AndroidDriver.kt +++ b/maestro-client/src/main/java/maestro/drivers/AndroidDriver.kt @@ -88,9 +88,15 @@ class AndroidDriver( private fun startInstrumentationSession() { val startTime = System.currentTimeMillis() - val instrumentationCommand = "am instrument -w -m -e debug false " + - "-e class 'dev.mobile.maestro.MaestroDriverService#grpcServer' " + - "dev.mobile.maestro.test/androidx.test.runner.AndroidJUnitRunner &\n" + val apiLevel = getDeviceApiLevel() + + val instrumentationCommand = buildString { + append("am instrument -w ") + if (apiLevel >= 26) append("-m ") + append("-e debug false ") + append("-e class 'dev.mobile.maestro.MaestroDriverService#grpcServer' ") + append("dev.mobile.maestro.test/androidx.test.runner.AndroidJUnitRunner &\n") + } while (System.currentTimeMillis() - startTime < getStartupTimeout()) { instrumentationSession = dadb.openShell(instrumentationCommand) @@ -105,6 +111,15 @@ class AndroidDriver( throw AndroidInstrumentationSetupFailure("Maestro instrumentation could not be initialized") } + private fun getDeviceApiLevel(): Int { + val response = dadb.openShell("getprop ro.build.version.sdk").readAll() + if (response.exitCode != 0) { + throw IOException("Failed to get device API level: ${response.errorOutput}") + } + return response.output.trim().toIntOrNull() ?: throw IOException("Invalid API level: ${response.output}") + } + + private fun allocateForwarder() { PORT_TO_FORWARDER[hostPort]?.close() PORT_TO_ALLOCATION_POINT[hostPort]?.let { diff --git a/maestro-client/src/main/resources/maestro-app.apk b/maestro-client/src/main/resources/maestro-app.apk index 7fd0d8384d..a8b311fe23 100644 Binary files a/maestro-client/src/main/resources/maestro-app.apk and b/maestro-client/src/main/resources/maestro-app.apk differ diff --git a/maestro-client/src/main/resources/maestro-server.apk b/maestro-client/src/main/resources/maestro-server.apk index 227981913f..37e57f7c28 100644 Binary files a/maestro-client/src/main/resources/maestro-server.apk and b/maestro-client/src/main/resources/maestro-server.apk differ