Intelligente Anwendungen

App in das Android-Intelligenzsystem einbinden

Lautsprecher

Info

Über den Vortrag

Android entwickelt sich zu einem intelligenten System, in dem KI-Agenten wie Gemini Aufgaben über verschiedene Apps hinweg koordinieren. Damit Nutzer neue Funktionen nutzen und an diesen agentischen Workflows teilnehmen können, muss Ihre App ihre Kernfunktionen offenlegen.

In dieser Session erhalten Sie eine praktische Anleitung, wie Sie Ihre App in die intelligente Ebene von Android einbinden können. Wir stellen die wichtigsten Integrationspfade vor, darunter das On-Device-Android-MCP, das serverseitige MCP und die Aufgabenautomatisierung, und bieten ein Entscheidungsframework, mit dem Sie den richtigen Pfad für Ihren Anwendungsfall auswählen können. Außerdem sehen wir uns proaktive Funktionen mit persönlichem Kontext an.

Nach Abschluss können Sie:

  • Wählen Sie den optimalen Integrationspfad für die Anforderungen Ihrer App aus.
  • Sowohl reaktive als auch proaktive Intelligence-Funktionen implementieren
  • Leistung und Sicherheit bei agentischen Integrationen in Einklang bringen

Zusammenfassung

  • Android MCP ist ein einheitliches Framework für Toolaufrufe auf dem Gerät, das auf der Jetpack AppFunctions-Bibliothek basiert. Es ordnet MCP-Primitive Kotlin-Konstrukten zu und wird auf Android Binder IPC ausgeführt.
  • On-Device-Tools sind besonders nützlich, wenn auf verschlüsselte Daten oder Gerätesensoren zugegriffen werden soll. Alternativ können Sie mit der plattformgesteuerten Aufgabenautomatisierung vorinstallierte Agenten Touch-Events simulieren lassen, ohne dass Codeänderungen erforderlich sind.
  • Ihre App fungiert als Android-MCP-Server, während KI-Agenten Tools finden und aufrufen. Deklarieren Sie Tools mit AppFunctionDeclaration, geben Sie KDoc-Beschreibungen an und vermeiden Sie die Verwendung von Kotlin-Standardwerten.
  • Auf der Connected Apps Platform, die sich derzeit in der Vorabversion befindet, können Sie Ihre AppFunctions mit Gemini testen. Richten Sie Ihre verbundene App in der Google Connected Console ein und laden Sie dann Ihr Bundle in die Play Console hoch.

Ressourcen

Gemini Notebook

Themen

  • android-mcp
  • appfunctions
  • Gemini
  • verbundene Apps
  • model-context-protocol
  • on-device-ai

Transkript

Einführung in das Zeitalter der Intelligenz

  • Okay.

  • Hallo zusammen.

  • Ich bin Dan Galpin von Developer Relations.

  • Und ich bin Sarah Shamsie, Produktmanagerin für die Developer Intelligence Frameworks von Android.

  • Wir freuen uns, dass Sie heute dabei sind.

  • Wir erleben einen Paradigmenwechsel bei der Interaktion von Nutzern mit ihren Geräten.

  • Im Webzeitalter haben Nutzer ihre Customer Journey in der Regel im Webbrowser begonnen und dann die Suchfunktion verwendet.

  • Im Zeitalter der Mobilgeräte hat sich das Nutzerverhalten dann dahingehend geändert, dass mehr spezialisierte, systemeigene Apps für einmalige Aktionen verwendet wurden.

  • Hier ein Beispiel:

  • Ich erhalte eine Nachricht von einem Freund, der mich zum Abendessen einlädt.

  • Um darauf zu antworten, muss ich in meinem Kalender nachsehen, ob ich Zeit habe, den Termin in meinen Kalender eintragen, eine Fahrt buchen, wenn ich eine Mitfahrgelegenheit brauche, und dann meiner Freundin antworten, dass ich kommen kann.

  • Bisher war dafür das sogenannte App-Hopping erforderlich, bei dem ich im Grunde zwischen verschiedenen Apps auf meinem Gerät wechseln musste, um die Aktionen tatsächlich abzuschließen und meine Reise zu planen.

  • Jetzt befinden wir uns im Zeitalter der Intelligenz.

  • Im Zeitalter der künstlichen Intelligenz beginnen Nutzer ihre Interaktionen, indem sie übergeordnete Absichten und Ziele formulieren, anstatt bestimmte Aktionen anzugeben, die ausgeführt werden sollen.

  • Es gibt eine grundlegende Veränderung: Nutzer sehen ihr Gerät als etwas, das ihre Bedürfnisse proaktiv antizipiert und ihnen hilft, ihre Ziele zu erreichen, und nicht nur als etwas, das Aktionen ermöglicht.

  • Und wir haben festgestellt, dass sie erwarten, dass dieses intelligente Framework nicht nur auf ihren Mobilgeräten, sondern auf einer Vielzahl von Formfaktoren funktioniert, darunter Smartwatches, Autos, Fernseher, Laptops, XR-Geräte und Smart Glasses.

  • Im Zeitalter der KI kann ein einziger Prompt, der mehrere Apps einbezieht, das Beispiel mit der Einladung zum Abendessen von vorhin tatsächlich umsetzen.

  • Mit nur einem Prompt kann ein Agent auf meinem Gerät alle erforderlichen Aktionen ausführen – vom Prüfen meines Kalenders über das Hinzufügen des Termins und das Buchen der Fahrt bis hin zum Senden einer Nachricht.

  • Heute möchten wir euch mehr über die Pläne von Android in diesem Bereich erzählen und euch zeigen, wie ihr als Entwickler erstklassige Erlebnisse schaffen könnt.

  • Dan.

  • Vielen Dank, Sarah.

  • Android wird also immer intelligenter und bietet neue Möglichkeiten, Ihre Apps für dieses Zeitalter der Intelligenz zu rüsten.

  • Die erste dieser beiden Säulen ist die In-App-Intelligenz. Dabei geht es darum, Ihre Apps intelligenter zu machen und Intelligenz in Ihre App zu bringen. Sie können On-Device-Modelle wie Gemini Nano 4 über ML Kit verwenden, um Nachrichten-Threads zusammenzufassen, Antworten umzuschreiben und Bilder zu verstehen oder natürliche Sprache zu verwenden, um die Funktionen Ihrer App aufzurufen.

  • Sie können Gemini-Modelle in der Cloud sogar direkt in Ihrer App über Firebase AI Logic verwenden.

  • Heute konzentrieren wir uns aber auf die zweite Säule: die Verfügbarkeit der Funktionen Ihrer App im KI-System.

  • Was ist also ein KI-System?

  • Zusätzlich zu den von Sarah erwähnten Funktionen, die auf Prompts basieren, ist es ein System, das tiefgreifende Integrationen ermöglicht, um die Nutzung im gesamten App-Ökosystem zu verbessern.

  • Geräte, die für Gemini Intelligence entwickelt wurden, nutzen diese Integrationspunkte, um beispielsweise Vorschläge, zusammengefasste Benachrichtigungen, eine verbesserte Spracheingabe und das automatische Ausfüllen von Formularen zu ermöglichen.

  • Vorschläge sind jetzt Teil einer Funktion namens „Proaktive Unterstützung“.

  • Bisher haben wir über die Unterstützung durch Prompts gesprochen.

  • Wir alle kennen das: Ein Nutzer weist einen Agenten an, eine oder mehrere Aufgaben in seinem Namen zu erledigen.

  • Bei der proaktiven Unterstützung geht es jedoch darum, die nächste(n) Aktion(en) eines Nutzers vorherzusehen.

  • In unserem Beispiel mit dem Abendessen könnte der KI-Agent dem Nutzer proaktiv Vorschläge als Antwort auf die Nachricht machen, um die Einladung dem Kalender hinzuzufügen oder eine Mitfahrgelegenheit zu buchen.

  • Aber wie wird der Nutzer eigentlich aufgefordert?

  • Die proaktive Unterstützung kann Aktionen an verschiedenen Stellen anzeigen, z. B. auf dem Sperrbildschirm, in Benachrichtigungen, auf Bildschirmtastaturen und sogar in ausgewählten Apps.

  • Wie können Sie also dafür sorgen, dass Ihre App tatsächlich in diesen proaktiven Chips angezeigt wird, um mehr Traffic zu generieren?

  • Sie entwickeln bereits mit der neuesten SDK-Version und unseren modernsten Tools. Am besten sorgen Sie jetzt dafür, dass Ihre App auch für die Unterstützung durch Prompts bereit ist. Darüber wird Sarah als Nächstes sprechen.

