ANTIGRAVITY LABJP
Articles/App Development
App Development/2026-05-14Advanced

The Day Android Graduated from Being an OS — Connecting My Wallpaper App to Gemini Intelligence via AppFunctions API

An implementation record of connecting Android apps to Gemini Intelligence via AppFunctions API, tested on a 50M-download wallpaper app portfolio. Covers design decisions, Kotlin implementation, and Antigravity workflow.

Android27AppFunctionsGemini IntelligenceFirebase AI LogicAntigravity338indie-dev19

On May 12, 2026, I was working in Antigravity when an email arrived from the Android team. Subject line: "The Android Show: Developer's Cut." I almost skipped it, but the opening line caught my attention.

"Android is transitioning from an operating system to an intelligence system."

OS to Intelligence System. I sat with that sentence for a moment. I've been building Android apps since 2014, and throughout that time, Android always meant "something the user directly controls." That premise just changed.

With a portfolio of wallpaper apps that have crossed 50 million total downloads, I couldn't treat this as something to deal with later. This is a record written the day after I actually touched the AppFunctions API.

What Android Transitioning to an Intelligence System Actually Means

At the core of this announcement is something Google is calling Gemini Intelligence — a new AI integration layer designed to let Android devices anticipate user needs so apps can deliver experiences at exactly the right moment.

Two integration paths are provided for developers:

Path 1: Task Automation (zero code changes) Gemini navigates the existing app UI on the user's behalf to complete tasks. Examples include building a grocery order or requesting a ride. No changes to your app code required.

Path 2: AppFunctions API (developer-controlled) Developers explicitly define app features as "tools" with natural language descriptions, publish them to the OS, and Gemini discovers and calls them at the right time.

What struck me was that Google's official blog used the phrase "in an MCP-like fashion" to describe AppFunctions. They're applying the Model Context Protocol concept — familiar to anyone working with AI coding tools — to the Android platform itself.

Two Integration Paths and the Indie Developer Reality

I found it more useful to ask "which path fits my app's values?" rather than "which path should I choose?"

Task Automation is the right fit when:

  • Your app already uses standard Navigation Component with clear screen hierarchies
  • Users repeat predictable patterns (e.g., always picks a wallpaper from the same category every morning)
  • You need quick coverage without engineering investment

AppFunctions API is the right fit when:

  • Your app has complex business logic that can't be replicated through screen navigation alone
  • You want to connect abstract inputs ("I'm feeling calm today") to specific in-app actions
  • You're making a long-term investment in AI ecosystem integration

For a wallpaper app, "have Gemini pick a wallpaper based on my mood" runs into Task Automation's ceiling quickly. Turning an abstract input like "calm" into a "filter by category" + "save to favorites" workflow requires exposing deliberate business logic. AppFunctions is the right tool here.

How AppFunctions Work — The "Named Tool" Model

AppFunctions are built around publishing app capabilities as tools with names and natural language descriptions. The OS discovers these tools and makes them available to agents like Gemini.

The mental model maps closely to defining tools in an MCP server: function name, parameters with types and descriptions, return values, and a human-readable explanation of what the tool does and when to use it.

KakaoTalk is mentioned in the developer blog as an Early Access Program participant, having published two AppFunctions: "send messages" and "initiate voice calls." Google also noted that 25 apps' use cases are already running locally on device. These early examples suggest a clear design direction — start with high-frequency, high-value actions.

AppFunction structure:
  - id: unique identifier scoped to your package name
  - description: natural language explanation Gemini uses to decide when to call this
  - parameters: typed definitions with examples
  - implementation: a Kotlin suspend fun

Of these, the quality of description and parameters has the most direct impact on how well Gemini uses your function.

Minimum AppFunctions Implementation in Kotlin

Based on the EAP alpha documentation and Android's established annotation-based API patterns (App Actions, Shortcuts), here's a working minimum implementation for a wallpaper app.

First, add the dependency to build.gradle.kts:

// build.gradle.kts (app module)
dependencies {
    implementation("androidx.appfunctions:appfunctions:1.0.0-alpha01")
    implementation("com.google.firebase:firebase-ai:25.0.0")
    kapt("androidx.appfunctions:appfunctions-compiler:1.0.0-alpha01")
}

Define the AppFunction. For a wallpaper app, we expose "select and set a wallpaper by mood":

// WallpaperAppFunctions.kt
import androidx.appfunctions.AppFunction
import androidx.appfunctions.AppFunctionContext
import androidx.appfunctions.AppFunctionResult
 
