From ac72777818e9b18f481bfe007747867db18bac41 Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Tue, 14 Jul 2026 14:28:20 +0200 Subject: [PATCH 01/11] Clean up formatting and naming - Apply missing spotless changes --- .../AppFunctionInstrumentationTest.kt | 1 - .../chatapp/uicomponents/ChatScreen.kt | 18 ++++--- ChatApp/gradle/libs.versions.toml | 2 +- ChatApp/shared/build.gradle.kts | 1 - .../example/chatapp/BaseChatApplication.kt | 1 + .../BaseChatAppFunctionService.kt | 10 ++-- .../com/example/chatapp/appfunctions/Util.kt | 4 +- .../com/example/chatapp/util/Linkify.kt | 31 ++++++----- ChatApp/wear/build.gradle.kts | 1 - .../wear/uicomponents/WearChatScreen.kt | 7 +-- agent/app/build.gradle.kts | 2 +- .../data/LlmProviderIntegrationTestBase.kt | 3 +- ...ions.kt => AbstractBuiltInAppFunctions.kt} | 30 +++++------ ...s.kt => AbstractFakeAppFunctionService.kt} | 8 +-- .../agent/domain/AgentOrchestrator.kt | 3 +- .../AppFunctionExceptionFormatter.kt | 5 +- .../debugging/FunctionsFoundContent.kt | 1 - .../agent/domain/AgentOrchestratorTest.kt | 44 ++++++++-------- .../AppFunctionExceptionFormatterTest.kt | 7 ++- .../ExecuteAppFunctionUseCaseTest.kt | 51 ++++++++++--------- 20 files changed, 118 insertions(+), 112 deletions(-) rename agent/app/src/main/java/com/example/appfunctions/agent/data/{BuiltInAppFunctions.kt => AbstractBuiltInAppFunctions.kt} (85%) rename agent/app/src/main/java/com/example/appfunctions/agent/data/{FakeAppFunctions.kt => AbstractFakeAppFunctionService.kt} (90%) diff --git a/ChatApp/app/src/androidTest/kotlin/com/example/chatapp/appfunctions/AppFunctionInstrumentationTest.kt b/ChatApp/app/src/androidTest/kotlin/com/example/chatapp/appfunctions/AppFunctionInstrumentationTest.kt index 74e7e8e..4678127 100644 --- a/ChatApp/app/src/androidTest/kotlin/com/example/chatapp/appfunctions/AppFunctionInstrumentationTest.kt +++ b/ChatApp/app/src/androidTest/kotlin/com/example/chatapp/appfunctions/AppFunctionInstrumentationTest.kt @@ -24,7 +24,6 @@ import androidx.appfunctions.ExecuteAppFunctionRequest import androidx.appfunctions.ExecuteAppFunctionResponse import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.example.chatapp.appfunctions.ChatAppFunctionService import com.example.chatapp.data.MessageRepository import com.example.chatapp.data.RecipientsRepository import com.google.common.truth.Truth.assertThat diff --git a/ChatApp/app/src/main/kotlin/com/example/chatapp/uicomponents/ChatScreen.kt b/ChatApp/app/src/main/kotlin/com/example/chatapp/uicomponents/ChatScreen.kt index 388dd89..13f1b9d 100644 --- a/ChatApp/app/src/main/kotlin/com/example/chatapp/uicomponents/ChatScreen.kt +++ b/ChatApp/app/src/main/kotlin/com/example/chatapp/uicomponents/ChatScreen.kt @@ -269,14 +269,16 @@ fun MessageBubble( } } if (message.content.isNotEmpty()) { - val linkColor = if (message.isInbound) { - MaterialTheme.colorScheme.primary - } else { - MaterialTheme.colorScheme.inversePrimary - } - val annotatedText = remember(message.content, linkColor) { - linkifyString(message.content, linkColor = linkColor) - } + val linkColor = + if (message.isInbound) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.inversePrimary + } + val annotatedText = + remember(message.content, linkColor) { + linkifyString(message.content, linkColor = linkColor) + } Text( modifier = Modifier.padding(16.dp), text = annotatedText, diff --git a/ChatApp/gradle/libs.versions.toml b/ChatApp/gradle/libs.versions.toml index 9a44bde..93ac268 100644 --- a/ChatApp/gradle/libs.versions.toml +++ b/ChatApp/gradle/libs.versions.toml @@ -13,7 +13,7 @@ composeMaterialIconsAndroid = "1.11.1" composeMaterialIconsCore = "1.7.8" hilt = "2.59.2" androidxHiltNavigationCompose = "1.3.0" -ksp = "2.3.6" +ksp = "2.3.9" spotless = "8.5.0" truth = "1.4.5" coil = "2.7.0" diff --git a/ChatApp/shared/build.gradle.kts b/ChatApp/shared/build.gradle.kts index b9aba68..5a720e6 100644 --- a/ChatApp/shared/build.gradle.kts +++ b/ChatApp/shared/build.gradle.kts @@ -48,5 +48,4 @@ dependencies { // App functions implementation(libs.androidx.appfunctions) ksp(libs.androidx.appfunctions.compiler) - } diff --git a/ChatApp/shared/src/main/kotlin/com/example/chatapp/BaseChatApplication.kt b/ChatApp/shared/src/main/kotlin/com/example/chatapp/BaseChatApplication.kt index b051a26..4d53914 100644 --- a/ChatApp/shared/src/main/kotlin/com/example/chatapp/BaseChatApplication.kt +++ b/ChatApp/shared/src/main/kotlin/com/example/chatapp/BaseChatApplication.kt @@ -16,4 +16,5 @@ package com.example.chatapp import android.app.Application + abstract class BaseChatApplication : Application() diff --git a/ChatApp/shared/src/main/kotlin/com/example/chatapp/appfunctions/BaseChatAppFunctionService.kt b/ChatApp/shared/src/main/kotlin/com/example/chatapp/appfunctions/BaseChatAppFunctionService.kt index caae605..19cfa52 100644 --- a/ChatApp/shared/src/main/kotlin/com/example/chatapp/appfunctions/BaseChatAppFunctionService.kt +++ b/ChatApp/shared/src/main/kotlin/com/example/chatapp/appfunctions/BaseChatAppFunctionService.kt @@ -19,19 +19,19 @@ import android.app.PendingIntent import android.content.Intent import android.net.Uri import androidx.annotation.RequiresApi +import androidx.appfunctions.AppFunction import androidx.appfunctions.AppFunctionAppUnknownException import androidx.appfunctions.AppFunctionElementNotFoundException import androidx.appfunctions.AppFunctionInvalidArgumentException import androidx.appfunctions.AppFunctionService import androidx.appfunctions.AppFunctionServiceEntryPoint import androidx.appfunctions.AppFunctionStringValueConstraint -import androidx.appfunctions.AppFunction import com.example.chatapp.data.CallManager import com.example.chatapp.data.MessageRepository import com.example.chatapp.data.RecipientsRepository import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject import kotlinx.coroutines.CancellationException +import javax.inject.Inject /** * Service entry point for chat-related AppFunctions such as searching contacts, sending messages, and making calls. @@ -44,7 +44,9 @@ import kotlinx.coroutines.CancellationException ) abstract class BaseChatAppFunctionService : AppFunctionService() { @Inject lateinit var messageRepository: MessageRepository + @Inject lateinit var recipientsRepository: RecipientsRepository + @Inject lateinit var callManager: CallManager /** @@ -153,9 +155,7 @@ abstract class BaseChatAppFunctionService : AppFunctionService() { * @throws AppFunctionElementNotFoundException If no recipient exists for endpointValue. If thrown, call "searchContacts" to find the correct ID. */ @AppFunction(isDescribedByKDoc = true) - suspend fun makeCall( - endpointValue: String, - ): PendingIntent { + suspend fun makeCall(endpointValue: String): PendingIntent { val recipient = recipientsRepository.getRecipientById(endpointValue) ?: throw AppFunctionElementNotFoundException( diff --git a/ChatApp/shared/src/main/kotlin/com/example/chatapp/appfunctions/Util.kt b/ChatApp/shared/src/main/kotlin/com/example/chatapp/appfunctions/Util.kt index 81d0354..51ccd17 100644 --- a/ChatApp/shared/src/main/kotlin/com/example/chatapp/appfunctions/Util.kt +++ b/ChatApp/shared/src/main/kotlin/com/example/chatapp/appfunctions/Util.kt @@ -13,12 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.example.chatapp.appfunctions import androidx.appfunctions.AppFunctionSerializable - /** * Represents a result from a contact or group search. */ @@ -67,4 +65,4 @@ data class ChatGroup( val name: String, /** List of members belonging to the group. */ val recipients: List, -) \ No newline at end of file +) diff --git a/ChatApp/shared/src/main/kotlin/com/example/chatapp/util/Linkify.kt b/ChatApp/shared/src/main/kotlin/com/example/chatapp/util/Linkify.kt index f0a8f93..453ff4f 100644 --- a/ChatApp/shared/src/main/kotlin/com/example/chatapp/util/Linkify.kt +++ b/ChatApp/shared/src/main/kotlin/com/example/chatapp/util/Linkify.kt @@ -24,7 +24,10 @@ import androidx.compose.ui.text.TextLinkStyles import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextDecoration -fun linkifyString(text: String, linkColor: Color? = null): AnnotatedString { +fun linkifyString( + text: String, + linkColor: Color? = null, +): AnnotatedString { val matcher = Patterns.WEB_URL.matcher(text) var lastIndex = 0 return buildAnnotatedString { @@ -35,22 +38,24 @@ fun linkifyString(text: String, linkColor: Color? = null): AnnotatedString { append(text.substring(lastIndex, start)) - val uriStr = if (url.startsWith("http://", ignoreCase = true) || url.startsWith("https://", ignoreCase = true)) { - url - } else { - "https://$url" - } - - val linkStyle = SpanStyle( - color = linkColor ?: Color.Unspecified, - textDecoration = TextDecoration.Underline - ) + val uriStr = + if (url.startsWith("http://", ignoreCase = true) || url.startsWith("https://", ignoreCase = true)) { + url + } else { + "https://$url" + } + + val linkStyle = + SpanStyle( + color = linkColor ?: Color.Unspecified, + textDecoration = TextDecoration.Underline, + ) pushLink( LinkAnnotation.Url( url = uriStr, - styles = TextLinkStyles(style = linkStyle) - ) + styles = TextLinkStyles(style = linkStyle), + ), ) append(url) pop() diff --git a/ChatApp/wear/build.gradle.kts b/ChatApp/wear/build.gradle.kts index fe02a1e..23bbd5b 100644 --- a/ChatApp/wear/build.gradle.kts +++ b/ChatApp/wear/build.gradle.kts @@ -67,7 +67,6 @@ dependencies { // App functions implementation(libs.androidx.appfunctions) ksp(libs.androidx.appfunctions.compiler) - } // AppFunctions ksp option diff --git a/ChatApp/wear/src/main/kotlin/com/example/chatapp/wear/uicomponents/WearChatScreen.kt b/ChatApp/wear/src/main/kotlin/com/example/chatapp/wear/uicomponents/WearChatScreen.kt index c710615..5b21082 100644 --- a/ChatApp/wear/src/main/kotlin/com/example/chatapp/wear/uicomponents/WearChatScreen.kt +++ b/ChatApp/wear/src/main/kotlin/com/example/chatapp/wear/uicomponents/WearChatScreen.kt @@ -70,9 +70,10 @@ fun WearChatScreen( title = { Text(text = if (message.isInbound) message.senderName ?: "Sender" else "Me") }, ) { val linkColor = MaterialTheme.colorScheme.primary - val annotatedText = remember(message.content, linkColor) { - linkifyString(text = message.content, linkColor = linkColor) - } + val annotatedText = + remember(message.content, linkColor) { + linkifyString(text = message.content, linkColor = linkColor) + } Text(text = annotatedText) } } diff --git a/agent/app/build.gradle.kts b/agent/app/build.gradle.kts index b2a3b3d..d6ce120 100644 --- a/agent/app/build.gradle.kts +++ b/agent/app/build.gradle.kts @@ -30,7 +30,7 @@ android { defaultConfig { applicationId = "com.example.appfunctions.agent" - minSdk = 35 + minSdk = 36 targetSdk = 37 versionCode = 1 versionName = "0.0.0" diff --git a/agent/app/src/androidTest/java/com/example/appfunctions/agent/data/LlmProviderIntegrationTestBase.kt b/agent/app/src/androidTest/java/com/example/appfunctions/agent/data/LlmProviderIntegrationTestBase.kt index e7d44af..33e4dd8 100644 --- a/agent/app/src/androidTest/java/com/example/appfunctions/agent/data/LlmProviderIntegrationTestBase.kt +++ b/agent/app/src/androidTest/java/com/example/appfunctions/agent/data/LlmProviderIntegrationTestBase.kt @@ -33,6 +33,7 @@ import kotlinx.serialization.json.Json import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue +import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.Test @@ -58,7 +59,7 @@ abstract class LlmProviderIntegrationTestBase { fun setUp() { val arguments = androidx.test.platform.app.InstrumentationRegistry.getArguments() apiKey = arguments.getString(apiKeyArgumentKey) ?: "" - assertTrue( + assumeTrue( "API key is required. Please provide it via instrumentation arguments.", apiKey.isNotEmpty() && apiKey != "PLACEHOLDER", ) diff --git a/agent/app/src/main/java/com/example/appfunctions/agent/data/BuiltInAppFunctions.kt b/agent/app/src/main/java/com/example/appfunctions/agent/data/AbstractBuiltInAppFunctions.kt similarity index 85% rename from agent/app/src/main/java/com/example/appfunctions/agent/data/BuiltInAppFunctions.kt rename to agent/app/src/main/java/com/example/appfunctions/agent/data/AbstractBuiltInAppFunctions.kt index 693f7bb..d271201 100644 --- a/agent/app/src/main/java/com/example/appfunctions/agent/data/BuiltInAppFunctions.kt +++ b/agent/app/src/main/java/com/example/appfunctions/agent/data/AbstractBuiltInAppFunctions.kt @@ -22,7 +22,6 @@ import android.content.pm.PackageManager import android.location.Address import android.location.Geocoder import android.location.LocationManager -import androidx.annotation.RequiresApi import androidx.appfunctions.AppFunction import androidx.appfunctions.AppFunctionSerializable import androidx.appfunctions.AppFunctionService @@ -35,29 +34,27 @@ import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine /** Built-in AppFunctions for location and geocoding services. */ -@RequiresApi(36) @AndroidEntryPoint @AppFunctionServiceEntryPoint( serviceName = "BuiltInAppFunctionService", appFunctionXmlFileName = "builtin_app_function_service", ) -abstract class BaseBuiltInAppFunctionService : AppFunctionService() { +abstract class AbstractBuiltInAppFunctions : AppFunctionService() { /** - * Geocode a physical address string into its latitude and longitude coordinates. + * Geocodes a physical address string into its latitude and longitude coordinates. * * @param address The physical address to geocode (e.g., "1600 Amphitheatre Pkwy, Mountain View, * CA"). * @return The latitude and longitude coordinates of the address, or null if geocoding fails. */ @AppFunction(isDescribedByKDoc = true) - suspend fun geocodeAddress( - address: String, - ): LatLng? { + suspend fun geocodeAddress(address: String): LatLng? { + val context = this if (!Geocoder.isPresent()) { return null } - val geocoder = Geocoder(this) + val geocoder = Geocoder(context) return withContext(Dispatchers.IO) { try { @@ -90,7 +87,7 @@ abstract class BaseBuiltInAppFunctionService : AppFunctionService() { } /** - * Retrieve the current latitude and longitude coordinates of the device. + * Retrieves the current latitude and longitude coordinates of the device. * * @return The current location coordinates of the device, or null if location is unavailable or * permission is denied. @@ -99,25 +96,26 @@ abstract class BaseBuiltInAppFunctionService : AppFunctionService() { @AppFunction(isDescribedByKDoc = true) suspend fun getCurrentLocation(): LatLng? = withContext(Dispatchers.Default) { + val context = this@AbstractBuiltInAppFunctions + // Check permissions val hasFineLocation = - ContextCompat.checkSelfPermission( - this@BaseBuiltInAppFunctionService, - Manifest.permission.ACCESS_FINE_LOCATION, - ) == PackageManager.PERMISSION_GRANTED + ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == + PackageManager.PERMISSION_GRANTED val hasCoarseLocation = ContextCompat.checkSelfPermission( - this@BaseBuiltInAppFunctionService, + context, Manifest.permission.ACCESS_COARSE_LOCATION, - ) == PackageManager.PERMISSION_GRANTED + ) == + PackageManager.PERMISSION_GRANTED if (!hasFineLocation && !hasCoarseLocation) { throw IllegalStateException("Location permission is not granted") } val locationManager = - this@BaseBuiltInAppFunctionService.getSystemService(Context.LOCATION_SERVICE) as LocationManager + context.getSystemService(Context.LOCATION_SERVICE) as LocationManager try { // Try GPS Provider first diff --git a/agent/app/src/main/java/com/example/appfunctions/agent/data/FakeAppFunctions.kt b/agent/app/src/main/java/com/example/appfunctions/agent/data/AbstractFakeAppFunctionService.kt similarity index 90% rename from agent/app/src/main/java/com/example/appfunctions/agent/data/FakeAppFunctions.kt rename to agent/app/src/main/java/com/example/appfunctions/agent/data/AbstractFakeAppFunctionService.kt index 1f82650..773ed08 100644 --- a/agent/app/src/main/java/com/example/appfunctions/agent/data/FakeAppFunctions.kt +++ b/agent/app/src/main/java/com/example/appfunctions/agent/data/AbstractFakeAppFunctionService.kt @@ -15,7 +15,6 @@ */ package com.example.appfunctions.agent.data -import androidx.annotation.RequiresApi import androidx.appfunctions.AppFunction import androidx.appfunctions.AppFunctionSerializable import androidx.appfunctions.AppFunctionService @@ -23,13 +22,12 @@ import androidx.appfunctions.AppFunctionServiceEntryPoint import dagger.hilt.android.AndroidEntryPoint /** Fake AppFunction service used for testing and verification. */ -@RequiresApi(36) @AndroidEntryPoint @AppFunctionServiceEntryPoint( serviceName = "FakeAppFunctionService", appFunctionXmlFileName = "fake_app_function_service", ) -abstract class BaseFakeAppFunctionService : AppFunctionService() { +abstract class AbstractFakeAppFunctionService : AppFunctionService() { /** * Execute a fake function for testing purposes. * @@ -37,9 +35,7 @@ abstract class BaseFakeAppFunctionService : AppFunctionService() { * @return The test response containing output data. */ @AppFunction(isDescribedByKDoc = true) - suspend fun fakeFunction( - params: FakeParams, - ): FakeResponse { + suspend fun fakeFunction(params: FakeParams): FakeResponse { return FakeResponse("success") } diff --git a/agent/app/src/main/java/com/example/appfunctions/agent/domain/AgentOrchestrator.kt b/agent/app/src/main/java/com/example/appfunctions/agent/domain/AgentOrchestrator.kt index 6b47c36..8ba18b0 100644 --- a/agent/app/src/main/java/com/example/appfunctions/agent/domain/AgentOrchestrator.kt +++ b/agent/app/src/main/java/com/example/appfunctions/agent/domain/AgentOrchestrator.kt @@ -17,7 +17,6 @@ package com.example.appfunctions.agent.domain import android.app.PendingIntent import android.util.Log -import androidx.appfunctions.AppFunctionException import androidx.appfunctions.metadata.AppFunctionMetadata import com.example.appfunctions.agent.data.LlmProviderName import com.example.appfunctions.agent.data.SettingsRepository @@ -38,7 +37,6 @@ import com.example.appfunctions.agent.domain.chat.UpdateMessageUseCase import com.example.appfunctions.agent.domain.chat.UpdateThreadParams import com.example.appfunctions.agent.domain.chat.UpdateThreadUseCase import com.example.appfunctions.agent.domain.pendingintent.SavePendingIntentUseCase -import java.util.UUID import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope @@ -50,6 +48,7 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext +import java.util.UUID import javax.inject.Inject import javax.inject.Singleton diff --git a/agent/app/src/main/java/com/example/appfunctions/agent/domain/appfunction/AppFunctionExceptionFormatter.kt b/agent/app/src/main/java/com/example/appfunctions/agent/domain/appfunction/AppFunctionExceptionFormatter.kt index c3b381a..fc42248 100644 --- a/agent/app/src/main/java/com/example/appfunctions/agent/domain/appfunction/AppFunctionExceptionFormatter.kt +++ b/agent/app/src/main/java/com/example/appfunctions/agent/domain/appfunction/AppFunctionExceptionFormatter.kt @@ -22,7 +22,10 @@ object AppFunctionExceptionFormatter { /** * Formats the given [exception] including its class name and message. */ - fun format(exception: AppFunctionException, functionId: String? = null): String { + fun format( + exception: AppFunctionException, + functionId: String? = null, + ): String { val className = exception.javaClass.simpleName val message = exception.errorMessage ?: exception.message ?: "No error message provided" val prefix = if (functionId != null) "Tool execution failed for $functionId: " else "" diff --git a/agent/app/src/main/java/com/example/appfunctions/agent/ui/screens/debugging/FunctionsFoundContent.kt b/agent/app/src/main/java/com/example/appfunctions/agent/ui/screens/debugging/FunctionsFoundContent.kt index 146d1f6..ca95ccb 100644 --- a/agent/app/src/main/java/com/example/appfunctions/agent/ui/screens/debugging/FunctionsFoundContent.kt +++ b/agent/app/src/main/java/com/example/appfunctions/agent/ui/screens/debugging/FunctionsFoundContent.kt @@ -65,7 +65,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.appfunctions.AppFunctionException import com.example.appfunctions.agent.R import com.example.appfunctions.agent.domain.appfunction.AppFunctionExceptionFormatter import com.example.appfunctions.agent.domain.appfunction.ExecuteAppFunctionResult diff --git a/agent/app/src/test/java/com/example/appfunctions/agent/domain/AgentOrchestratorTest.kt b/agent/app/src/test/java/com/example/appfunctions/agent/domain/AgentOrchestratorTest.kt index 4dbf97d..d424089 100644 --- a/agent/app/src/test/java/com/example/appfunctions/agent/domain/AgentOrchestratorTest.kt +++ b/agent/app/src/test/java/com/example/appfunctions/agent/domain/AgentOrchestratorTest.kt @@ -282,17 +282,18 @@ class AgentOrchestratorTest { coEvery { llmProvider.generateResponse(null, any(), any(), any(), any()) - } returns LlmResponse.Success( - "interaction_1", - listOf( - LlmResponsePart.ToolCall( - packageName = "com.example.calendar", - functionId = "create_event", - arguments = emptyMap(), - callId = "call_1", - ) + } returns + LlmResponse.Success( + "interaction_1", + listOf( + LlmResponsePart.ToolCall( + packageName = "com.example.calendar", + functionId = "create_event", + arguments = emptyMap(), + callId = "call_1", + ), + ), ) - ) val expectedErrorOutput = "Tool execution failed for create_event: Error: AppFunctionPermissionRequiredException - Calendar permission required" @@ -305,17 +306,18 @@ class AgentOrchestratorTest { coVerify { llmProvider.generateResponse( previousInteractionId = eq("interaction_1"), - input = eq( - LlmInput.ToolResponse( - listOf( - ToolOutput( - functionId = "create_event", - callId = "call_1", - result = expectedErrorOutput, - ) - ) - ) - ), + input = + eq( + LlmInput.ToolResponse( + listOf( + ToolOutput( + functionId = "create_event", + callId = "call_1", + result = expectedErrorOutput, + ), + ), + ), + ), tools = listOf(tool1), apiKey = "dummy_key", modelName = any(), diff --git a/agent/app/src/test/java/com/example/appfunctions/agent/domain/appfunction/AppFunctionExceptionFormatterTest.kt b/agent/app/src/test/java/com/example/appfunctions/agent/domain/appfunction/AppFunctionExceptionFormatterTest.kt index b60f919..ab4ef8a 100644 --- a/agent/app/src/test/java/com/example/appfunctions/agent/domain/appfunction/AppFunctionExceptionFormatterTest.kt +++ b/agent/app/src/test/java/com/example/appfunctions/agent/domain/appfunction/AppFunctionExceptionFormatterTest.kt @@ -71,7 +71,8 @@ class AppFunctionExceptionFormatterTest { @Test fun `getAppFunctionException extracts exception from cause chain`() { val appException = AppFunctionInvalidArgumentException("Invalid arg") - val wrappedException = RuntimeException("Wrapper", RuntimeException("Inner wrapper", appException)) + val wrappedException = + RuntimeException("Wrapper", RuntimeException("Inner wrapper", appException)) val extracted = AppFunctionExceptionFormatter.getAppFunctionException(wrappedException) assertEquals(appException, extracted) @@ -86,7 +87,9 @@ class AppFunctionExceptionFormatterTest { "com.example.chatapp.appfunctions.BaseChatAppFunctionService#send", ) assertEquals( - "Tool execution failed for com.example.chatapp.appfunctions.BaseChatAppFunctionService#send: Error: AppFunctionInvalidArgumentException - Message body cannot be empty", + "Tool execution failed for " + + "com.example.chatapp.appfunctions.BaseChatAppFunctionService#send: " + + "Error: AppFunctionInvalidArgumentException - Message body cannot be empty", formatted, ) } diff --git a/agent/app/src/test/java/com/example/appfunctions/agent/domain/appfunction/ExecuteAppFunctionUseCaseTest.kt b/agent/app/src/test/java/com/example/appfunctions/agent/domain/appfunction/ExecuteAppFunctionUseCaseTest.kt index 7d4db46..9c660ff 100644 --- a/agent/app/src/test/java/com/example/appfunctions/agent/domain/appfunction/ExecuteAppFunctionUseCaseTest.kt +++ b/agent/app/src/test/java/com/example/appfunctions/agent/domain/appfunction/ExecuteAppFunctionUseCaseTest.kt @@ -38,38 +38,39 @@ class ExecuteAppFunctionUseCaseTest { private val convertAppFunctionDataToJsonUseCase = mockk() private val useCase = ExecuteAppFunctionUseCase(appFunctionManager, convertAppFunctionDataToJsonUseCase) - @Test - fun `invoke returns Error with original AppFunctionException when response is Error`() = runTest { - val function = mockk(relaxed = true) - every { function.packageName } returns "com.example.app" - every { function.id } returns "test_function" - val parameters = mockk() + fun `invoke returns Error with original AppFunctionException when response is Error`() = + runTest { + val function = mockk(relaxed = true) + every { function.packageName } returns "com.example.app" + every { function.id } returns "test_function" + val parameters = mockk() - val appException = AppFunctionPermissionRequiredException("Permission needed") - coEvery { appFunctionManager.executeAppFunction(any()) } returns ExecuteAppFunctionResponse.Error(appException) + val appException = AppFunctionPermissionRequiredException("Permission needed") + coEvery { appFunctionManager.executeAppFunction(any()) } returns ExecuteAppFunctionResponse.Error(appException) - val result = useCase(function, parameters) + val result = useCase(function, parameters) - assertTrue(result is ExecuteAppFunctionResult.Error) - val errorResult = result as ExecuteAppFunctionResult.Error - assertEquals(appException, errorResult.exception) - } + assertTrue(result is ExecuteAppFunctionResult.Error) + val errorResult = result as ExecuteAppFunctionResult.Error + assertEquals(appException, errorResult.exception) + } @Test - fun `invoke returns Error when executeAppFunction throws exception`() = runTest { - val function = mockk(relaxed = true) - every { function.packageName } returns "com.example.app" - every { function.id } returns "test_function" - val parameters = mockk() + fun `invoke returns Error when executeAppFunction throws exception`() = + runTest { + val function = mockk(relaxed = true) + every { function.packageName } returns "com.example.app" + every { function.id } returns "test_function" + val parameters = mockk() - val runtimeException = RuntimeException("Unexpected crash") - coEvery { appFunctionManager.executeAppFunction(any()) } throws runtimeException + val runtimeException = RuntimeException("Unexpected crash") + coEvery { appFunctionManager.executeAppFunction(any()) } throws runtimeException - val result = useCase(function, parameters) + val result = useCase(function, parameters) - assertTrue(result is ExecuteAppFunctionResult.Error) - val errorResult = result as ExecuteAppFunctionResult.Error - assertEquals(runtimeException, errorResult.exception) - } + assertTrue(result is ExecuteAppFunctionResult.Error) + val errorResult = result as ExecuteAppFunctionResult.Error + assertEquals(runtimeException, errorResult.exception) + } } From 09281a72e3e2b32ea0249fd5448aac4cda092679 Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Tue, 14 Jul 2026 16:37:43 +0200 Subject: [PATCH 02/11] Add Github Actions workflows --- .github/workflows/build.yaml | 137 +++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 .github/workflows/build.yaml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..56ebb00 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,137 @@ +name: Build + +on: + push: + branches: + - main + pull_request: + paths: + - 'agent/**' + - 'ChatApp/**' + - '.github/workflows/**' + workflow_dispatch: + +concurrency: + group: build-${{ github.ref }} + cancel-in-progress: true + +jobs: + agent-build: + name: "Agent build & test" + runs-on: ubuntu-latest + timeout-minutes: 45 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Copy CI gradle.properties + run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: 21 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + build-root-directory: agent + + - name: Run spotless + run: ./gradlew spotlessCheck + working-directory: agent + + - name: Run unit tests + run: ./gradlew test -PGEMINI_API_KEY=dummy + working-directory: agent + + - name: Build Debug and Release APKs + run: ./gradlew assembleDebug assembleRelease -PGEMINI_API_KEY=dummy + working-directory: agent + + - name: Upload Agent APKs + uses: actions/upload-artifact@v4 + with: + name: agent-apks + path: agent/app/build/outputs/apk/**/*.apk + + - name: Enable KVM group perms + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Run instrumentation tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 36 + target: google_apis + arch: x86_64 + force-avd-creation: false + emulator-options: -no-window -gpu libswiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + working-directory: agent + script: ./gradlew connectedAndroidTest -PGEMINI_API_KEY=dummy + + chatapp-build: + name: "ChatApp build & test" + runs-on: ubuntu-latest + timeout-minutes: 45 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Copy CI gradle.properties + run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: 21 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + build-root-directory: ChatApp + + - name: Run spotless + run: ./gradlew spotlessCheck + working-directory: ChatApp + + - name: Run unit tests + run: ./gradlew test + working-directory: ChatApp + + - name: Build Debug and Release APKs + run: ./gradlew assembleDebug assembleRelease + working-directory: ChatApp + + - name: Upload ChatApp APKs + uses: actions/upload-artifact@v4 + with: + name: chatapp-apks + path: | + ChatApp/app/build/outputs/apk/**/*.apk + ChatApp/wear/build/outputs/apk/**/*.apk + + - name: Enable KVM group perms + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Run instrumentation tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 35 + target: google_apis + arch: x86_64 + force-avd-creation: false + emulator-options: -no-window -gpu libswiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + working-directory: ChatApp + script: ./gradlew connectedAndroidTest From dd919f4c735c7d519d502b5eb7c61cd59056f7c4 Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Tue, 14 Jul 2026 17:14:42 +0200 Subject: [PATCH 03/11] Unignore editorconfig --- .gitignore | 2 ++ ChatApp/.editorconfig | 5 +++++ agent/.editorconfig | 5 +++++ 3 files changed, 12 insertions(+) create mode 100644 ChatApp/.editorconfig create mode 100644 agent/.editorconfig diff --git a/.gitignore b/.gitignore index 54dbdc5..edff31d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ build/ app/build gradle/gradle-daemon-jvm.properties local.properties +!.editorconfig + diff --git a/ChatApp/.editorconfig b/ChatApp/.editorconfig new file mode 100644 index 0000000..9416c63 --- /dev/null +++ b/ChatApp/.editorconfig @@ -0,0 +1,5 @@ +root = true + +[*.{kt,kts}] +ktlint_code_style = ktlint_official +ktlint_function_naming_ignore_when_annotated_with = Composable diff --git a/agent/.editorconfig b/agent/.editorconfig new file mode 100644 index 0000000..7fb7a60 --- /dev/null +++ b/agent/.editorconfig @@ -0,0 +1,5 @@ +root = true + +[*.{kt,kts}] +ktlint_code_style = ktlint_official +ktlint_function_naming_ignore_when_annotated_with = Composable \ No newline at end of file From 96a094b18d250b017279e455c10db55590fa71c7 Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Tue, 14 Jul 2026 17:33:55 +0200 Subject: [PATCH 04/11] dismiss emulator keyguard --- .github/workflows/build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 56ebb00..8c6aa03 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -73,7 +73,7 @@ jobs: emulator-options: -no-window -gpu libswiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true working-directory: agent - script: ./gradlew connectedAndroidTest -PGEMINI_API_KEY=dummy + script: adb shell wm dismiss-keyguard && ./gradlew :app:connectedAndroidTest -PGEMINI_API_KEY=dummy chatapp-build: name: "ChatApp build & test" @@ -134,4 +134,4 @@ jobs: emulator-options: -no-window -gpu libswiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true working-directory: ChatApp - script: ./gradlew connectedAndroidTest + script: adb shell wm dismiss-keyguard && ./gradlew :app:connectedAndroidTest From ad0234c99c3551f498d1a7d9bc086f3c5d0fb2d9 Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Wed, 15 Jul 2026 14:49:30 +0200 Subject: [PATCH 05/11] Update emulator version to 37.1 --- .github/workflows/build.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 8c6aa03..e82d343 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -44,11 +44,11 @@ jobs: working-directory: agent - name: Run unit tests - run: ./gradlew test -PGEMINI_API_KEY=dummy + run: ./gradlew test -PGEMINI_API_KEY=dummy_key working-directory: agent - name: Build Debug and Release APKs - run: ./gradlew assembleDebug assembleRelease -PGEMINI_API_KEY=dummy + run: ./gradlew assembleDebug assembleRelease -PGEMINI_API_KEY=dummy_key working-directory: agent - name: Upload Agent APKs @@ -66,14 +66,14 @@ jobs: - name: Run instrumentation tests uses: reactivecircus/android-emulator-runner@v2 with: - api-level: 36 - target: google_apis + api-level: 37.1 + target: google_apis_ps16k arch: x86_64 force-avd-creation: false emulator-options: -no-window -gpu libswiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true working-directory: agent - script: adb shell wm dismiss-keyguard && ./gradlew :app:connectedAndroidTest -PGEMINI_API_KEY=dummy + script: adb shell wm dismiss-keyguard && ./gradlew :app:connectedAndroidTest -PGEMINI_API_KEY=dummy_key chatapp-build: name: "ChatApp build & test" @@ -127,8 +127,8 @@ jobs: - name: Run instrumentation tests uses: reactivecircus/android-emulator-runner@v2 with: - api-level: 35 - target: google_apis + api-level: 37.1 + target: google_apis_ps16k arch: x86_64 force-avd-creation: false emulator-options: -no-window -gpu libswiftshader_indirect -noaudio -no-boot-anim -camera-back none From 917cd1fc3adc3f5ee55f14ff42a9e8e10a6b9ba7 Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Fri, 17 Jul 2026 13:20:38 +0200 Subject: [PATCH 06/11] Set emulator disk and heap size --- .github/workflows/build.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e82d343..f81215e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -71,6 +71,8 @@ jobs: arch: x86_64 force-avd-creation: false emulator-options: -no-window -gpu libswiftshader_indirect -noaudio -no-boot-anim -camera-back none + disk-size: 6000M + heap-size: 600M disable-animations: true working-directory: agent script: adb shell wm dismiss-keyguard && ./gradlew :app:connectedAndroidTest -PGEMINI_API_KEY=dummy_key @@ -133,5 +135,7 @@ jobs: force-avd-creation: false emulator-options: -no-window -gpu libswiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true + disk-size: 6000M + heap-size: 600M working-directory: ChatApp script: adb shell wm dismiss-keyguard && ./gradlew :app:connectedAndroidTest From df18641bb80046d000d2200aa40909fd85cac40b Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Fri, 17 Jul 2026 13:32:35 +0200 Subject: [PATCH 07/11] Run on macos-latest --- .github/workflows/build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f81215e..2c092ac 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -18,7 +18,7 @@ concurrency: jobs: agent-build: name: "Agent build & test" - runs-on: ubuntu-latest + runs-on: macos-latest timeout-minutes: 45 steps: @@ -79,7 +79,7 @@ jobs: chatapp-build: name: "ChatApp build & test" - runs-on: ubuntu-latest + runs-on: macos-latest timeout-minutes: 45 steps: From 44563117886d15434f19a0a1f105180bb856fd4f Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Fri, 17 Jul 2026 14:46:30 +0200 Subject: [PATCH 08/11] remove KVM --- .github/workflows/build.yaml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2c092ac..bed36f6 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -57,12 +57,6 @@ jobs: name: agent-apks path: agent/app/build/outputs/apk/**/*.apk - - name: Enable KVM group perms - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - name: Run instrumentation tests uses: reactivecircus/android-emulator-runner@v2 with: @@ -120,12 +114,6 @@ jobs: ChatApp/app/build/outputs/apk/**/*.apk ChatApp/wear/build/outputs/apk/**/*.apk - - name: Enable KVM group perms - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - name: Run instrumentation tests uses: reactivecircus/android-emulator-runner@v2 with: From 12bbc4ce9254d6bac015e38e618f46a7a7c806fb Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Fri, 17 Jul 2026 15:14:53 +0200 Subject: [PATCH 09/11] remove unnecessary tools, update emulator options --- .github/workflows/build.yaml | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index bed36f6..d63f183 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -57,14 +57,25 @@ jobs: name: agent-apks path: agent/app/build/outputs/apk/**/*.apk + - name: Delete unnecessary tools + uses: jlumbroso/free-disk-space@v1.3.1 + with: + android: false # Don't remove Android tools + tool-cache: true # Remove image tool cache - rm -rf "$AGENT_TOOLSDIRECTORY" + dotnet: true # rm -rf /usr/share/dotnet + haskell: true # rm -rf /opt/ghc... + swap-storage: true # rm -f /mnt/swapfile (4GiB) + docker-images: false # Takes 16s, enable if needed in the future + large-packages: false # includes google-cloud-sdk and it's slow + - name: Run instrumentation tests uses: reactivecircus/android-emulator-runner@v2 with: api-level: 37.1 target: google_apis_ps16k arch: x86_64 - force-avd-creation: false - emulator-options: -no-window -gpu libswiftshader_indirect -noaudio -no-boot-anim -camera-back none + force-avd-creation: true + emulator-options: -no-snapshot-load -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disk-size: 6000M heap-size: 600M disable-animations: true @@ -114,14 +125,25 @@ jobs: ChatApp/app/build/outputs/apk/**/*.apk ChatApp/wear/build/outputs/apk/**/*.apk + - name: Delete unnecessary tools + uses: jlumbroso/free-disk-space@v1.3.1 + with: + android: false # Don't remove Android tools + tool-cache: true # Remove image tool cache - rm -rf "$AGENT_TOOLSDIRECTORY" + dotnet: true # rm -rf /usr/share/dotnet + haskell: true # rm -rf /opt/ghc... + swap-storage: true # rm -f /mnt/swapfile (4GiB) + docker-images: false # Takes 16s, enable if needed in the future + large-packages: false # includes google-cloud-sdk and it's slow + - name: Run instrumentation tests uses: reactivecircus/android-emulator-runner@v2 with: api-level: 37.1 target: google_apis_ps16k arch: x86_64 - force-avd-creation: false - emulator-options: -no-window -gpu libswiftshader_indirect -noaudio -no-boot-anim -camera-back none + force-avd-creation: true + emulator-options: -no-snapshot-load -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true disk-size: 6000M heap-size: 600M From b0889442ecd38a17d5e7fbf9f5607f929c1c8dc5 Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Fri, 17 Jul 2026 15:27:27 +0200 Subject: [PATCH 10/11] x86 -> arm; partition size 4096 --- .github/workflows/build.yaml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d63f183..55d4233 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -73,10 +73,9 @@ jobs: with: api-level: 37.1 target: google_apis_ps16k - arch: x86_64 + arch: arm64-v8a force-avd-creation: true - emulator-options: -no-snapshot-load -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disk-size: 6000M + emulator-options: -no-snapshot-load -no-snapshot-save -no-window -gpu host -noaudio -no-boot-anim -camera-back none -partition-size 4096 heap-size: 600M disable-animations: true working-directory: agent @@ -141,11 +140,10 @@ jobs: with: api-level: 37.1 target: google_apis_ps16k - arch: x86_64 + arch: arm64-v8a force-avd-creation: true - emulator-options: -no-snapshot-load -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + emulator-options: -no-snapshot-load -no-snapshot-save -no-window -gpu host -noaudio -no-boot-anim -camera-back none -partition-size 4096 disable-animations: true - disk-size: 6000M heap-size: 600M working-directory: ChatApp script: adb shell wm dismiss-keyguard && ./gradlew :app:connectedAndroidTest From 7e827be9a1172d2939b557245bf7a965a7054df9 Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Fri, 17 Jul 2026 15:38:33 +0200 Subject: [PATCH 11/11] undo deleting of tools, adjust boot timeout --- .github/ci-gradle.properties | 1 + .github/workflows/build.yaml | 30 +++++++----------------------- 2 files changed, 8 insertions(+), 23 deletions(-) diff --git a/.github/ci-gradle.properties b/.github/ci-gradle.properties index a983460..70c29a6 100644 --- a/.github/ci-gradle.properties +++ b/.github/ci-gradle.properties @@ -19,6 +19,7 @@ org.gradle.parallel=true org.gradle.workers.max=2 org.gradle.configuration-cache=true org.gradle.configuration-cache.parallel=true +org.gradle.jvmargs="-Xmx2g" kotlin.incremental=false diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 55d4233..a18df95 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -57,23 +57,15 @@ jobs: name: agent-apks path: agent/app/build/outputs/apk/**/*.apk - - name: Delete unnecessary tools - uses: jlumbroso/free-disk-space@v1.3.1 - with: - android: false # Don't remove Android tools - tool-cache: true # Remove image tool cache - rm -rf "$AGENT_TOOLSDIRECTORY" - dotnet: true # rm -rf /usr/share/dotnet - haskell: true # rm -rf /opt/ghc... - swap-storage: true # rm -f /mnt/swapfile (4GiB) - docker-images: false # Takes 16s, enable if needed in the future - large-packages: false # includes google-cloud-sdk and it's slow - - name: Run instrumentation tests uses: reactivecircus/android-emulator-runner@v2 + env: + ANDROID_EMULATOR_WAIT_TIME_BEFORE_KILL: 180 with: api-level: 37.1 target: google_apis_ps16k arch: arm64-v8a + emulator-boot-timeout: 900 force-avd-creation: true emulator-options: -no-snapshot-load -no-snapshot-save -no-window -gpu host -noaudio -no-boot-anim -camera-back none -partition-size 4096 heap-size: 600M @@ -124,26 +116,18 @@ jobs: ChatApp/app/build/outputs/apk/**/*.apk ChatApp/wear/build/outputs/apk/**/*.apk - - name: Delete unnecessary tools - uses: jlumbroso/free-disk-space@v1.3.1 - with: - android: false # Don't remove Android tools - tool-cache: true # Remove image tool cache - rm -rf "$AGENT_TOOLSDIRECTORY" - dotnet: true # rm -rf /usr/share/dotnet - haskell: true # rm -rf /opt/ghc... - swap-storage: true # rm -f /mnt/swapfile (4GiB) - docker-images: false # Takes 16s, enable if needed in the future - large-packages: false # includes google-cloud-sdk and it's slow - - name: Run instrumentation tests uses: reactivecircus/android-emulator-runner@v2 + env: + ANDROID_EMULATOR_WAIT_TIME_BEFORE_KILL: 180 with: api-level: 37.1 target: google_apis_ps16k arch: arm64-v8a + emulator-boot-timeout: 900 force-avd-creation: true emulator-options: -no-snapshot-load -no-snapshot-save -no-window -gpu host -noaudio -no-boot-anim -camera-back none -partition-size 4096 - disable-animations: true heap-size: 600M + disable-animations: true working-directory: ChatApp script: adb shell wm dismiss-keyguard && ./gradlew :app:connectedAndroidTest