Prompt-basierte Unterstützung und Android MCP

  • Danke, Dan.

  • Bei der Unterstützung durch Prompts wenden sich Nutzer direkt an einen KI-Agenten und bitten ihn, Aufgaben zu initiieren und zu koordinieren, auch über mehrere Apps hinweg.

  • So lassen sich bisher manuelle und zeitaufwendige Workflows automatisieren.

  • Bei unserem KI-System für Android konzentrieren wir uns hauptsächlich auf System-Agenten wie Gemini, die auf Apps auf dem Gerät des Nutzers zugreifen können.

  • Es gibt verschiedene Möglichkeiten, wie Ihre App in dieser Umgebung angezeigt werden und mit dem System-Agenten interagieren kann.

  • Es gibt unsere plattformgestützte Lösung für die Aufgabenautomatisierung und direkte API-Integrationen in den System-Agenten.

  • Wir empfehlen, nach Möglichkeit immer direkte API-Integrationen zu verwenden.

  • Diese werden von Entwicklern gesteuert. Sie können also entscheiden und steuern, wie Ihre App und Ihre Marke präsentiert werden.

  • Sie sind außerdem schneller und zuverlässiger und bieten Ihren Nutzern eine bessere Nutzererfahrung.

  • Es gibt zwei API-Pfade, die Sie für die Einbindung des KI-Agenten in Betracht ziehen können.

  • Das Model Context Protocol (MCP) ist die branchenübliche Best Practice für serverseitige Funktionsaufrufe.

  • Für Toolaufrufe auf dem Gerät kündigen wir Android MCP an, das einheitliche Framework für Funktionsaufrufe auf Android.

  • Android MCP basiert auf unserer Jetpack AppFunctions-Bibliothek, die technisch und konzeptionell eng mit MCP verwandt ist und gleichzeitig eine vertraute und idiomatische Kotlin-Implementierung unterstützt, mit der viele Android-Entwickler vertraut sind.

  • Dadurch ist die Implementierung der API relativ intuitiv und einfach zu verwenden. Außerdem können Sie alle Erkenntnisse nutzen, die Sie möglicherweise aus einer bereits vorhandenen serverseitigen MCP-Integration gewonnen haben.

  • Viele MCP-Primitive lassen sich eins zu eins einem Jetpack AppFunctions-Konstrukt zuordnen.

  • Sehen wir uns dazu einige Beispiele an.

  • Ein MCP-Tool-Endpunkt lässt sich gut einer Kotlin-Funktion zuordnen, die mit AppFunctionDeclaration annotiert ist.

  • Ihre Pydantic-Modelle werden Kotlin-Datenklassen zugeordnet, die mit AppFunctionSerializable annotiert sind.

  • Anstatt streamfähiger HTTP- und/oder Standard-E/A-Transporte wird Android MCP jedoch über die optimierte Android Binder-IPC ausgeführt.

  • Wir freuen uns, Ihnen heute mitteilen zu können, dass die Jetpack-Bibliothek „AppFunctions“ jetzt in der Betaphase ist.