@AppFunction(
    id = "com.example.wallpaper.SET_WALLPAPER_BY_MOOD",
    description = "Set device wallpaper based on user's current mood or preference. " +
        "Supports moods like 'calm', 'energetic', 'nature', 'minimal', and 'seasonal'. " +
        "Use this when the user wants to change their wallpaper based on how they feel.",
    parameters = [
        AppFunction.Parameter(
            name = "mood",
            description = "The mood or theme for wallpaper selection. " +
                "Examples: calm, nature, minimal, energetic, space, warm, seasonal",
            type = String::class
        ),
        AppFunction.Parameter(
            name = "setImmediately",
            description = "Whether to apply the wallpaper right away (true) " +
                "or open a preview screen first (false). Defaults to false.",
            type = Boolean::class,
            required = false
        )
    ]
)
class SetWallpaperByMoodFunction(
    private val wallpaperRepository: WallpaperRepository,
    private val wallpaperManager: WallpaperManager
) : AppFunction {
 
    override suspend fun execute(
        context: AppFunctionContext,
        parameters: Map<String, Any?>
    ): AppFunctionResult {
        val mood = parameters["mood"] as? String
            ?: return AppFunctionResult.error("mood parameter is required")
        val setImmediately = parameters["setImmediately"] as? Boolean ?: false
 
        val wallpaper = wallpaperRepository.findByMood(mood)
            ?: return AppFunctionResult.error("No wallpaper found matching mood: $mood")
 
        return if (setImmediately) {
            wallpaperManager.setWallpaper(wallpaper.bitmap)
            AppFunctionResult.success(
                message = "Wallpaper set to \"${wallpaper.title}\" ($mood theme)",
                data = mapOf("wallpaperId" to wallpaper.id, "title" to wallpaper.title)
            )
        } else {
            AppFunctionResult.deepLink(
                uri = "wallpaperapp://preview/${wallpaper.id}",
                message = "Opening preview for \"${wallpaper.title}\""
            )
        }
    }
}

The most critical detail here is the description field. The phrase "Use this when the user wants to change their wallpaper based on how they feel" explicitly tells Gemini when to invoke this function. "Supports moods like 'calm', 'energetic'..." gives Gemini concrete vocabulary it can match against user intent. Writing this is the same exercise as writing good tool descriptions for an MCP server.

Register the AppFunction in AndroidManifest.xml:

<!-- AndroidManifest.xml -->
<application>
    <meta-data
        android:name="androidx.appfunctions.APP_FUNCTIONS"
        android:resource="@xml/app_functions" />
</application>
<!-- res/xml/app_functions.xml -->
<app-functions>
    <app-function
        android:id="com.example.wallpaper.SET_WALLPAPER_BY_MOOD"
        android:enabled="true"
        android:allowedCallers="com.google.android.apps.assistant" />
</app-functions>

The allowedCallers restriction ensures only the Gemini assistant can invoke this function — recommended during the EAP phase for security reasons.

Firebase AI Logic × Gemini 2.5 Pro for In-App AI Features

Separate from having Gemini operate your app via AppFunctions, you can also use Gemini inside your app to enhance the user experience. Firebase AI Logic makes this straightforward.

For a wallpaper app, this enables natural language interactions like "what time of day works best with this wallpaper?" or "suggest a category that matches today's weather."

// AIWallpaperAdvisor.kt
import com.google.firebase.ai.FirebaseAI
import com.google.firebase.ai.GenerativeModel
import com.google.firebase.ai.type.GenerationConfig
import com.google.firebase.ai.type.content
 
class AIWallpaperAdvisor {
 
    private val model: GenerativeModel by lazy {
        FirebaseAI.getInstance().generativeModel(
            modelName = "gemini-2.5-pro",
            generationConfig = GenerationConfig.builder()
                .maxOutputTokens(512)
                .temperature(0.4f)  // Low temperature for consistent utility-app suggestions
                .build(),
            systemInstruction = content {
                text(
                    """
                    You are an AI advisor for a wallpaper app.
                    Based on the user's mood, time of day, and weather,
                    recommend a wallpaper category and explain why in one sentence.
                    Always respond in the user's language.
                    Available categories: nature, minimal, space, warm, cool, abstract, city, art, seasonal
                    Format: category_name|reason
                    """.trimIndent()
                )
            }
        )
    }
 
    suspend fun recommendCategory(
        mood: String,
        timeOfDay: String,
        weather: String = "unknown"
    ): RecommendationResult {
        return try {
            val prompt = "Mood: $mood | Time: $timeOfDay | Weather: $weather"
            val response = model.generateContent(content { text(prompt) })
            val text = response.text?.trim() ?: return RecommendationResult.Fallback
 
            val parts = text.split("|")
            if (parts.size >= 2) {
                RecommendationResult.Success(
                    category = parts[0].trim(),
                    reason = parts[1].trim()
                )
            } else {
                RecommendationResult.Fallback
            }
        } catch (e: Exception) {
            RecommendationResult.Fallback
        }
    }
}
 
sealed class RecommendationResult {
    data class Success(val category: String, val reason: String) : RecommendationResult()
    object Fallback : RecommendationResult()
}

One deliberate choice worth explaining: temperature = 0.4f. For a utility app like this, you want stable, contextually appropriate suggestions — not creative variety. Users who see different category recommendations each time they open the app at the same time of day will lose trust in the feature. Lower temperature means fewer surprises, which is the right call for utility contexts.

The systemInstruction also specifies "one sentence" as the expected response length. AI output that overflows your UI layout and gets truncated creates a poor experience regardless of response quality. Constraining length in the instruction saves defensive code in your UI layer.

