Adicionar a API AppFunctions ao seu app

Este guia explica como integrar a API AppFunctions ao seu app Android, implementar a lógica de uma função e verificar se a integração funciona corretamente.

Compatibilidade de versões

Essa implementação exige que o compileSdk do projeto seja definido como o nível 37 ou mais recente da API. Seu projeto precisa usar o Plug-in do Android para Gradle 9.1.0 ou mais recente.

Seu app não precisa verificar se as AppFunctions são compatíveis. Isso é processado automaticamente na biblioteca AppFunctions do Jetpack. AppFunctionManager vai retornar uma instância se o recurso for compatível e nulo se não for.

Dependências

Adicione as dependências de biblioteca necessárias ao arquivo build.gradle.kts (ou build.gradle) do módulo e configure o plug-in do KSP no módulo do app de nível superior, como mostrado abaixo:

dependencies {
  implementation("androidx.appfunctions:appfunctions:1.0.0-beta01")
  // If this project uses any Kotlin source, use Kotlin Symbol Processing (KSP)
  // See Add the KSP plugin to your project
  ksp("androidx.appfunctions:appfunctions-compiler:1.0.0-beta01")
}

Implementar a lógica de AppFunctions

Para implementar uma AppFunction no seu app Android, crie uma classe que implemente a lógica específica das AppFunctions. Isso envolve a criação de classes de dados serializáveis para parâmetros e respostas e, em seguida, o fornecimento da lógica principal no método da função.

O código a seguir mostra um exemplo de implementação para criar uma tarefa no app TODO, incluindo a definição de parâmetros personalizados e tipos de resposta, além da lógica da função principal usando um repositório.

@RequiresApi(36)
@AndroidEntryPoint
@AppFunctionServiceEntryPoint(
    serviceName = "TaskAppFunctionService",
    appFunctionXmlFileName = "task_app_function_service",
)
abstract class BaseTaskAppFunctionService : AppFunctionService() {
    @Inject internal lateinit var taskRepository: TaskRepository

    /**
     * Creates a task based on [createTaskParams].
     *
     * @param createTaskParams The parameter to describe how to create the task.
     */
    @AppFunctionDeclaration(isDescribedByKDoc = true)
    suspend fun createTask(
        createTaskParams: CreateTaskParams,
    ): Task = withContext(Dispatchers.IO) {
        // Developers can use predefined exceptions to let the agent know
        // why it failed.
        if (createTaskParams.title == null && createTaskParams.content == null) {
            throw AppFunctionInvalidArgumentException("Title or content should be non-null")
        }

        val id = taskRepository.createTask(
            createTaskParams.title,
            createTaskParams.content
        )

        return@withContext taskRepository
            .getTask(id)
            ?.toTask()
            ?: throw AppFunctionElementNotFoundException("Task not found for ID = $id")
    }

    // Maps internal TaskEntity
    private fun TaskEntity.toTask() = Task(id = id, title = title, content = description)
}

Pontos principais sobre o código

  • Por padrão, uma implementação de AppFunction é executada na linha de execução de interface do Android. Portanto, uma operação de longa duração precisa fazer o seguinte:
    • Declare a AppFunction como uma função de suspensão.
    • Mude para um agente de corrotina adequado quando a operação puder bloquear a linha de execução.
  • Quando isDescribedByKDoc é definido como true, a descrição da função ou a descrição serializável é codificada como parte do AppFunctionMetadata para ajudar o agente a entender como usar a AppFunction do app. A partir da versão 1.0.0-beta01, isDescribedByKDoc não tem valor padrão, então defina-o explicitamente.

Declarar o serviço AppFunction no manifesto

Registre a declaração de serviço gerada pelo KSP e a propriedade app_metadata no manifesto do módulo, por exemplo, em src/main/AndroidManifest.xml. O compilador do KSP gera a classe de serviço concreta (TaskAppFunctionService) que estende a classe de ponto de entrada abstrata, além do esquema XML correspondente no diretório assets/.

<service
    android:name="com.example.snippets.ai.TaskAppFunctionService"
    android:permission="android.permission.BIND_APP_FUNCTION_SERVICE"
    android:exported="true"
    tools:targetApi="36">
    <property
        android:name="android.app.appfunctions.schema"
        android:value="app_functions_schema.xsd" />
    <property
        android:name="android.app.appfunctions.v2"
        android:value="task_app_function_service.xml" />
    <intent-filter>
        <action android:name="android.app.appfunctions.AppFunctionService" />
    </intent-filter>
</service>
<property
    android:name="android.app.appfunctions.app_metadata"
    android:resource="@xml/app_metadata" />

Opcional: alternar a disponibilidade da AppFunction no momento da execução

Use a API AppFunctionManager para ativar ou desativar explicitamente as funções ao restringir as AppFunctions. A restrição pode ser útil quando determinados recursos do app não estão disponíveis para todos os usuários. Ao ativar ou desativar dinamicamente as AppFunctions, o sistema de inteligência sabe exatamente quais recursos estão disponíveis para o usuário em um determinado momento.

Para restringir com segurança as AppFunctions que exigem um estado de conta específico, siga um processo de duas etapas:

Etapa 1. Desativar a função por padrão

