From f5b1f436db10915e35edbc9d4586ff1ed7c6c2e1 Mon Sep 17 00:00:00 2001 From: Anton Malinskiy Date: Fri, 13 Dec 2024 19:47:42 +1000 Subject: [PATCH] feat(android): add support android:mock_location --- .../config/vendor/VendorConfiguration.kt | 8 +++++++- docs/runner/android/configure.md | 15 +++++++++++++++ .../marathon/android/AndroidAppInstaller.kt | 18 ++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/VendorConfiguration.kt b/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/VendorConfiguration.kt index cc846c61a..189393706 100644 --- a/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/VendorConfiguration.kt +++ b/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/VendorConfiguration.kt @@ -38,6 +38,7 @@ const val DEFAULT_APPLICATION_PM_CLEAR = false const val DEFAULT_TEST_APPLICATION_PM_CLEAR = false const val DEFAULT_INSTALL_OPTIONS = "" const val DEFAULT_WAIT_FOR_DEVICES_TIMEOUT = 30000L +const val DEFAULT_MOCK_LOCATION = false @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, @@ -77,6 +78,7 @@ sealed class VendorConfiguration { @JsonProperty("adbServers") val adbServers: List = listOf(AdbEndpoint()), @JsonProperty("disableWindowAnimation") val disableWindowAnimation: Boolean = DEFAULT_DISABLE_WINDOW_ANIMATION, @JsonProperty("profilingConfiguration") val profilingConfiguration: ProfilingConfiguration = ProfilingConfiguration(), + @JsonProperty("mockLocation") val mockLocation: Boolean = DEFAULT_MOCK_LOCATION ) : VendorConfiguration() { fun safeAndroidSdk(): File = androidSdk ?: throw ConfigurationException("No android SDK path specified") @@ -125,6 +127,8 @@ sealed class VendorConfiguration { var testAccessConfiguration: TestAccessConfiguration = TestAccessConfiguration() var adbServers: List = listOf(AdbEndpoint()) var disableWindowAnimation: Boolean = DEFAULT_DISABLE_WINDOW_ANIMATION + var profilingConfiguration: ProfilingConfiguration = ProfilingConfiguration() + var mockLocation: Boolean = DEFAULT_MOCK_LOCATION fun build() = AndroidConfiguration( androidSdk, @@ -149,7 +153,9 @@ sealed class VendorConfiguration { testParserConfiguration, testAccessConfiguration, adbServers, - disableWindowAnimation + disableWindowAnimation, + profilingConfiguration, + mockLocation, ) } diff --git a/docs/runner/android/configure.md b/docs/runner/android/configure.md index 0ec757bc6..642fb24a4 100644 --- a/docs/runner/android/configure.md +++ b/docs/runner/android/configure.md @@ -1089,6 +1089,21 @@ marathon { +### Location mock access +Some tests require mocking device location. Marathon can setup access for instrumentation package if you enable this option as following: + + + + +```yaml +vendorConfiguration: + type: "Android" + mockLocation: true +``` + + + + [1]: https://developer.android.com/studio/ [2]: https://developer.android.com/studio/command-line/adb#issuingcommands diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/AndroidAppInstaller.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/AndroidAppInstaller.kt index d4c97bae0..90d76952d 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/AndroidAppInstaller.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/AndroidAppInstaller.kt @@ -53,9 +53,27 @@ class AndroidAppInstaller(configuration: Configuration) { logger.debug { "Installing instrumentation package to ${device.serialNumber}" } reinstall(device, applicationInfo.instrumentationPackage, bundle.testApplication) + appops(device, applicationInfo.instrumentationPackage) logger.debug { "Prepare installation finished for ${device.serialNumber}" } } + private suspend fun appops(device: AndroidDevice, instrumentationPackage: String) { + if (androidConfiguration.mockLocation) { + if (device.apiLevel < 23) { + logger.warn { "Can't setup mock location: device ${device.serialNumber} doesn't support appops" } + return + } + + val appopsMessage = device.criticalExecuteShellCommand("appops set $instrumentationPackage android:mock_location allow") + appopsMessage.let { + if (it.exitCode != 0) { + val (output, _) = device.criticalExecuteShellCommand("appops query-op android:mock_location allow") + logger.error { "Can't set android:mock_location on $instrumentationPackage. List of apps currently using android:mock_location:$output" } + } + } + } + } + /** * @throws DeviceSetupException if unable to reinstall (even with retries) */