Using Antigravity to Accelerate Android Intelligence System Integration

AppFunctions API is still in alpha, which means thin documentation and rapid change. This is exactly when having well-configured AGENTS.md in Antigravity pays off.

# AGENTS.md (Android Intelligence System Integration)
 
## Project goal
Add Gemini Intelligence support to a wallpaper app (50M+ downloads).
Use AppFunctions API to let users control wallpaper selection via natural language.
 
## Tech stack
- Android 17 / Kotlin 2.x / Jetpack Compose
- AppFunctions API alpha (androidx.appfunctions:1.0.0-alpha01)
- Firebase AI Logic SDK (Gemini 2.5 Pro)
- EAP-stage API — reference docs at goo.gle/eap-af
 
## AppFunctions design principles
- Write description and parameter explanations in English (better Gemini accuracy)
- Always include concrete value examples in parameter descriptions
- Error messages and success.message should be in user-facing language
- Default setImmediately to false (wallpaper misapplications are high-friction)
- Use deepLink only for preview flows; execute confirmable actions directly
 
## Firebase AI Logic principles
- Use temperature 0.3–0.5 for utility app suggestions
- Always constrain response length and format in systemInstruction
- Sanitize and trim AI responses before displaying in UI
 
## Known constraints (as of 2026-05-14)
- AppFunctions confirmed working on Android 17+
- allowedCallers currently limited to com.google.android.apps.assistant
- EAP registration: goo.gle/eap-af

With this AGENTS.md in place, when you ask Antigravity to add an AppFunction, it generates code with English descriptions, setImmediately defaulting to false, and user-facing error messages — matching your design decisions without requiring repeated explanations.

One particularly effective pattern for alpha APIs: paste EAP sample code directly into AGENTS.md. Antigravity doesn't have training data for APIs in private preview, so giving it the canonical implementation patterns in the AGENTS.md context leads to far more accurate code generation.

Design Decisions from My 50M-Download App Portfolio

After ten years of building apps independently, I know that "update everything immediately" when a new API ships is not how you stay sustainable. Here's how I prioritized Android Intelligence System adoption:

High priority: Core wallpaper app Used daily, so the value of Gemini Intelligence integration is high. "Automatically change wallpaper in the morning" maps to Task Automation. "Select based on mood and weather" needs AppFunctions. Plan: validate Task Automation first, add AppFunctions in three months.

Medium priority: Healing sound app Natural language control ("play work mode music") has clear value. Cross-app synergy with wallpapers (Visual + Sound) is an interesting future direction. Registered for EAP to get more information.

Low priority: Manifestation-category app For these apps, the user's conscious, voluntary action is the product. Automating that interaction might undermine the core value. Watching how the platform evolves before deciding.

What matters as much as the priority ranking is being able to articulate why something is low priority. My grandfather was a temple carpenter who made deliberate decisions about where to use machinery and where to work by hand. He wasn't being stubborn — he was protecting the quality that made the craft meaningful.

I carry that same instinct into app development: being thoughtful about where AI intervention adds value versus where the human act of choosing is the point.

Three Actions to Take Now

Google I/O 2026 is next week (May 19–20). More detailed developer information will come from there. Here's what makes sense to do before that:

1. Test Task Automation behavior this week No code changes needed. If you have a Pixel or Galaxy running the Android 17 preview, try having Gemini navigate your app. Understanding what works out of the box — and what doesn't — tells you exactly what gap AppFunctions needs to fill.

2. Register for the AppFunctions EAP this week The registration link is goo.gle/eap-af. EAP approval gives you access to full API documentation and developer support. Approval may take a few days, so worth submitting now.

3. Prepare your AGENTS.md for post-I/O integration After Google I/O 2026, the AppFunctions API reference should become more complete. Update your AGENTS.md with that information as soon as it lands. Treating AGENTS.md as a living document that tracks an evolving API is the most effective workflow I've found for building on fast-moving platforms with Antigravity.

Android Intelligence System adoption isn't a single milestone — it's something that matures alongside the platform. I'm looking forward to seeing what next week's I/O announcements reveal.

Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

App Dev2026-07-14
Protecting Screenshot Tests for AdMob Screens from Ad Nondeterminism
Screens with ads turn red on every screenshot run, and eventually nobody reviews the diffs. Here is a design that seals off AdMob banner nondeterminism and leaves only real layout breaks in your checks, with Compose code and Antigravity-driven diff triage.
App Dev2026-07-06
Building a Live Wallpaper That Doesn't Drain the Battery — Designing the WallpaperService Render Loop Around a Power Budget
Live wallpapers quietly burn battery because they keep drawing when nothing is visible. Here is the WallpaperService.Engine design I used to cut power draw by roughly 60% in a real wallpaper app, built around visibility, idle, and thermal gates.
App Dev2026-07-03
When Universal Links Break Silently — Catching Association Drift with an Agent-Run Verification Gate
Universal Links and App Links fall back to the browser with no error when your association files or entitlements drift apart. Here is a design that generates the files from one source of truth and hands weekly and pre-release checks to an agent.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →