Aggiungere l'API AppFunctions alla tua app

Questa guida spiega come integrare l'API AppFunctions nella tua app per Android, implementare la logica per una funzione e verificare che l'integrazione funzioni correttamente.

Compatibilità delle versioni

Questa implementazione richiede che il progetto compileSdk sia impostato sul livello API 36 o versioni successive.

La tua app non è tenuta a verificare se le AppFunctions sono supportate. Questa operazione viene gestita automaticamente all'interno della libreria Jetpack AppFunctions. AppFunctionManager restituisce un'istanza se la funzionalità è supportata e restituisce null in caso contrario.

Dipendenze

Aggiungi le dipendenze della libreria richieste al file build.gradle.kts (o build.gradle) del modulo e configura il plug-in KSP nel modulo dell'app di primo livello come mostrato:

dependencies {
  implementation("androidx.appfunctions:appfunctions:1.0.0-alpha10")
  // 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-alpha10")
}

Implementare la logica di AppFunctions

Per implementare una AppFunction per la tua app per Android, crea una classe che implementi la logica specifica di AppFunctions. Ciò comporta la creazione di classi di dati serializzabili per parametri e risposte, quindi la fornitura della logica di base all'interno del metodo della funzione.

Il seguente codice mostra un'implementazione di esempio per la creazione di un'attività nell'app TODO, inclusa la definizione di parametri personalizzati e tipi di risposta e la logica della funzione principale utilizzando un repository.

@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.
     */
    @AppFunction(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)
}

Punti chiave sul codice

  • Per impostazione predefinita, un'implementazione di AppFunction viene eseguita nel thread dell'interfaccia utente di Android. Pertanto, un'operazione a lunga esecuzione deve:
    • Dichiara AppFunction come funzione di sospensione.
    • Passa a un dispatcher di coroutine adatto quando l'operazione potrebbe bloccare il thread.
  • Quando isDescribedByKDoc è impostato su true, la descrizione della funzione o la descrizione serializzabile viene codificata come parte di AppFunctionMetadata per aiutare l'agente a capire come utilizzare AppFunction dell'app.

Dichiarare il servizio AppFunction nel manifest

Registra la dichiarazione del servizio e la proprietà app_metadata generate da KSP all'interno del manifest del modulo, ad esempio in src/main/AndroidManifest.xml. Il compilatore KSP genera la classe di servizio concreta (TaskAppFunctionService) che estende la classe di punto di ingresso astratta, insieme allo schema XML corrispondente nella directory 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" />

(Facoltativo) Attiva/disattiva la disponibilità di AppFunction durante il runtime

Utilizza l'API AppFunctionManager per abilitare o disabilitare esplicitamente le funzioni quando controlli l'accesso alle tue AppFunctions. Il gating può essere utile quando alcune funzionalità della tua app non sono disponibili per tutti gli utenti. Attivando o disattivando dinamicamente AppFunctions, il sistema intelligente sa esattamente quali funzionalità sono disponibili per l'utente in un determinato momento.

Per proteggere in modo sicuro le AppFunctions che richiedono uno stato specifico dell'account, segui una procedura in due passaggi:

Passaggio 1: Disattivare la funzione per impostazione predefinita

Per impedire l'accesso alla funzione prima della verifica del flag di funzionalità, imposta il parametro isEnabled dell'annotazione @AppFunction su false.

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

Passaggio 2: Attivare dinamicamente la funzione in fase di runtime

Per ogni classe AppFunction, il compilatore genera una classe corrispondente contenente costanti ID funzione (utilizzando un suffisso Ids). Puoi utilizzare queste costanti ID generate insieme al metodo setAppFunctionEnabled di AppFunctionManagerCompat per modificare lo stato di attivazione di una funzione in fase di runtime.

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
    }
}

Considerazioni sui tipi di funzionalità da rendere disponibili

La sicurezza è sempre fondamentale. Quando scegli quali funzionalità della tua app rendere disponibili come AppFunctions, è importante ricordare che gli agenti di sistema potrebbero elaborare le query degli utenti sul server per sfruttare le funzionalità avanzate dei LLM.

Per offrire un'esperienza utente ottimale ed evitare l'esposizione di informazioni sensibili, ti consigliamo di seguire queste linee guida:

  • Funzionalità che traggono vantaggio dal linguaggio naturale: rendi disponibili attività che un utente può esprimere più facilmente in una conversazione che tramite la navigazione manuale dell'interfaccia utente.
  • Accesso limitato: crea AppFunctions che concedano all'agente l'accesso solo ai dati e alle azioni necessari per soddisfare la richiesta specifica dell'utente.
  • Informazioni non sensibili: condividi solo dati non altamente personali o riservati oppure dati che l'utente acconsente esplicitamente a condividere nel contesto dell'azione.
  • Conferma inequivocabile per qualsiasi azione distruttiva: fai molta attenzione alle funzioni che eseguono azioni distruttive (come l'eliminazione dei dati). Sebbene l'agente possa richiamarli, la tua app deve includere un proprio passaggio di conferma e utilizzare un linguaggio chiaro e inequivocabile sulle intenzioni. È anche utile aggiungere più di un passaggio di conferma per assicurarsi che l'utente sia consapevole di ciò che gli viene chiesto di fare.

Verificare l'integrazione di AppFunction

Per verificare se hai integrato correttamente App Functions, puoi utilizzare adb shell cmd app_function.

Utilizza adb shell cmd app_function list-app-functions | grep --after-context 10 $myPackageName per visualizzare i dettagli delle AppFunctions fornite dalla tua app.

Puoi anche eseguire una AppFunction direttamente dalla riga di comando utilizzando il suo identificatore esplicito ("$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\"}}'"

Per provare Android MCP in azione e verificare i workflow end-to-end senza richiedere prompt, installa ed esegui l'app per Android Agente di test AppFunctions sul tuo dispositivo.

Se stai verificando l'integrazione utilizzando assistenti basati sulla chat come Gemini in Android Studio, utilizza la skill di sviluppo AppFunctions o fornisci un prompt come il seguente:

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.
Rely on the AppFunction description as instructions.

Eseguire la migrazione dalle versioni API precedenti

Nella versione 1.0.0-alpha10, AppFunctions ha introdotto un'architettura @AppFunctionServiceEntryPoint in fase di compilazione che consolida le dipendenze della libreria e sostituisce i provider di configurazione legacy (AppFunctionConfiguration.Provider).

Se la tua app utilizza attualmente una versione precedente di AppFunctions (ad esempio 1.0.0-alpha09), puoi automatizzare la migrazione utilizzando la skill dell'agente AppFunctions in un IDE AI come Gemini in Android Studio. La skill contiene regole di migrazione dedicate che guidano un agente a consolidare le dipendenze della build, creare il wrapper del servizio @AppFunctionServiceEntryPoint richiesto, separare i parametri di contesto e aggiornare le dichiarazioni del manifest.

Per avviare una migrazione automatica con il tuo agente AI, utilizza un prompt come il seguente:

Use the AppFunctions migration skill to upgrade my app's AppFunctions implementation from 1.0.0-alpha09 to the 1.0.0-alpha10 @AppFunctionServiceEntryPoint architecture.