Wichtige Anwendungsfälle für Android MCP

  • Sie fragen sich jetzt wahrscheinlich, wann es sinnvoll ist, einen API-Pfad zu verwenden, und wann Android MCP.

  • Als übergeordnetes Prinzip gilt: Android MCP ist besonders nützlich, wenn Sie On-Device- oder lokalen Kontext nutzen möchten, um eine User Journey zu erfüllen.

  • Sehen wir uns einige Beispiele an, bei denen Android MCP wirklich glänzt.

  • Insbesondere sollten Sie Android MCP in Betracht ziehen, wenn Sie auf lokale, verschlüsselte Daten zugreifen müssen.

  • Das ist besonders wichtig für Messaging- und Kommunikations-Apps, bei denen der Nachrichtenverlauf aufgrund der Ende-zu-Ende-Verschlüsselung auf dem Gerät bleiben muss.

  • Android MCP ist auch für reine On-Device- und Offline-Aktionen nützlich, sofern KI-Agenten dies unterstützen.

  • Unser Wear OS-Partner Samsung Health hat eine Funktion eingeführt, mit der Läufe und Trainings gestartet werden können, die vollständig auf dem Gerät koordiniert werden.

  • Nutzer möchten oft, dass diese Arten von Aktionen sehr schnell, mit hoher Zuverlässigkeit, geringer Latenz und manchmal sogar in Umgebungen mit schlechter Verbindung ausgeführt werden. Wenn das gesamte Toolaufrufen und Routing sowie das Abfragerouting auf dem Gerät erfolgen kann, ist das gewährleistet.

  • Android MCP ist auch der bevorzugte API-Pfad, wenn Sie auf Gerätesensoren zugreifen oder direkt mit der Gerätehardware interagieren möchten.

  • Viele Auto-Apps sollen es Nutzern beispielsweise ermöglichen, ihre Autotüren sofort über eine UWB-Verbindung und oft auch per Sprachbefehl zu entriegeln.

  • Android MCP kann dabei helfen.

  • Und schließlich ist es die Lösung, wenn für Ihre Anwendungen oder API kein Cloud-Endpunkt verfügbar ist.

  • Viele der eigenen Apps unserer Gerätehersteller sind nur clientseitig verfügbar. Aus diesem Grund verwenden sie Android MCP.

  • Gemini kann beispielsweise verwendet werden, um Fotos aus Galerie-Apps zu suchen und abzurufen, die nur clientseitig sind, wie Xiaomi Gallery.

  • Hier kann ich Gemini aufrufen und fragen: „Suche die Fotos des Android-Bots, die ich letzte Woche in der Xiaomi-Galerie aufgenommen habe.“

  • Xiaomi hat eine App-Funktion zum Suchen nach Fotos bereitgestellt, die dann von Gemini aufgerufen wird. Die relevanten Fotos werden zurückgegeben und direkt in der Benutzeroberfläche der Gemini App angezeigt, sodass der Nutzer die App nicht verlassen muss.

  • Und wir nutzen genau dieselbe Architektur für On-Device-Aktionen im Android-Betriebssystem.

  • Android MCP unterstützt die kürzlich eingeführte Funktion „Gerätehilfe“, die eine direkte Integration des Android-Betriebssystems in die Gemini App ist.

  • Diese Funktion wurde bereits auf Pixel- und Lenovo-Geräten eingeführt und wird derzeit auf weitere Android-OEM-Geräte ausgeweitet.

  • Nutzer können die Gerätehilfe verwenden, indem sie Gemini fragen, wie sie ihre Einstellungen, Geräteeinstellungen und Systemeinstellungen optimieren können, um die Nutzung ihres Geräts zu verbessern.

  • Anstatt sich durch verschiedene Menüs zu klicken, kann ein farbenblinder Nutzer Gemini beispielsweise fragen: „Ich bin farbenblind.

  • Was kann ich tun, um dieses Smartphone für mich zu optimieren?“

  • Gemini ruft lokale App-Funktionen auf, analysiert die Anfrage auf intelligente Weise und schlägt konkrete Möglichkeiten vor, wie der Nutzer seine System- und Geräteeinstellungen ändern kann, um die Nutzung zu verbessern.

  • Anschließend werden die In-App-Einstellungen direkt in der Benutzeroberfläche der Gemini App angezeigt, sodass Nutzer sofort die Kontrolle übernehmen können.

  • Um beispielsweise diese Anfrage zur Optimierung für Farbenblindheit zu erfüllen, hat Gemini diesen Schalter für die Farbkorrektur eingeblendet, damit Nutzer die Änderung sofort vornehmen können.

  • Android-MCP-Funktionen sind bereits auf ausgewählten Geräten verfügbar. Wir arbeiten mit mehreren Partnern zusammen, um noch mehr Anwendungsfälle für das Android-Ökosystem zu entwickeln.

  • Explizite Android-MCP-Tool-Deklarationen sind unser Goldstandard. Wir wissen jedoch, dass es Fälle geben wird, in denen Nutzer Agenten bitten, komplexe oder weniger gängige Anwendungsfälle zu erledigen. Hier kommt unsere plattformgesteuerte Funktion zur Aufgabenautomatisierung ins Spiel.

  • Bei diesem Modell kann der vom OEM vorinstallierte Agent die Computersteuerungs-APIs von Android verwenden, um Touch-Events auf einer App-Oberfläche zu simulieren.

  • Dafür sind keine Codeänderungen durch Entwickler erforderlich. Der KI-Agent muss sich durch die Benutzeroberfläche tippen, wie es ein Nutzer tun würde, und dabei den App-Bildschirm analysieren.

  • Damit Nutzer die Kontrolle behalten und geschützt sind, ist es wichtig, dass KI-Agenten bei potenziell sensiblen Aktionen wie Zahlungsvorgängen pausieren und die Bestätigung des Nutzers einholen, bevor sie fortfahren.

  • Sehen wir uns ein Beispiel an.

  • In diesem Beispiel hat ein Nutzer ein Foto von einer Kaffee- und Schokoladentour gemacht und möchte Gemini bitten, auf Expedia nach einer ähnlichen Tour zu suchen, aber die Anzahl der Teilnehmer auf sechs Personen anzupassen.

  • Gemini navigiert im Hintergrund durch die Benutzeroberfläche von Expedia, genau wie ein Nutzer, ohne dass eine direkte API-Integration erforderlich ist.

  • Anschließend werden relevante Optionen angezeigt, die gefunden wurden, und die Nutzer können die endgültige Bestätigung und Buchung vornehmen.

  • Diese drei verschiedenen Pfade schließen sich nicht gegenseitig aus, sondern ergänzen sich.

  • Nutzer und Entwickler sollten erwarten können, dass Agenten Tools über Serverimplementierungen und die lokale Android-App hinweg koordinieren können, um Nutzern die umfassendste und ganzheitlichste Erfahrung zu bieten.