Para evitar que a função seja acessível antes que a flag de recurso seja verificada, defina o parâmetro isEnabled da anotação @AppFunctionDeclaration como false.

@AppFunctionDeclaration(isEnabled = false, isDescribedByKDoc = true)
suspend fun createTask(
    createTaskParams: CreateTaskParams,
): Task = TODO()

Etapa 2: Ativar a função dinamicamente no ambiente de execução

Para cada classe @AppFunctionServiceEntryPoint, o compilador gera uma classe correspondente que contém constantes de ID de função (usando um sufixo Ids). É possível usar essas constantes de ID geradas com o método setAppFunctionEnabled de AppFunctionManager para mudar o estado ativado de uma função no momento da execução.

suspend fun onFeatureEnabled(context: Context) {
    try {
        AppFunctionManager.getInstance(context)
            ?.setAppFunctionEnabled(
                BaseTaskAppFunctionServiceIds.CREATE_TASK_ID,
                AppFunctionManager.APP_FUNCTION_STATE_ENABLED,
            )
    } catch (e: Exception) {
        // Handle exception: AppFunctions indexation may not be fully completed
        // upon initial app startup.
    }
}

suspend fun onFeatureDisabled(context: Context) {
    try {
        AppFunctionManager.getInstance(context)
            ?.setAppFunctionEnabled(
                BaseTaskAppFunctionServiceIds.CREATE_TASK_ID,
                AppFunctionManager.APP_FUNCTION_STATE_DISABLED,
            )
    } catch (e: Exception) {
        // Handle exception
    }
}

Considerações sobre os tipos de funcionalidades a serem disponibilizadas

A segurança é sempre fundamental. Ao escolher quais recursos do seu app serão disponibilizados como AppFunctions, é importante lembrar que os agentes do sistema podem processar consultas do usuário no servidor para aproveitar os recursos avançados de LLM.

Para oferecer uma ótima experiência do usuário que também evite expor informações sensíveis, recomendamos seguir estas diretrizes:

  • Funcionalidade que se beneficia da linguagem natural: disponibilize tarefas que sejam mais fáceis de expressar em uma conversa do que pela navegação manual na interface.
  • Acesso restrito: crie AppFunctions que dão ao agente acesso apenas aos dados e às ações necessárias para atender à solicitação específica do usuário.
  • Informações não sensíveis: compartilhe apenas dados que não sejam altamente pessoais ou confidenciais ou dados que o usuário consente explicitamente em compartilhar no contexto da ação.
  • Confirmação inequívoca para qualquer ação destrutiva: tenha muito cuidado com funções que realizam ações destrutivas, como excluir dados. Embora o agente possa invocá-las, seu app precisa incluir uma etapa de confirmação própria e usar uma linguagem clara e inequívoca sobre as intenções. Também é útil adicionar mais de uma etapa de confirmação para garantir que o usuário esteja ciente do que está sendo solicitado.

Verificar a integração da AppFunction

Para verificar se você integrou as AppFunctions corretamente, use adb shell cmd app_function.

Use adb shell cmd app_function list-app-functions --package $myPackageName para conferir detalhes das AppFunctions que seu app oferece.

Também é possível executar uma AppFunction diretamente na linha de comando usando o identificador explícito ("$enclosingClassName#$methodName"):

adb shell "cmd app_function execute-app-function \
  --package com.example.android.appfunctions \
  --function 'com.example.android.appfunctions.BaseTaskAppFunctionService#createTask' \
  --parameters '{\"createTaskParams\": {\"title\": \"Buy milk\", \"content\": \"From grocery store\"}}'"

Para testar a MCP do Android em ação e verificar fluxos de trabalho completos sem precisar de comandos, instale e execute o app Android Agente de teste de AppFunctions no seu dispositivo.

Se você estiver verificando sua integração usando assistentes baseados em chat, como o Gemini no Android Studio, use a habilidade de desenvolvimento do AppFunctions ou forneça um comando como este:

Execute `adb shell cmd app_function` to learn how the tool works, then act as a
chat agent aiming to invoke AppFunctions to fulfil user prompts for this app.
Use the AppFunction descriptions to choose functions and parameters.

Migrar de versões anteriores da API

Na versão 1.0.0-alpha10, as AppFunctions introduziram uma arquitetura @AppFunctionServiceEntryPoint em tempo de compilação que consolida as dependências da biblioteca e substitui os provedores de configuração legados (AppFunctionConfiguration.Provider). A versão 1.0.0-alpha12 removeu os provedores legados. A versão 1.0.0-alpha13 renomeou a anotação @AppFunction como @AppFunctionDeclaration, e a versão 1.0.0-beta01 renomeou o atributo appfn:description no arquivo XML de metadados do app como appfn:instructions.

Se o app usa uma versão anterior do AppFunctions (como 1.0.0-alpha09 ou 1.0.0-alpha10), é possível automatizar a migração usando a habilidade do agente do AppFunctions em um ambiente de desenvolvimento integrado de IA, como o Gemini no Android Studio. A habilidade contém regras de migração dedicadas que orientam um agente a consolidar as dependências de build, criar o wrapper de serviço @AppFunctionServiceEntryPoint necessário, desvincular parâmetros de contexto, atualizar as declarações de manifesto e aplicar as renomeações da versão 1.0.0-beta01.