Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,9 @@ class AppFunctionInstrumentationTest {
runBlocking<Unit> {
val sendMessageFunctionMetadata =
appFunctionManager
.observeAppFunctions(
.searchAppFunctions(
AppFunctionSearchSpec(packageNames = setOf(context.packageName)),
)
.first()
.flatMap { it.appFunctions }
.single { it.id == ChatAppFunctionService.FUNCTION_ID_SEND_MESSAGE }
val testRecipient = recipientsRepository.getAllRecipients().first()
val request =
Expand Down Expand Up @@ -103,11 +101,9 @@ class AppFunctionInstrumentationTest {
runBlocking<Unit> {
val sendMessageFunctionMetadata =
appFunctionManager
.observeAppFunctions(
.searchAppFunctions(
AppFunctionSearchSpec(packageNames = setOf(context.packageName)),
)
.first()
.flatMap { it.appFunctions }
.single { it.id == ChatAppFunctionService.FUNCTION_ID_SEND_MESSAGE }
val testRecipient = recipientsRepository.getAllRecipients().first()
val request =
Expand All @@ -134,11 +130,9 @@ class AppFunctionInstrumentationTest {
runBlocking<Unit> {
val getRecipientsFunctionMetadata =
appFunctionManager
.observeAppFunctions(
.searchAppFunctions(
AppFunctionSearchSpec(packageNames = setOf(context.packageName)),
)
.first()
.flatMap { it.appFunctions }
.single { it.id == ChatAppFunctionService.FUNCTION_ID_SEARCH_CONTACTS }

val request =
Expand Down
2 changes: 1 addition & 1 deletion ChatApp/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[versions]
agp = "9.3.1"
appfunctions = "1.0.0-alpha10"
appfunctions = "1.0.0-beta01"
kotlin = "2.4.10"
coreKtx = "1.19.0"
junit = "4.13.2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject
Expand Down Expand Up @@ -66,30 +67,40 @@ class AppFunctionsViewModel
private val appFunctionManager = AppFunctionManager.getInstance(application)

val uiState: StateFlow<AppFunctionsUiState> =
appFunctionManager?.observeAppFunctions(
AppFunctionSearchSpec(packageNames = setOf(application.packageName)),
)?.map { packageMetadataList ->
val functions =
packageMetadataList.firstOrNull {
it.packageName == application.packageName
}?.appFunctions ?: emptyList()
appFunctionManager
?.observeAppFunctions()
?.map { loadAppFunctions(application.packageName) }
?.onStart { emit(loadAppFunctions(application.packageName)) }
?.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = AppFunctionsUiState(),
)
?: MutableStateFlow(AppFunctionsUiState())

private suspend fun loadAppFunctions(packageName: String): AppFunctionsUiState {
if (appFunctionManager == null) return AppFunctionsUiState()
val functions =
appFunctionManager.searchAppFunctions(
AppFunctionSearchSpec(packageNames = setOf(packageName)),
)
val statesByName =
appFunctionManager
.getAppFunctionStates(functions.map { it.name })
.associateBy { it.functionName }

val functionInfos =
functions.map {
val functionInfos =
functions
.map {
AppFunctionInfo(
id = it.id,
isEnabled = it.isEnabled,
isEnabled = statesByName[it.name]?.isEnabled ?: true,
description = it.description,
)
}.sortedBy { it.id }

AppFunctionsUiState(functions = functionInfos)
}?.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = AppFunctionsUiState(),
)
?: MutableStateFlow(AppFunctionsUiState())
return AppFunctionsUiState(functions = functionInfos)
}

/**
* Toggles the enabled state of an app function.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ 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.AppFunctionDeclaration
import androidx.appfunctions.AppFunctionElementNotFoundException
import androidx.appfunctions.AppFunctionInvalidArgumentException
import androidx.appfunctions.AppFunctionService
import androidx.appfunctions.AppFunctionServiceEntryPoint
import androidx.appfunctions.AppFunctionStringValueConstraint
import androidx.appfunctions.AppFunctionTextResource
import androidx.appfunctions.ExperimentalAppFunctionsApi
import com.example.chatapp.data.CallManager
import com.example.chatapp.data.DisplayMessage
import com.example.chatapp.data.MessageRepository
Expand Down Expand Up @@ -66,7 +67,7 @@ abstract class BaseChatAppFunctionService : AppFunctionService() {
* @return List of [ContactSearchResult] objects matching the query.
* @throws AppFunctionInvalidArgumentException If query is empty or blank, an invalid contactType is provided, or if no matching contact or group is found. If thrown, suggest the user clarify the recipient or group name.
*/
@AppFunction(isDescribedByKDoc = true)
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun searchContacts(
query: String,
@AppFunctionStringValueConstraint(enumValues = ["INDIVIDUAL", "GROUP", "ANY"])
Expand Down Expand Up @@ -119,7 +120,8 @@ abstract class BaseChatAppFunctionService : AppFunctionService() {
* @throws AppFunctionInvalidArgumentException If messageBody is empty or blank. If thrown, ask the user to provide the message content to send.
* @throws AppFunctionElementNotFoundException If no contact or group matches endpointValue. If thrown, call "searchContacts" to find the correct ID.
*/
@AppFunction(isDescribedByKDoc = true)
@OptIn(ExperimentalAppFunctionsApi::class)
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun fetchSendMessageDetails(
endpointValue: String,
messageBody: String,
Expand Down Expand Up @@ -200,7 +202,7 @@ abstract class BaseChatAppFunctionService : AppFunctionService() {
* @throws AppFunctionElementNotFoundException If no contact or group matches endpointValue. If thrown, call "searchContacts" to find the correct ID.
* @throws AppFunctionAppUnknownException If sending fails due to a repository error. If thrown, suggest the user retry later.
*/
@AppFunction(isDescribedByKDoc = true)
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun sendMessage(
endpointValue: String,
messageBody: String,
Expand Down Expand Up @@ -239,7 +241,7 @@ abstract class BaseChatAppFunctionService : AppFunctionService() {
* @return A [PendingIntent] that launches the call activity in the application UI.
* @throws AppFunctionElementNotFoundException If no recipient exists for endpointValue. If thrown, call "searchContacts" to find the correct ID.
*/
@AppFunction(isDescribedByKDoc = true)
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun makeCall(endpointValue: String): PendingIntent {
val recipient =
recipientsRepository.getRecipientById(endpointValue)
Expand All @@ -264,7 +266,7 @@ abstract class BaseChatAppFunctionService : AppFunctionService() {
* @param chatId The unique identifier for the recipient or chat group.
* @param wallpaperUri The URI of the image file to set as the chat wallpaper.
*/
@AppFunction(isDescribedByKDoc = true)
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun updateChatWallpaper(
chatId: String,
wallpaperUri: Uri,
Expand Down Expand Up @@ -293,7 +295,7 @@ abstract class BaseChatAppFunctionService : AppFunctionService() {
* @return List of [MessagesSearchResult] matching the query.
* @throws AppFunctionInvalidArgumentException If the query is blank.
*/
@AppFunction(isDescribedByKDoc = true)
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun searchMessages(
query: String,
endpointValue: String? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.example.chatapp.appfunctions
import androidx.appfunctions.AppFunctionResourceContainer
import androidx.appfunctions.AppFunctionSerializable
import androidx.appfunctions.AppFunctionTextResource
import androidx.appfunctions.ExperimentalAppFunctionsApi

/**
* Represents a result from a contact or group search.
Expand All @@ -37,6 +38,7 @@ data class ContactSearchResult(
/**
* Confirmation-card content for Gemini.
*/
@OptIn(ExperimentalAppFunctionsApi::class)
@AppFunctionSerializable(isDescribedByKDoc = true)
data class ConfirmationPreview(
/** Exactly one A2UI resource (mimeType "application/a2ui+json"). */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ package com.example.appfunctions.agent.domain

import android.content.Context
import androidx.appfunctions.AppFunctionManager
import androidx.appfunctions.metadata.AppFunctionName
import androidx.appfunctions.AppFunctionName
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/
package com.example.appfunctions.agent.data

import androidx.appfunctions.AppFunction
import androidx.appfunctions.AppFunctionDeclaration
import androidx.appfunctions.AppFunctionSerializable
import androidx.appfunctions.AppFunctionService
import androidx.appfunctions.AppFunctionServiceEntryPoint
Expand All @@ -34,7 +34,7 @@ abstract class AbstractFakeAppFunctionService : AppFunctionService() {
* @param params The test parameters containing input data.
* @return The test response containing output data.
*/
@AppFunction(isDescribedByKDoc = true)
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun fakeFunction(params: FakeParams): FakeResponse {
return FakeResponse("success")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ import android.location.Geocoder
import android.location.LocationManager
import android.util.Base64
import androidx.annotation.RequiresApi
import androidx.appfunctions.AppFunctionName
import androidx.appfunctions.AppFunctionState
import androidx.appfunctions.metadata.AppFunctionComponentsMetadata
import androidx.appfunctions.metadata.AppFunctionDoubleTypeMetadata
import androidx.appfunctions.metadata.AppFunctionMetadata
import androidx.appfunctions.metadata.AppFunctionName
import androidx.appfunctions.metadata.AppFunctionObjectTypeMetadata
import androidx.appfunctions.metadata.AppFunctionPackageMetadata
import androidx.appfunctions.metadata.AppFunctionParameterMetadata
Expand Down Expand Up @@ -390,7 +390,6 @@ class AgentInternalTools
packageMetadata =
AppFunctionPackageMetadata(
packageName = INTERNAL_TOOL_PACKAGE,
appFunctions = listOf(),
components = AppFunctionComponentsMetadata(),
),
)
Expand Down Expand Up @@ -418,7 +417,6 @@ class AgentInternalTools
packageMetadata =
AppFunctionPackageMetadata(
packageName = INTERNAL_TOOL_PACKAGE,
appFunctions = listOf(),
components = AppFunctionComponentsMetadata(),
),
)
Expand Down Expand Up @@ -452,7 +450,6 @@ class AgentInternalTools
packageMetadata =
AppFunctionPackageMetadata(
packageName = INTERNAL_TOOL_PACKAGE,
appFunctions = listOf(),
components = AppFunctionComponentsMetadata(),
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
package com.example.appfunctions.agent.domain.appfunction

import androidx.appfunctions.AppFunctionManager
import androidx.appfunctions.AppFunctionName
import androidx.appfunctions.AppFunctionState
import androidx.appfunctions.metadata.AppFunctionName
import javax.inject.Inject

/** Use case to get [AppFunctionState] with given list of [AppFunctionName]. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
*/
package com.example.appfunctions.agent.ui.screens.debugging

import androidx.appfunctions.AppFunctionName
import androidx.appfunctions.metadata.AppFunctionComponentsMetadata
import androidx.appfunctions.metadata.AppFunctionDataTypeMetadata
import androidx.appfunctions.metadata.AppFunctionMetadata
import androidx.appfunctions.metadata.AppFunctionName
import androidx.appfunctions.metadata.AppFunctionPackageMetadata
import androidx.appfunctions.metadata.AppFunctionParameterMetadata
import androidx.appfunctions.metadata.AppFunctionResponseMetadata
Expand Down Expand Up @@ -328,7 +328,7 @@ fun AppFunctionItemPreview() {
packageMetadata =
AppFunctionPackageMetadata(
packageName = "com.example.test",
appFunctions = listOf(),
components = AppFunctionComponentsMetadata(),
),
)

Expand Down Expand Up @@ -365,7 +365,7 @@ fun AppFunctionItem_NoParams_Preview() {
packageMetadata =
AppFunctionPackageMetadata(
packageName = "com.example.test",
appFunctions = listOf(),
components = AppFunctionComponentsMetadata(),
),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
*/
package com.example.appfunctions.agent.ui.screens.debugging

import androidx.appfunctions.AppFunctionName
import androidx.appfunctions.metadata.AppFunctionMetadata
import androidx.appfunctions.metadata.AppFunctionName
import com.example.appfunctions.agent.domain.appfunction.AppInfo
import com.example.appfunctions.agent.domain.appfunction.ExecuteAppFunctionResult
import com.example.appfunctions.agent.domain.troubleshoot.TroubleshootReport
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ package com.example.appfunctions.agent.ui.screens.debugging
import android.app.PendingIntent
import android.content.res.Resources
import android.util.Log
import androidx.appfunctions.AppFunctionName
import androidx.appfunctions.AppFunctionState
import androidx.appfunctions.metadata.AppFunctionMetadata
import androidx.appfunctions.metadata.AppFunctionName
import androidx.appfunctions.metadata.AppFunctionPackageMetadata
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ package com.example.appfunctions.agent.ui.screens.debugging

import android.app.PendingIntent
import android.widget.Toast
import androidx.appfunctions.AppFunctionName
import androidx.appfunctions.metadata.AppFunctionMetadata
import androidx.appfunctions.metadata.AppFunctionName
import androidx.appfunctions.metadata.AppFunctionPackageMetadata
import androidx.appfunctions.metadata.AppFunctionResponseMetadata
import androidx.appfunctions.metadata.AppFunctionStringTypeMetadata
Expand Down Expand Up @@ -320,7 +320,6 @@ fun AppFunctionErrorItemPreview() {
packageMetadata =
AppFunctionPackageMetadata(
packageName = "com.example.test",
appFunctions = listOf(),
components = androidx.appfunctions.metadata.AppFunctionComponentsMetadata(),
),
)
Expand Down Expand Up @@ -351,7 +350,6 @@ fun FunctionsFoundContentPreview() {
packageMetadata =
AppFunctionPackageMetadata(
packageName = "com.example.test",
appFunctions = listOf(),
components = androidx.appfunctions.metadata.AppFunctionComponentsMetadata(),
),
)
Expand All @@ -368,7 +366,6 @@ fun FunctionsFoundContentPreview() {
packageMetadata =
AppFunctionPackageMetadata(
packageName = "com.example.test",
appFunctions = listOf(),
components = androidx.appfunctions.metadata.AppFunctionComponentsMetadata(),
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ package com.example.appfunctions.agent.domain

import android.content.Intent
import androidx.appfunctions.AppFunctionData
import androidx.appfunctions.AppFunctionName
import androidx.appfunctions.AppFunctionState
import androidx.appfunctions.metadata.AppFunctionMetadata
import androidx.appfunctions.metadata.AppFunctionName
import androidx.appfunctions.metadata.AppFunctionPackageMetadata
import com.example.appfunctions.agent.data.AgentInternalTools
import com.example.appfunctions.agent.data.LlmModel
Expand Down Expand Up @@ -377,13 +377,11 @@ class AgentOrchestratorTest {
private fun createMockTool(
packageName: String,
id: String,
isEnabled: Boolean = true,
): AppFunctionMetadata {
val tool = mockk<AppFunctionMetadata>(relaxed = true)
every { tool.packageName } returns packageName
every { tool.id } returns id
every { tool.name } returns AppFunctionName(packageName, id)
every { tool.isEnabled } returns isEnabled
return tool
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import androidx.appfunctions.AppFunctionName
import androidx.appfunctions.AppFunctionState
import androidx.appfunctions.metadata.AppFunctionMetadata
import androidx.appfunctions.metadata.AppFunctionName
import androidx.appfunctions.metadata.AppFunctionPackageMetadata
import androidx.test.core.app.ApplicationProvider
import com.example.appfunctions.agent.R
Expand Down
Loading
Loading