Technische Details zu Android MCP

  • Okay.

  • Vielen Dank, Sarah.

  • Wir sehen uns Android MCP jetzt etwas genauer an.

  • Und wenn möglich, möchte ich, dass Sie wissen, dass ich die MCP-Code-Snippets aus Jetpacker abrufe, unserer Beispiel-App für Reisen, die tatsächlich mit Hilfe von Antigravity erstellt wurde.

  • Sie haben das in der Keynote gesehen.

  • Sie bietet viele coole intelligente Funktionen, darunter On-Device-Integrationen mit den APIs für generative KI und Spracherkennung des ML Kit.

  • Android-MCP und cloudbasierte MCP haben jetzt eine ähnliche Struktur.

  • Beide haben Clients und Server.

  • In der MCP-Welt stellen Server Tools bereit, die von Clients gefunden und aufgerufen werden können.

  • Mit Android MCP ist Ihre App jetzt der Server und der Agent der Client.

  • Die Android-Plattform fungiert als Tool-Registry.

  • Apps registrieren zuerst einen Dienst, um den Server bereitzustellen. In unserem Beispiel fügen wir zuerst die Abhängigkeiten der AppFunctions-Jetpack-Bibliothek hinzu.

    implementation(libs.androidx.appfunctions)
    ksp(libs.androidx.appfunctions.compiler)
  • Anschließend verwenden wir KSP-Annotationen, um eine AppFunctionService zu erstellen.

  • Dieser Dienst sorgt dafür, dass die Funktionen Ihrer App die Android-Lebenszyklen ordnungsgemäß berücksichtigen.

  • Wir können die Abhängigkeitsinjektion mit Hilt verwenden, um Daten-Repositories einzufügen, die von unseren AppFunctions verwendet werden können.

    @RequiresApi(36)
    @AndroidEntryPoint
    @AppFunctionServiceEntryPoint(
        serviceName = "JetPackerAppFunctionService",
        appFunctionXmlFileName = "jetpacker_app_function_service"
    )
    abstract class BaseJetPackerAppFunctionService : AppFunctionService() {
        @Inject internal lateinit var tripDao: TripDao
        @Inject internal lateinit var expenseDao: ExpenseDao
        ...
  • Tut mir leid.

  • Zurück zu unserem Diagramm.

  • Nachdem der Dienst für unseren Android-MCP-Server eingerichtet wurde, registriert die App Tools in Form von AppFunctions sowie alle zugehörigen Datenmodelle, die von ihnen verwendet werden.

  • Jetzt können Sie ein Datenmodell mit der Annotation AppFunctionSerializable registrieren. Dieses KDoc hier ist ziemlich cool.

    /** Represents an event in a trip itinerary. */
    @AppFunctionSerializable(isDescribedByKDoc = true)
    data class TimelineEventSerializable(
      /** The event's unique identifier. */
      val id: String,
      /** The trip ID this event belongs to. */
      val tripId: String,
      /** The type of event (e.g., TRANSPORTATION, ACCOMMODATION, FOOD_AND_DRINK). */
      val type: String,
      /** The timestamp of the event. */
      val timestamp: Long,
      /** The title of the event. */
      val title: String,
      /** The location of the event. */
      val location: String,
      /** A description of the event. */
      val description: String?,
      /** The title of the trip this event belongs to. */
      val tripTitle: String? = null,
      /** Optional title of a matched itinerary event when added as a reminder. */
      val matchedEventTitle: String? = null,
      /** Execution status of the tool call (e.g., SUCCESS). */
      val status: String? = "SUCCESS",
      /** Human-readable completion message instructing the agent not to repeat the call. */
      val message: String? = null,
    )
  • Sie werden einfach zu Metadaten, die der KI-Agent verwendet, um zu verstehen, wie das Modell verwendet werden soll.

  • Mit der Annotation AppFunctionDeclaration können Sie Ihre Funktion als Tool registrieren.

    @AppFunctionDeclaration(isDescribedByKDoc = true)
    suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
      return withContext(Dispatchers.IO) {
        val trip = resolveTrip(tripId = tripId)
        val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?: tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
        val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
        val events =
          if (dbEvents.isNotEmpty()) { dbEvents } else {
            val fallback = DummyData.events.filter { it.tripId == targetTripId }
            if (fallback.isNotEmpty()) { eventDao.insertEvents(fallback) }
            fallback
          }
        events.map { it.toSerializable(tripTitle = trip?.title) }
      }
    }
  • Wenn isDescribedByKDoc auf „true“ gesetzt ist, stellt KDoc dem Agenten eine Beschreibung zur Verfügung, damit er versteht, was Ihre Funktion tut. Außerdem hilft es Menschen, sie zu verstehen.

    /**
     * Retrieves the itinerary events (with their eventId, title, type, and location) for a specific
     * trip. Note: getItinerary returns event summaries only; call getEventDetails(eventId) with a
     * specific event's ID to retrieve ticket prices, hotel nightly rates, baggage allowance, or
     * visitor tips.
     *
     * @param tripId Optional ID of the trip (e.g., '2026-1'). Defaults to the active trip if omitted.
     * @return A list of events in the itinerary.
     */
    @AppFunctionDeclaration(isDescribedByKDoc = true)
    suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
      return withContext(Dispatchers.IO) {
        val trip = resolveTrip(tripId = tripId)
        val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?: tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
        val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
    ...
  • Hier sind einige Best Practices für das Parameterdesign.

    /**
     * @param tripId Optional ID of the trip (e.g., '2026-1').
     *   Defaults to the active trip if omitted.
     * @return A list of events in the itinerary.
     */
    @AppFunctionDeclaration(isDescribedByKDoc = true)
    suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
      return withContext(Dispatchers.IO) {
        // resolveTrip defaults to the active trip if tripId == null
        val trip = resolveTrip(tripId = tripId)
  • Zuerst sollten Sie sich auf die minimale Teilmenge von Parametern mit klaren, eindeutigen Namen konzentrieren.

  • Machen Sie nicht unbedingt erforderliche Parameter nullable und lösen Sie ihre Standardwerte im Funktionsrumpf auf.

  • Das ist nicht nur eine Best Practice.

  • Das funktioniert nicht, weil Kotlin-Standardwerte ignoriert werden, da die AppFunctions-Bibliothek für ausgelassene Parameter, die Nullwerte zulassen, „null“ übergibt.

  • Ich zeige Ihnen nicht einmal ein Beispiel für die erste Option, aber verwenden Sie keine Catch-All-Parameter.

  • Dinge wie der vollständige Kontext oder der Unterhaltungsverlauf sind einfach schlecht.

  • Sie können dazu führen, dass die Agenten große Datenmengen weitergeben, was zu einer Exfiltration führen kann. Das ist einfach keine gute Praxis.

  • Da die AppFunction standardmäßig im UI-Thread ausgeführt wird, sollten Sie dafür sorgen, dass alle Vorgänge mit langer Ausführungszeit im Hintergrund ausgeführt werden. Wählen Sie dazu den entsprechenden Coroutine-Dispatcher aus.

    @AppFunctionDeclaration(isDescribedByKDoc = true)
    suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
      return withContext(Dispatchers.IO) {
        val trip = resolveTrip(tripId = tripId)
        val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?:
        tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
        val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
        val events =
          if (dbEvents.isNotEmpty()) { dbEvents } else {
            val fallback = DummyData.events.filter { it.tripId == targetTripId }
            ...
  • Sie sollten bei Bedarf AppFunction-Ausnahmen mit detaillierten Informationen auslösen, damit der Agent weiß, wie ein Fehler behoben werden kann.

    @AppFunctionDeclaration(isDescribedByKDoc = true)
    suspend fun send(
        message: Message
    ): Result {
      ...
      throw AppFunctionException(
        errorCode = AppFunctionException.ErrorCode.ERROR_INVALID_ARGUMENT,
        errorMessage = "To address has invalid syntax!"
      )
    }
  • Wir haben jetzt eine AppFunction. Kehren wir zu unserem Architekturdiagramm zurück.

  • Die Agent-Client-App registriert sich bei der Plattform, um AppFunctions aufzurufen.

  • Das ist zum Beispiel Gemini.

  • Dazu benötigen sie eine privilegierte Berechtigung. Registrierte Agents können dann AppFunction-Metadaten von der Android-Plattform abrufen und entscheiden, ob sie eine oder mehrere registrierte AppFunctions ausführen sollen, um die Anfrage eines Nutzers zu erfüllen.

  • Wie entscheidet der Agent, welche AppFunctions er verwendet?

  • Eine letzte Sache müssen Sie noch tun: Sie müssen die Funktionen Ihrer App in einer App-Metadatendatei beschreiben.

    <AppFunctionAppMetadata
    xmlns:appfn="http://schemas.android.com/apk/androidx.appfunctions"
    
      appfn:description="This app manages trips and travel expenses.
    
      Operational Patterns:
        - Use 'getAllTrips' to find the correct 'tripId' before
          adding expenses or managing trip details.
    
      Constraints:
        - Expenses must be associated with a valid trip ID."
    
      appfn:displayDescription="@string/user_visible_description" />
  • KI-Agenten wählen Tools aus, indem sie zuerst diese Beschreibung in Ihrem AppFunctionAppMetadata lesen und dann die Beschreibungen der einzelnen AppFunctions.

  • Konzentrieren Sie sich am Anfang der Beschreibung also auf eine kurze Erläuterung des Hauptbereichs Ihrer App oder Tools.

  • Anschließend können Sie operative Muster hinzufügen, damit der Agent den logischen Ablauf von Vorgängen versteht. Das ist hilfreich, um Voraussetzungen zu beschreiben oder den Agenten auf optimale Pfade zur Erledigung von Aufgaben zu lenken.

  • Außerdem können Sie einen Abschnitt zu Einschränkungen hinzufügen, in dem logische Grenzen für Eingabedatentypen definiert oder operative Muster verstärkt werden.

  • Das waren die Grundlagen zum Android-MCP. Es gibt aber noch ein paar weitere Dinge, die du tun kannst.

  • Eine interessante Möglichkeit ist, dass Sie AppFunctions dynamisch aktivieren und deaktivieren können.

    viewModelScope.launch {
        val newState =
            if (enabled) {
                AppFunctionManager.APP_FUNCTION_STATE_ENABLED
            } else {
                AppFunctionManager.APP_FUNCTION_STATE_DISABLED
            }
        appFunctionManager?.setAppFunctionEnabled(id, newState)
    }
  • Das ist zum Beispiel wichtig, wenn AppFunction nur verfügbar sein soll, wenn der Nutzer angemeldet ist.

  • Sie können auch AppFunctions erstellen, die auf einen bestimmten Bildschirm oder eine bestimmte Aktivität in Ihrer App beschränkt sind.

    @AppFunctionSignature(
        scope = AppFunctionMetadata.SCOPE_ACTIVITY,
        appFunctionXmlFileName = "cart_functions"
    )
    fun interface AddCurrentItemToCart {
        /** Adds the item currently shown on screen to the cart. Returns the new cart size. */
        suspend fun addToCart(quantity: Int): Int
    }
  • Anstatt die AppFunction zu deklarieren, deklarieren Sie also AppFunctionSignature.

  • Anschließend können Sie einen Handler für die AppFunction in Ihrer Benutzeroberfläche implementieren.

    val addToCartAppFunctionAdapter =
        appFunctionManager.getHandleAppFunctionRequestAdapter<AddCurrentItemToCart>()
    
    coroutineScope.launch {
        appFunctionManager.handleAppFunction(
            addToCartAppFunctionAdapter.adapt { quantity ->
                val newCartSize = cart.addItem(quantity)
                newCartSize
            }
        )
    }
  • Das Tolle daran ist, dass der Lebenszyklus der Handler automatisch an den Lebenszyklus des verwendeten Koroutinenbereichs gebunden ist.

  • Jetzt sollten Sie diesen AppFunctions-Anfrageadapter im Cache speichern, da die Bibliothek im Hintergrund Reflexion verwendet, um ihn zu instanziieren.

  • Außerdem gibt es Funktionen zum Erstellen von AppFunctions mit eingeschränktem Bereich, deren Registrierung nicht direkt an den Android-Lebenszyklus gebunden ist.

  • Eine letzte Sache ist die Zugriffsebene.

    @AppFunctionDeclaration(isDescribedByKDoc = true)
    @AppFunctionAccessLevel(
        level = AppFunctionMetadata.ACCESS_LEVEL_SELF,
        isCompatEnforcementEnabled = true,
    )
    suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
      return withContext(Dispatchers.IO) {
      ...
  • Sie können die Zugriffsebene „self“, „system“ oder „trusted“ verwenden. Damit wird angegeben, welche Funktionen nur für Ihren eigenen In-App-Agenten (z. B. einen mit ML Kit GenAI erstellten Agenten), welche Funktionen nur für das System und welche Funktionen für vertrauenswürdige System-Agenten verfügbar sind.

  • Und um Ihnen beim Erstellen Ihres eigenen In-App-Agents zu helfen, haben wir bereits angekündigt, dass ML Kit AppFunctions als Teil seines einheitlichen Toolaufruf-Frameworks nativ unterstützt.

  • Sie definieren sie einmal und erhalten Abdeckung sowohl für Agenten auf Systemebene als auch für lokale In-App-Modelle. So können Sie fragmentierte Tooldeklarationen vermeiden.

  • Weitere Informationen finden Sie im Vortrag „Unlock agentic capabilities in your app with ML Kit GenAI“ oder in den neuesten Jetpack-Inhalten.

  • Das ist ziemlich cool.

  • Zurück zu Sarah.

  • Okay.

  • Dan hat Ihnen gerade gezeigt, wie Android MCP im Hintergrund funktioniert.

  • Setzen wir das Gelernte in die Praxis um.

  • Denken Sie daran, dass Ihre App ein Server und die Kundenservicemitarbeiter die Clients sind. Das Ziel sollte also sein, dass Ihre App ein guter Server für sie ist.

  • Wir begleiten Jetpacker, unsere Beispiel-App für Reisen, von der ersten Tooldeklaration bis zu einem tatsächlichen Funktionsaufruf über Gemini.

  • Sie können Ihre AppFunctions selbst schreiben oder einen Coding-Agenten verwenden, um den Einstieg zu erleichtern.

  • Wir haben den AppFunctions-Skill veröffentlicht, der Ihnen den Einstieg in das Schreiben von Funktionen erleichtert. Sie können diese über die Android CLI mit Android Studio verbinden oder mit dem KI-Coding-Agenten Ihrer Wahl verwenden.

    android skills add appfunctions
  • Der Skill funktioniert in drei Schritten.

  • Zuerst wird Ihre Codebasis analysiert, um wichtige User Journeys zu identifizieren, die nützliche Tools darstellen könnten, z. B. das Abspielen von Medien, das Erstellen einer Notiz oder das Senden einer Nachricht.

  • Anschließend werden AppFunction-Deklarationen generiert, die diese Abläufe mit der erforderlichen Implementierung und Plattformunterstützung bereitstellen.

  • Beispiele: Gradle und KSP, XML-Metadaten der App und Dependency Injection.

  • Schließlich werden Ihre KDoc-Beschreibungen optimiert, da der KI-Agent genau diese verwendet, um zu entscheiden, wann und wie Ihre Funktion aufgerufen werden soll.

  • Hier ist die Art von Ergebnis, nach der wir suchen.

  • Dies ist eine addExpense-Funktion für Jetpacker.

    /**
     * Adds an expense to a trip.
     *
     * @param tripId ID of the trip. If null, the active trip is used.
     * @param category Expense category. If null, it's inferred from the title.
     */
    @AppFunctionDeclaration(isDescribedByKDoc = true)
    suspend fun addExpense(
        title: String,
        amount: Double,
        currency: String,
        tripId: String? = null,
        category: String? = null,
    ): ExpenseSerializable = withContext(Dispatchers.IO) {
        if (amount <= 0.0) {
            throw AppFunctionInvalidArgumentException("amount must be greater than zero")
        }
        val trip = resolveTrip(tripId)
            ?: throw AppFunctionElementNotFoundException("No trip with ID $tripId")
        val expense = Expense(trip.id, title, amount, currency,
            category = category ?: inferExpenseCategory(title))
        expenseDao.insertExpense(expense)
        expense.toSerializable(remainingBudgetUsd(trip.id))
    }
  • Die Beschreibung ist ein einzeiliger Satz, der mit einem Verb und einer Ressource beginnt: „adds an expense to a trip“ (fügt einer Reise eine Ausgabe hinzu).

  • Die Felder „trip“ und „category“ können null sein, da Nutzer diese nicht immer angeben. Die Funktion löst sie jedoch im Text auf.

  • Wenn eine Anfrage nicht erfolgreich ist, wird ein AppFunctionException ausgegeben, damit der Agent weiß, was schiefgelaufen ist.

App-Funktionen und KI-Agenten testen

  • Sehen wir uns nun das Testen an.

  • Ihre Funktionen sind jetzt Dienste, die von KI-Agenten verwendet werden können.

  • Sie müssen also zwei Dinge testen: das Verhalten Ihrer Funktion und das Verhalten des KI-Agenten.

  • Für Ihre Funktion beginnen wir mit dem Unit-Test, den Sie wahrscheinlich bereits geschrieben haben. Es gibt jedoch ein paar Dinge, die Sie beachten sollten, wenn der KI-Agent den Funktionsaufruf tatsächlich ausführt.

  • Erstens gelten hier keine Kotlin-Standardwerte.

  • Ein weggelassener Parameter wird als null, false oder 0 übergeben.

  • Ein Limit, das zuvor standardmäßig auf 10 festgelegt war, ist hier also 0.

  • Der Aufruf beginnt auch im Hauptthread. Wenn Sie Ihre Funktion aus einem Test aufrufen, ist das möglicherweise nicht immer offensichtlich. Sie sollten Ihre Standard-E/A daher in einem Hintergrund-Dispatcher ausführen.

  • Wenn ein Fehler auftritt, wird ein AppFunctionException ausgelöst.

  • Alles andere wird als allgemeiner unbekannter Fehler mit der angehängten Rohausnahmemeldung an Ihren Agenten weitergeleitet.

  • Ihre Eingaben stammen aus einem Modell. Sie sollten also auf jeden Fall Grenzfälle wie Leerzeichen, Synonyme für Dollar wie USD und Werte außerhalb des Bereichs testen.

  • Auf der Seite des KI-Agenten möchten Sie wissen, ob ein KI-Agent Ihr Tool zur richtigen Zeit auswählt und die Funktion korrekt ausführt.

  • Jeder Agent verhält sich etwas anders. Daher ist es wichtig, Ihre Funktionen mit allen Agenten zu testen, die sie voraussichtlich verwenden werden.

  • Sie können unseren Open-Source-Beispiel-Agenten oder Gemini verwenden, um diese Integration zu testen.

Verbindung mit Gemini über die Connected Apps Platform herstellen

  • Wenn Sie sehen möchten, wie Gemini mit Ihren AppFunctions interagiert, oder wenn Sie ein Android-MCP in der Gemini App starten möchten, können Sie das mit der Connected Apps Platform tun, die wir heute vorgestellt haben und die sich derzeit in der Vorabversion befindet.

  • Weitere Informationen finden Sie in unserem früheren Vortrag über die Erweiterung Ihrer App auf Gemini und die Google Suche über die Connected Apps Platform.

  • Sobald Sie am Vorschauprogramm teilnehmen, sind nur zwei Schritte erforderlich, um Gemini zu testen.

  • Zuerst erstellen wir in der Google Connected Console eine verbundene App, die Gemini nutzen kann.

  • Zweitens müssen wir in der Play Console Ihr App-Bundle hochladen, damit wir auf die AppFunctions zugreifen können. Sie können mit einem internen Test-Track beginnen, damit Tester die Funktionen tatsächlich validieren können.

  • Beginnen wir in der Google Connected Console.

  • Hier kannst du über den Button „App erstellen“ rechts oben eine verbundene App erstellen.

  • Wählen Sie beim Erstellen der verbundenen App auf diesem Bildschirm „Android MCP“ aus.

  • Wenn Sie eine serverseitige MCP-Implementierung erstellen würden, würden Sie hier Ihren Servercode hochladen.

  • Wenn Sie auf „Einrichten“ klicken, werden alle Google Play-Apps, die mit Ihrem Entwicklerkonto verknüpft sind, hier angezeigt und können mit Ihrer verbundenen App verknüpft werden.

  • Wenn Sie noch keine Google Play-App haben, müssen Sie zuerst eine in der Play Console erstellen.

  • Hier haben wir bereits Jetpacker, also verknüpfen wir es.

  • Fügen Sie als Nächstes die Details hinzu, die Nutzer im Gemini-Frontend sehen, wenn sie versuchen, mit Ihrer App zu interagieren.

  • Dazu gehören beispielsweise ein App-Symbol, ein Anzeigename, die Funktionen Ihrer verbundenen App und Beispielprompts, mit denen Nutzer mit der App interagieren können.

  • Diese sollten aussagekräftig sein, um Nutzern die Orientierung zu erleichtern.

  • Damit ist die Einrichtung der verbundenen App abgeschlossen. Sie fragen sich aber vielleicht, wie die verbundene App tatsächlich Zugriff auf die Funktionen erhält.

  • Da die Funktionen in Ihrem App-Code enthalten sind, müssen wir jetzt die Play Console aufrufen, Ihr App-Bundle hochladen und einen Test-Track erstellen, damit wir die Funktionen mit echten Nutzern testen können.

  • Auf diesem Bildschirm laden wir das App Bundle hoch. Wir sehen dann, dass Google Play die AppFunctions erkennen kann und Sie sie direkt mit Ihrer verbundenen App verknüpfen können.

  • Jetzt sehen wir die verbundene Jetpack-App mit den integrierten Funktionen.

  • Als Nächstes müssen Sie Tester hinzufügen, damit jemand bereit ist, Ihre Funktionen zu validieren und sicherzustellen, dass sie ordnungsgemäß funktionieren.

  • Klicken Sie also hier auf Ihre verbundenen App-Versionen.

  • Auf diesem Bildschirm können Sie „Tester verwalten“ auswählen und eine begrenzte Liste von Testern konfigurieren, die in Gemini auf Ihre verbundene App zugreifen können, sobald sie verfügbar ist.

  • Sie können diesem Test-Track Tester auf dieselbe Weise hinzufügen wie bei jedem anderen Release.

  • Achten Sie darauf, dass die Liste der Tester für die verbundene App mit der Liste der Tester für Ihre Google Play-App übereinstimmt.

Demo der Gemini-App-Integration

  • Jetzt testen wir die Integration.

  • Hier ist die Jetpacker-App auf dem Smartphone eines Nutzers, in der bereits eine Reise für diesen Gipfel protokolliert ist.

  • Ich öffne Gemini, um die App zu verbinden.

  • Ich muss sicherstellen, dass auf meinem Gerät dieselbe Version der App installiert ist, die wir zuvor in Google Play hochgeladen haben. Außerdem sollten wir ein Konto für Gemini verwenden, das der Testerliste hinzugefügt wurde.

  • Ich erwähne also Jetpacker und frage nach der Bestätigungsnummer meines Hotelzimmers in Mountain View.

  • Gemini ruft die AppFunctions von Jetpacker auf dem Gerät auf und antwortet: „RED11223 im Redwood Grove Hotel.“

  • Als Nächstes frage ich: „Wie viel Budget habe ich noch für ADS?“

  • Der Funktionsaufruf gibt 1.850 $ zurück.

  • Jetzt möchte ich eine Änderung vornehmen und bitte Gemini, 45 $ für die Uber-Fahrt zum Flughafen zu meiner ADS-Reise hinzuzufügen.

  • Gemini ruft die zuvor erstellte Funktion addExpense auf, um diese direkt zu meinem Reiseplan hinzuzufügen.

  • Und wenn ich Jetpacker öffne, ist die Ausgabe bereits vorhanden und das Budget entsprechend reduziert.

  • Wenn Sie noch heute mit der Entwicklung von verbundenen Apps für Gemini beginnen möchten, können Sie Zugriff auf die Connected Apps Platform beantragen, die sich derzeit in der Vorabversion befindet.

Zusammenfassung und Zukunft mobiler Agenten

  • Okay.

  • Fassen wir also zusammen.

  • Wir stehen also am Anfang einer massiven Veränderung im Bereich Mobile Computing und es ist wirklich an der Zeit, dafür zu sorgen, dass Ihre App bereit ist.

  • Identifizieren Sie zuerst die wichtigsten User Journeys Ihrer App.

  • Stellen Sie das Backend Ihrer Cloud mit serverseitigem MCP bereit und verwenden Sie Android MCP, um mit Ende-zu-Ende-verschlüsselten Daten, lokalen Vorgängen, Gerätesensoren und anderen On-Device-Elementen zu arbeiten.

  • Wenn Sie jetzt einen Prototyp erstellen möchten, können Sie Android Studio und die Android-Befehlszeile herunterladen.

  • Sie können die AndroidX AppFunctions-Beta-Abhängigkeit zu Ihren Build-Dateien hinzufügen und Ihre erste On-Device-Funktion lokal testen.

  • Wenn Sie sich für einen frühen Start mit Gemini in der Produktion registrieren möchten, können Sie sich über diesen QR-Code anmelden.

  • Sehen Sie sich auch die Vorträge zur Arbeit mit der Connected Apps Platform und zur Verwendung interner AppFunctions mit ML Kit oder mit Ihrem eigenen Agenten an, um Ihren eigenen In-App-Agenten zu steuern.

  • Vielen Dank für Ihre Zeit.

  • Wir sind schon gespannt, welche tollen Apps ihr für Android entwickeln werdet.

Code

Code-Snippets

Kotlin-Skript-Snippet bei 11:56 in „Technical deep dive into Android MCP“
implementation(libs.androidx.appfunctions)
ksp(libs.androidx.appfunctions.compiler)
Kotlin-Snippet bei 12:14 in „Technical deep dive into Android MCP“
@RequiresApi(36)
@AndroidEntryPoint
@AppFunctionServiceEntryPoint(
    serviceName = "JetPackerAppFunctionService",
    appFunctionXmlFileName = "jetpacker_app_function_service"
)
abstract class BaseJetPackerAppFunctionService : AppFunctionService() {
    @Inject internal lateinit var tripDao: TripDao
    @Inject internal lateinit var expenseDao: ExpenseDao
    ...
Kotlin-Snippet bei 12:29 in „Technical deep dive into Android MCP“
/** Represents an event in a trip itinerary. */
@AppFunctionSerializable(isDescribedByKDoc = true)
data class TimelineEventSerializable(
  /** The event's unique identifier. */
  val id: String,
  /** The trip ID this event belongs to. */
  val tripId: String,
  /** The type of event (e.g., TRANSPORTATION, ACCOMMODATION, FOOD_AND_DRINK). */
  val type: String,
  /** The timestamp of the event. */
  val timestamp: Long,
  /** The title of the event. */
  val title: String,
  /** The location of the event. */
  val location: String,
  /** A description of the event. */
  val description: String?,
  /** The title of the trip this event belongs to. */
  val tripTitle: String? = null,
  /** Optional title of a matched itinerary event when added as a reminder. */
  val matchedEventTitle: String? = null,
  /** Execution status of the tool call (e.g., SUCCESS). */
  val status: String? = "SUCCESS",
  /** Human-readable completion message instructing the agent not to repeat the call. */
  val message: String? = null,
)
Kotlin-Snippet bei 12:38 in „Die App im Detail zu Android MCP“
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
  return withContext(Dispatchers.IO) {
    val trip = resolveTrip(tripId = tripId)
    val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?: tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
    val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
    val events =
      if (dbEvents.isNotEmpty()) { dbEvents } else {
        val fallback = DummyData.events.filter { it.tripId == targetTripId }
        if (fallback.isNotEmpty()) { eventDao.insertEvents(fallback) }
        fallback
      }
    events.map { it.toSerializable(tripTitle = trip?.title) }
  }
}
Kotlin-Snippet bei 12:44 in „Technical deep dive into Android MCP“
/**
 * Retrieves the itinerary events (with their eventId, title, type, and location) for a specific
 * trip. Note: getItinerary returns event summaries only; call getEventDetails(eventId) with a
 * specific event's ID to retrieve ticket prices, hotel nightly rates, baggage allowance, or
 * visitor tips.
 *
 * @param tripId Optional ID of the trip (e.g., '2026-1'). Defaults to the active trip if omitted.
 * @return A list of events in the itinerary.
 */
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
  return withContext(Dispatchers.IO) {
    val trip = resolveTrip(tripId = tripId)
    val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?: tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
    val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
...
Kotlin-Snippet bei 12:52 in „Technical deep dive into Android MCP“
/**
 * @param tripId Optional ID of the trip (e.g., '2026-1').
 *   Defaults to the active trip if omitted.
 * @return A list of events in the itinerary.
 */
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
  return withContext(Dispatchers.IO) {
    // resolveTrip defaults to the active trip if tripId == null
    val trip = resolveTrip(tripId = tripId)
Kotlin-Snippet bei 13:30 in „Technical deep dive into Android MCP“
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
  return withContext(Dispatchers.IO) {
    val trip = resolveTrip(tripId = tripId)
    val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?:
    tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
    val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
    val events =
      if (dbEvents.isNotEmpty()) { dbEvents } else {
        val fallback = DummyData.events.filter { it.tripId == targetTripId }
        ...
Kotlin-Snippet bei 13:40 in „Die App im Detail zu Android MCP“
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun send(
    message: Message
): Result {
  ...
  throw AppFunctionException(
    errorCode = AppFunctionException.ErrorCode.ERROR_INVALID_ARGUMENT,
    errorMessage = "To address has invalid syntax!"
  )
}
XML-Snippet bei 14:15 in „Technical deep dive into Android MCP“
<AppFunctionAppMetadata
xmlns:appfn="http://schemas.android.com/apk/androidx.appfunctions"

  appfn:description="This app manages trips and travel expenses.

  Operational Patterns:
    - Use 'getAllTrips' to find the correct 'tripId' before
      adding expenses or managing trip details.

  Constraints:
    - Expenses must be associated with a valid trip ID."

  appfn:displayDescription="@string/user_visible_description" />
Kotlin-Snippet bei 15:00 in „Technical deep dive into Android MCP“
viewModelScope.launch {
    val newState =
        if (enabled) {
            AppFunctionManager.APP_FUNCTION_STATE_ENABLED
        } else {
            AppFunctionManager.APP_FUNCTION_STATE_DISABLED
        }
    appFunctionManager?.setAppFunctionEnabled(id, newState)
}
Kotlin-Snippet bei 15:12 in „Technical deep dive into Android MCP“
@AppFunctionSignature(
    scope = AppFunctionMetadata.SCOPE_ACTIVITY,
    appFunctionXmlFileName = "cart_functions"
)
fun interface AddCurrentItemToCart {
    /** Adds the item currently shown on screen to the cart. Returns the new cart size. */
    suspend fun addToCart(quantity: Int): Int
}
Kotlin-Snippet bei 15:22 in „Technical deep dive into Android MCP“
val addToCartAppFunctionAdapter =
    appFunctionManager.getHandleAppFunctionRequestAdapter<AddCurrentItemToCart>()

coroutineScope.launch {
    appFunctionManager.handleAppFunction(
        addToCartAppFunctionAdapter.adapt { quantity ->
            val newCartSize = cart.addItem(quantity)
            newCartSize
        }
    )
}
Kotlin-Snippet bei 15:50 in „Technical deep dive into Android MCP“
@AppFunctionDeclaration(isDescribedByKDoc = true)
@AppFunctionAccessLevel(
    level = AppFunctionMetadata.ACCESS_LEVEL_SELF,
    isCompatEnforcementEnabled = true,
)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
  return withContext(Dispatchers.IO) {
  ...
Shell-Snippet bei 17:05 in „Technical deep dive into Android MCP“
android skills add appfunctions
Kotlin-Snippet bei 17:54 in „Technical deep dive into Android MCP“
/**
 * Adds an expense to a trip.
 *
 * @param tripId ID of the trip. If null, the active trip is used.
 * @param category Expense category. If null, it's inferred from the title.
 */
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun addExpense(
    title: String,
    amount: Double,
    currency: String,
    tripId: String? = null,
    category: String? = null,
): ExpenseSerializable = withContext(Dispatchers.IO) {
    if (amount <= 0.0) {
        throw AppFunctionInvalidArgumentException("amount must be greater than zero")
    }
    val trip = resolveTrip(tripId)
        ?: throw AppFunctionElementNotFoundException("No trip with ID $tripId")
    val expense = Expense(trip.id, title, amount, currency,
        category = category ?: inferExpenseCategory(title))
    expenseDao.insertExpense(expense)
    expense.toSerializable(remainingBudgetUsd(trip.id))
}