diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json
new file mode 100644
index 0000000..c2d27f9
--- /dev/null
+++ b/.agents/plugins/marketplace.json
@@ -0,0 +1,21 @@
+{
+ "name": "android-skills",
+ "interface": {
+ "displayName": "Android Skills"
+ },
+ "plugins": [
+ {
+ "name": "android-skills",
+ "source": {
+ "source": "url",
+ "url": "https://github.com/android/skills",
+ "ref": "main"
+ },
+ "policy": {
+ "installation": "AVAILABLE",
+ "authentication": "ON_INSTALL"
+ },
+ "category": "Developer Tools"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
new file mode 100644
index 0000000..5db479d
--- /dev/null
+++ b/.claude-plugin/marketplace.json
@@ -0,0 +1,20 @@
+{
+ "name": "android-skills",
+ "description": "Google's official Android skills for agentic Android development.",
+ "owner": {
+ "name": "Google / Android"
+ },
+ "plugins": [
+ {
+ "name": "android-skills",
+ "description": "Android skills are Google's official dedicated repository of AI-optimized, modular instructions and resources, to help LLMs better understand and execute specific patterns that follow the best practices and guidance on Android development. Android skills follow the open-standard agent skills - markdown files (SKILL.md) that provide a technical specification of a task, and ground LLMs with information on specialized domains and workflows.",
+ "author": {
+ "name": "Google / Android"
+ },
+ "category": "development",
+ "source": "./",
+ "homepage": "https://github.com/android/skills",
+ "license": "Apache-2.0"
+ }
+ ]
+}
diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json
new file mode 100644
index 0000000..1dca78b
--- /dev/null
+++ b/.claude-plugin/plugin.json
@@ -0,0 +1,51 @@
+{
+ "name": "android-skills",
+ "version": "1.0.15",
+ "displayName": "Android Skills",
+ "description": "Android skills are Google's official dedicated repository of AI-optimized, modular instructions and resources, to help LLMs better understand and execute specific patterns that follow the best practices and guidance on Android development. Android skills follow the open-standard agent skills - markdown files (SKILL.md) that provide a technical specification of a task, and ground LLMs with information on specialized domains and workflows.",
+ "author": {
+ "name": "Google / Android",
+ "url": "https://github.com/android"
+ },
+ "homepage": "https://github.com/android/skills",
+ "repository": "https://github.com/android/skills",
+ "license": "Apache-2.0",
+ "keywords": [
+ "android",
+ "kotlin",
+ "jetpack-compose",
+ "skills"
+ ],
+ "skills": [
+ "./build-system/agp/agp-9-upgrade",
+ "./camera/camerax",
+ "./cars/android-for-cars-parked-apps",
+ "./device-ai/appfunctions",
+ "./device-ai/litert/litert-runtime",
+ "./device-ai/ml-kit-genai-prompt-api",
+ "./devtools/android-cli",
+ "./identity/restore-credentials",
+ "./identity/verified-email",
+ "./jetpack-compose/adaptive",
+ "./jetpack-compose/jetpack-compose-performance",
+ "./jetpack-compose/migration/migrate-xml-views-to-jetpack-compose",
+ "./jetpack-compose/theming/styles",
+ "./media/media3-cast-integration",
+ "./navigation/navigation-3",
+ "./navigation/navigation-event",
+ "./performance/r8-analyzer",
+ "./play/engage-sdk-integration",
+ "./play/play-billing-library-version-upgrade",
+ "./play/play-policy-insights",
+ "./profilers/android-profiler",
+ "./security/android-intent-security",
+ "./security/android-permissions-security",
+ "./system/edge-to-edge",
+ "./system/glance-canonical-layouts",
+ "./testing/testing-setup",
+ "./tv/leanback-to-compose-tv-migration",
+ "./wear/wear-compose-m3",
+ "./wear/wear-widgets",
+ "./xr/display-glasses-with-jetpack-compose-glimmer"
+ ]
+}
diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json
new file mode 100644
index 0000000..1e8ee11
--- /dev/null
+++ b/.codex-plugin/plugin.json
@@ -0,0 +1,184 @@
+{
+ "name": "android-skills",
+ "version": "1.0.15",
+ "description": "Android skills are Google's official dedicated repository of AI-optimized, modular instructions and resources, to help LLMs better understand and execute specific patterns that follow the best practices and guidance on Android development. Android skills follow the open-standard agent skills - markdown files (SKILL.md) that provide a technical specification of a task, and ground LLMs with information on specialized domains and workflows.",
+ "author": {
+ "name": "Google / Android",
+ "url": "https://github.com/android"
+ },
+ "repository": "https://github.com/android/skills",
+ "license": "Apache-2.0",
+ "keywords": [
+ "android",
+ "kotlin",
+ "jetpack-compose",
+ "skills"
+ ],
+ "skills": [
+ {
+ "source": {
+ "path": "./build-system/agp/agp-9-upgrade"
+ }
+ },
+ {
+ "source": {
+ "path": "./camera/camerax"
+ }
+ },
+ {
+ "source": {
+ "path": "./cars/android-for-cars-parked-apps"
+ }
+ },
+ {
+ "source": {
+ "path": "./device-ai/appfunctions"
+ }
+ },
+ {
+ "source": {
+ "path": "./device-ai/litert/litert-runtime"
+ }
+ },
+ {
+ "source": {
+ "path": "./device-ai/ml-kit-genai-prompt-api"
+ }
+ },
+ {
+ "source": {
+ "path": "./devtools/android-cli"
+ }
+ },
+ {
+ "source": {
+ "path": "./identity/restore-credentials"
+ }
+ },
+ {
+ "source": {
+ "path": "./identity/verified-email"
+ }
+ },
+ {
+ "source": {
+ "path": "./jetpack-compose/adaptive"
+ }
+ },
+ {
+ "source": {
+ "path": "./jetpack-compose/jetpack-compose-performance"
+ }
+ },
+ {
+ "source": {
+ "path": "./jetpack-compose/migration/migrate-xml-views-to-jetpack-compose"
+ }
+ },
+ {
+ "source": {
+ "path": "./jetpack-compose/theming/styles"
+ }
+ },
+ {
+ "source": {
+ "path": "./media/media3-cast-integration"
+ }
+ },
+ {
+ "source": {
+ "path": "./navigation/navigation-3"
+ }
+ },
+ {
+ "source": {
+ "path": "./navigation/navigation-event"
+ }
+ },
+ {
+ "source": {
+ "path": "./performance/r8-analyzer"
+ }
+ },
+ {
+ "source": {
+ "path": "./play/engage-sdk-integration"
+ }
+ },
+ {
+ "source": {
+ "path": "./play/play-billing-library-version-upgrade"
+ }
+ },
+ {
+ "source": {
+ "path": "./play/play-policy-insights"
+ }
+ },
+ {
+ "source": {
+ "path": "./profilers/android-profiler"
+ }
+ },
+ {
+ "source": {
+ "path": "./security/android-intent-security"
+ }
+ },
+ {
+ "source": {
+ "path": "./security/android-permissions-security"
+ }
+ },
+ {
+ "source": {
+ "path": "./system/edge-to-edge"
+ }
+ },
+ {
+ "source": {
+ "path": "./system/glance-canonical-layouts"
+ }
+ },
+ {
+ "source": {
+ "path": "./testing/testing-setup"
+ }
+ },
+ {
+ "source": {
+ "path": "./tv/leanback-to-compose-tv-migration"
+ }
+ },
+ {
+ "source": {
+ "path": "./wear/wear-compose-m3"
+ }
+ },
+ {
+ "source": {
+ "path": "./wear/wear-widgets"
+ }
+ },
+ {
+ "source": {
+ "path": "./xr/display-glasses-with-jetpack-compose-glimmer"
+ }
+ }
+ ],
+ "interface": {
+ "displayName": "Android Skills",
+ "shortDescription": "AI-optimized Android development skills from Google.",
+ "longDescription": "Android skills are Google's official dedicated repository of AI-optimized, modular instructions and resources, to help LLMs better understand and execute specific patterns that follow the best practices and guidance on Android development. Android skills follow the open-standard agent skills - markdown files (SKILL.md) that provide a technical specification of a task, and ground LLMs with information on specialized domains and workflows.",
+ "developerName": "Google / Android",
+ "category": "Developer Tools",
+ "capabilities": [
+ "Skills"
+ ],
+ "websiteURL": "https://github.com/android/skills",
+ "defaultPrompt": [
+ "Help me upgrade my app to AGP 9.",
+ "Migrate this Camera1 code to CameraX."
+ ]
+ }
+}
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..af0172a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,25 @@
+---
+name: Bug Report
+about: Report an issue with an existing skill.
+title: '[Bug] '
+labels: bug
+assignees: ''
+---
+
+### Affected Skill
+
+
+### Description
+
+
+### Expected Behavior
+
+
+### Actual Behavior
+
+
+### Steps to Reproduce
+
+
+### Suggested Fix
+
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..e30f9e7
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,19 @@
+---
+name: Feature Request
+about: Suggest a new skill to fill a gap in the LLM's knowledge.
+title: '[Feature] '
+labels: enhancement
+assignees: ''
+---
+
+> [!NOTE]
+> Feature requests are accepted only if the proposed skill fills a verified gap in state-of-the-art LLMs. To assess this, please test your use case against both a fast model (e.g., Gemini 3 Flash) and a reasoning/thinking model (e.g., Gemini 3.1 Pro).
+
+### What is the gap in the LLM's knowledge?
+
+
+### Proposed Skill
+
+
+### Additional Context
+
diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml
index 92c9029..1bcf118 100644
--- a/.github/workflows/create-release.yml
+++ b/.github/workflows/create-release.yml
@@ -16,9 +16,50 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout main
- uses: actions/checkout@v4
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: main
+ token: ${{ secrets.ADR_GITHUB_BOT_PAT || secrets.GITHUB_TOKEN }}
+
+ - name: Update Plugin Versions
+ env:
+ TAG_NAME: ${{ inputs.tag_name }}
+ run: |
+ VERSION="${TAG_NAME#v}"
+ echo "Updating plugin manifest versions to $VERSION"
+
+ VERSION="$VERSION" python3 -c "
+ import json
+ import os
+ import pathlib
+
+ version = os.environ['VERSION']
+
+ # Both manifests carry the version. Claude Code pins installs to the
+ # version in .claude-plugin/plugin.json, so leaving it stale means
+ # users never receive the new release.
+ for path in ('.claude-plugin/plugin.json', '.codex-plugin/plugin.json'):
+ p = pathlib.Path(path)
+ if not p.exists():
+ raise FileNotFoundError(f'Required file {p} does not exist.')
+ with open(p, 'r') as f:
+ data = json.load(f)
+ data['version'] = version
+ with open(p, 'w') as f:
+ json.dump(data, f, indent=2)
+ f.write('\n')
+ print(f'Set {p} version to {version}')
+ "
+
+ - name: Commit and push updated version
+ env:
+ TAG_NAME: ${{ inputs.tag_name }}
+ run: |
+ git config user.name "android-devrel-github-bot"
+ git config user.email "android-devrel-github-bot@users.noreply.github.com"
+ git add .claude-plugin/plugin.json .codex-plugin/plugin.json
+ git commit -m "Bump plugin version to $TAG_NAME" || echo "No version change to commit"
+ git push origin main
- name: Create Zip Archive
run: |
@@ -26,6 +67,10 @@ jobs:
- name: Create Release
env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.ADR_GITHUB_BOT_PAT || secrets.GITHUB_TOKEN }}
+ TAG_NAME: ${{ inputs.tag_name }}
run: |
- gh release create ${{ inputs.tag_name }} android-skills.zip --title "Release ${{ inputs.tag_name }}" --notes "Manual release of main branch contents."
+ gh release create "$TAG_NAME" android-skills.zip \
+ --target main \
+ --title "Release $TAG_NAME" \
+ --notes "Release $TAG_NAME of Android Skills."
diff --git a/.github/workflows/update-skills.yml b/.github/workflows/update-skills.yml
index 08bf523..6eb079c 100644
--- a/.github/workflows/update-skills.yml
+++ b/.github/workflows/update-skills.yml
@@ -8,13 +8,14 @@ on:
permissions:
contents: write
+ pull-requests: write
jobs:
update-skills:
runs-on: ubuntu-latest
steps:
- name: Checkout main
- uses: actions/checkout@v4
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: main
token: ${{ secrets.ADR_GITHUB_BOT_PAT }}
@@ -59,11 +60,87 @@ jobs:
rm -rf temp_skills
echo "Successfully copied directories from github-skills branch."
- - name: Commit and push changes
+ - name: Update Skill Lists in Plugin Files
run: |
+ python3 -c "
+ import json
+ import pathlib
+
+ # Discover all skills with SKILL.md
+ skill_paths = []
+ for path in pathlib.Path('.').glob('**/SKILL.md'):
+ if any(p.startswith('.') for p in path.parts[:-1]):
+ continue
+ skill_paths.append(f'./{path.parent.as_posix()}')
+
+ skill_paths = sorted(list(set(skill_paths)))
+
+ # 1. Update .claude-plugin/plugin.json in-place.
+ # The skill list belongs in the plugin MANIFEST, never in the
+ # marketplace entry. Component fields such as 'skills' on the entry
+ # are rejected by Anthropic's plugin directory with
+ # MARKETPLACE_ENTRY_INLINE_MANIFEST.
+ claude_manifest = pathlib.Path('.claude-plugin/plugin.json')
+ if not claude_manifest.exists():
+ raise FileNotFoundError(f'Required file {claude_manifest} does not exist. Ensure you are running on the correct branch.')
+
+ with open(claude_manifest, 'r') as f:
+ data = json.load(f)
+ data['skills'] = skill_paths
+ with open(claude_manifest, 'w') as f:
+ json.dump(data, f, indent=2)
+ f.write('\n')
+
+ # 2. Keep .claude-plugin/marketplace.json a pure catalogue.
+ # With a plugin.json present, strict:false plus any component field
+ # makes the plugin fail to load with 'conflicting manifests'.
+ claude_marketplace = pathlib.Path('.claude-plugin/marketplace.json')
+ if not claude_marketplace.exists():
+ raise FileNotFoundError(f'Required file {claude_marketplace} does not exist. Ensure you are running on the correct branch.')
+
+ with open(claude_marketplace, 'r') as f:
+ data = json.load(f)
+ if 'plugins' in data and len(data['plugins']) > 0:
+ for stale in ('skills', 'strict', 'commands', 'agents', 'hooks', 'outputStyles', 'themes'):
+ data['plugins'][0].pop(stale, None)
+ with open(claude_marketplace, 'w') as f:
+ json.dump(data, f, indent=2)
+ f.write('\n')
+
+ # 3. Update .codex-plugin/plugin.json in-place
+ codex_file = pathlib.Path('.codex-plugin/plugin.json')
+ if not codex_file.exists():
+ raise FileNotFoundError(f'Required file {codex_file} does not exist. Ensure you are running on the correct branch.')
+
+ with open(codex_file, 'r') as f:
+ data = json.load(f)
+ data['skills'] = [{'source': {'path': p}} for p in skill_paths]
+ with open(codex_file, 'w') as f:
+ json.dump(data, f, indent=2)
+ f.write('\n')
+ "
+
+ - name: Commit and push to PR branch
+ run: |
+ BRANCH="update-skills/$(date +'%Y-%m-%d-%H%M')"
+
git config user.name "android-devrel-github-bot"
git config user.email "android-devrel-github-bot@users.noreply.github.com"
+ git checkout -b "$BRANCH"
git add -A
- # The '|| true' ensures the workflow doesn't fail if there's nothing new
- git commit -m "Updates skills ($(date +'%Y-%m-%d %H:%M'))" || echo "No changes to commit"
- git push origin main
+
+ # The workflow doesn't fail if there's nothing new to commit
+ if git diff --cached --quiet; then
+ echo "No changes to commit — skipping PR creation."
+ exit 0
+ fi
+
+ git commit -m "Updates skills ($(date +'%Y-%m-%d %H:%M'))"
+ git push origin "$BRANCH"
+ gh pr create \
+ --title "Update skills ($(date +'%Y-%m-%d %H:%M'))" \
+ --body "Automated skill update from the \`Update Skills\` workflow. Please review the changes before merging." \
+ --base main \
+ --head "$BRANCH"
+ env:
+ GH_TOKEN: ${{ secrets.ADR_GITHUB_BOT_PAT }}
\ No newline at end of file
diff --git a/README.md b/README.md
index effd96e..dae981c 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,74 @@
## Android skills
-Android skills are a dedicated repository of AI-optimised, modular instructions and resources, to
-help LLMs better understand and execute specific patterns that follow the best practices and
-guidance on Android development from [developer.android.com](developer.android.com).
+**Android skills** are a dedicated repository of **AI-optimized, modular instructions** and
+resources, to help agents better understand and execute specific patterns that follow the best
+practices and guidance on Android development
+from [developer.android.com](https://developer.android.com).
Android skills follow the [open-standard agent skills](https://agentskills.io/home) - markdown
-files (SKILL.md) that provide a technical specification of a task, and ground LLMs with information
-on specialized domains and workflows.
+files (SKILL.md) that provide a technical specification of a task, and **ground LLMs** with
+information on specialized domains and workflows.
-To use Android skills, check
-your agent's documentation.
+Our Android skill development focuses on **use cases and workflows where evaluations show LLMs
+underperform**.
+We aren't prioritizing well-established areas where LLMs are already proficient, such
+as basic Jetpack Compose best practices.
+
+To learn more, read the official documentation:
+
+- [Android skills](https://developer.android.com/tools/agents/android-skills)
+- [Android CLI](https://developer.android.com/tools/agents/android-cli)
+- [Android Studio](https://developer.android.com/studio/gemini/skills)
+
+### Install Android skills
+
+Use Android CLI to install a specific skill into the current directory:
+
+```
+android skills add r8-analyzer --project=.
+```
+
+Use Android CLI to install all Android skills to directories for all detected agents:
+
+```
+android skills add --all
+```
+
+If you don't have any existing agent directories and don't specify particular agents, the skills
+will be installed for Gemini and Antigravity at `~/.gemini/antigravity/skills`.
+
+**Options:**
+
+- `--all` - Add all Android skills. If omitted, only the `android-cli` skill will be installed.
+- `--agent` - A comma-separated list of agents to install the skill for. If omitted, the skill will be installed for all detected agents.
+- `--project` - Path to a project root in which to install the skills.
+
+
+To update all skills or specific ones, run:
+
+```
+android skills update --all
+android skills update wear-compose-m3
+```
+
+### Install as an agent plugin
+
+**Claude Code**:
+
+```
+/plugin install android-skills --marketplace android/skills
+```
+
+**Codex:**
+
+```
+codex plugin marketplace add android/skills
+codex plugin add android-skills@android-skills
+```
+
+### Activate skills
+
+Your agent automatically activates skills that are relevant to your task. To use a skill, prompt your agent to complete a task that is related to the skill, for example "Make my app UI edge-to-edge." The agent should automatically find and use the skill if it has been installed.
## Disclaimer
@@ -30,4 +89,4 @@ details.
## Review our community guidelines
This project follows
-[Google's Open Source Community Guidelines](https://opensource.google/conduct/).
\ No newline at end of file
+[Google's Open Source Community Guidelines](https://opensource.google/conduct/).
diff --git a/build/agp/agp-9-upgrade/SKILL.md b/build-system/agp/agp-9-upgrade/SKILL.md
similarity index 91%
rename from build/agp/agp-9-upgrade/SKILL.md
rename to build-system/agp/agp-9-upgrade/SKILL.md
index 6436b33..efb2f1a 100644
--- a/build/agp/agp-9-upgrade/SKILL.md
+++ b/build-system/agp/agp-9-upgrade/SKILL.md
@@ -1,10 +1,12 @@
---
name: agp-9-upgrade
description: Upgrades, or migrates, an Android project to use Android Gradle Plugin
- (AGP) version 9. Do not use this skill for migrating Kotlin Multiplatform (KMP) projects.
+ (AGP) version 9. Do not use this skill for migrating Kotlin Multiplatform (KMP)
+ projects.
license: Complete terms in LICENSE.txt
metadata:
author: Google LLC
+ last-updated: '2026-09-18'
keywords:
- Android Gradle Plugin 9
- AGP 9
@@ -27,8 +29,7 @@ Upgrade Assistant in Android Studio to update to the latest stable version of
AGP, and confirm when done. The user may also request that this requirement be
skipped; if this is the case, you should update the version of AGP to the latest
stable version as part of the AGP 9 migration. See the
-[AGP 9 migration guide](references/android/build/releases/agp-9-0-0-release-notes.md)
-for how to do this.
+[AGP 9 migration guide](references/android/build/releases/agp-9-0-0-release-notes.md) for how to do this.
Each version of AGP has its own set of compatibilities with other tools, such as
Gradle, JDK, and Kotlin. The release notes for each of these versions will
@@ -48,11 +49,11 @@ version 2.3.6 or higher.
If Hilt is used in the project, ensure it is on version 2.59.2 or higher.
-### Step 2: Migrate to built-in Kotlin.
+### Step 2: Migrate to built-in Kotlin
See [the guide](references/android/build/migrate-to-built-in-kotlin.md) for detailed information.
-### Step 3. Migrate to the new AGP DSL.
+### Step 3. Migrate to the new AGP DSL
See [the guide](references/android/build/releases/agp-9-0-0-release-notes.md) for detailed information.
@@ -62,8 +63,8 @@ that is compatible with AGP 9 and the new DSL.
### Step 4. Migrate kapt to KSP or legacy-kapt
If KSP (`com.google.devtools.ksp`) or kapt (`org.jetbrains.kotlin.kapt`) are
-used in the project, [KSP, kapt, and legacy-kapt](references/ksp-kapt.md)
-for detailed migration steps.
+used in the project, see [KSP, kapt, and legacy-kapt](references/ksp-kapt.md) for detailed migration
+steps.
### Step 5. BuildConfig
@@ -99,4 +100,4 @@ After migration, verify the following:
## Troubleshooting
Paparazzi v2.0.0-alpha04 and lower versions have issues with AGP 9. See
-[references/paparazzi-gradle-9.md](references/paparazzi-gradle-9.md) for details.
\ No newline at end of file
+[references/paparazzi-gradle-9.md](references/paparazzi-gradle-9.md) for details.
diff --git a/build/agp/agp-9-upgrade/references/android/build/migrate-to-built-in-kotlin.md b/build-system/agp/agp-9-upgrade/references/android/build/migrate-to-built-in-kotlin.md
similarity index 100%
rename from build/agp/agp-9-upgrade/references/android/build/migrate-to-built-in-kotlin.md
rename to build-system/agp/agp-9-upgrade/references/android/build/migrate-to-built-in-kotlin.md
diff --git a/build/agp/agp-9-upgrade/references/android/build/releases/agp-9-0-0-release-notes.md b/build-system/agp/agp-9-upgrade/references/android/build/releases/agp-9-0-0-release-notes.md
similarity index 78%
rename from build/agp/agp-9-upgrade/references/android/build/releases/agp-9-0-0-release-notes.md
rename to build-system/agp/agp-9-upgrade/references/android/build/releases/agp-9-0-0-release-notes.md
index b6a6e56..4e0aa6b 100644
--- a/build/agp/agp-9-upgrade/references/android/build/releases/agp-9-0-0-release-notes.md
+++ b/build-system/agp/agp-9-upgrade/references/android/build/releases/agp-9-0-0-release-notes.md
@@ -3,12 +3,17 @@
Android Gradle plugin 9.0 is a major release that brings API and behavior
changes.
-To update to Android Gradle plugin 9.0.1,
-use the [Android Gradle plugin Upgrade Assistant](https://developer.android.com/build/agp-upgrade-assistant).
+To update to Android Gradle plugin 9.0.1, use the
+[Android Gradle plugin Upgrade Assistant](https://developer.android.com/build/agp-upgrade-assistant). The AGP upgrade assistant helps
+preserve existing behaviors when upgrading your project whenever appropriate, so
+you can upgrade your project to use AGP 9.0 even if you're not ready to
+adopt all the new defaults in AGP 9.0.
-The AGP upgrade assistant helps preserve existing behaviors when upgrading your
-project whenever appropriate, allowing you to upgrade your project to use AGP
-9.0 even if you're not ready to adopt all the new defaults in AGP 9.0.
+There are also two agent skills available to make the upgrade process easier.
+For a non-KMP app, try the [AGP 9 upgrade skill](https://github.com/android/skills/tree/main/build-system/agp/agp-9-upgrade) from the
+Android skills repository. For a KMP app, try the [AGP 9 upgrade
+skill](https://github.com/Kotlin/kotlin-agent-skills/tree/main/skills/kotlin-tooling-agp9-migration) from JetBrains. For more information about using skills
+in Android Studio, see [Extend Agent Mode with skills](https://developer.android.com/studio/gemini/skills).
## Compatibility
@@ -22,6 +27,7 @@ Here is other compatibility info:
| SDK Build Tools | 36.0.0 | 36.0.0 | [Install](https://developer.android.com/studio/intro/update#sdk-manager) or [configure](https://developer.android.com/tools/releases/build-tools) SDK Build Tools. |
| NDK | N/A | 28.2.13676358 | [Install](https://developer.android.com/studio/projects/install-ndk#specific-version) or [configure](https://developer.android.com/studio/projects/install-ndk#apply-specific-version) a different version of the NDK. |
| JDK | 17 | 17 | To learn more, see [setting the JDK version](https://developer.android.com/studio/intro/studio-config#jdk). |
+| Kotlin Gradle plugin (KGP) | 2.2.10 | 2.2.10 | To learn more, see [Runtime dependency on Kotlin Gradle plugin](https://developer.android.com/build/releases/agp-9-0-0-release-notes#runtime-dependency-on-kotlin-gradle-plugin). |
@@ -99,6 +105,9 @@ your `gradle.properties` file:
android.newDsl=false
+Alternatively, for a more gradual migration, AGP 9.4 lets you opt out individual
+modules. To learn how, see [Variant API module opt-out](https://developer.android.com/build/releases/agp-9-4-0-release-notes#new-dsl-opt-out).
+
The previous classes are marked as deprecated in AGP 9.0. This means projects
that opt out of the `newDsl` flag will see deprecation warnings, including on
the `android` block itself.
@@ -541,11 +550,6 @@ options of D8 and R8.
### Android Gradle plugin 9.0.0
-| Fixed Issues ||
-|---|---|
-| **Android Gradle Plugin** | |---| | [Issue #171293712](https://issuetracker.google.com/issues/171293712) Feature Request: Inject ideal AGP version as a property | | [Issue #443976533](https://issuetracker.google.com/issues/443976533) Stabilize SingleArtifact.VERSION_CONTROL_INFO_FILE | | [Issue #223643506](https://issuetracker.google.com/issues/223643506) androidTest connectedCheck logcat output is broken | | [Issue #386221070](https://issuetracker.google.com/issues/386221070) Built-in Kotlin support in AGP should not synchronize with the Kotlin sourcesets | | [Issue #460094802](https://issuetracker.google.com/issues/460094802) missingDimensionStrategy prefers a flavor maching its own name even from an unrelated dimension | | [Issue #386221070](https://issuetracker.google.com/issues/386221070) Built-in Kotlin support in AGP should not synchronize with the Kotlin sourcesets | | [Issue #471410336](https://issuetracker.google.com/issues/471410336) AGP 9.0.0-rc01 doesn't resolve Kotlin libraries via kotlin() function | | [Issue #450851465](https://issuetracker.google.com/issues/450851465) Built-in-kotlin does not publish kotlin-stdlib dependency constraint in maven pom | | [Issue #420592288](https://issuetracker.google.com/issues/420592288) Add a test case for divergence between compileSdk and targetSdk | | [Issue #449153004](https://issuetracker.google.com/issues/449153004) empty resConfigs value leads to obscure aapt error | | [Issue #447375921](https://issuetracker.google.com/issues/447375921) Pure Java project have dependency on kotlin stdlib. | | [Issue #368600704](https://issuetracker.google.com/issues/368600704) Remove deprecated KotlinMultiplatformAndroidCompilationBuilder properties in AGP 9.0 | | [Issue #445209309](https://issuetracker.google.com/issues/445209309) \`com.android.tools.build:gradle:9.0.0-alpha05\` should have an api dependency on KGP and gradle-api | | [Issue #452645779](https://issuetracker.google.com/issues/452645779) Rename com.android.experimental.built-in-kotlin Gradle plugin | | [Issue #446220448](https://issuetracker.google.com/issues/446220448) AGP9: \`variant.sources.kotlin!!.addGeneratedSourceDirectory()\` is not working | | [Issue #448450771](https://issuetracker.google.com/issues/448450771) Aar metadata checks on compile Sdk uses the old DSL | | [Issue #441523448](https://issuetracker.google.com/issues/441523448) Remove deprecated \`com.android.build.api.dsl.ManagedDevices.devices\` property | | [Issue #386221070](https://issuetracker.google.com/issues/386221070) Built-in Kotlin support in AGP should not synchronize with the Kotlin sourcesets | | [Issue #433758231](https://issuetracker.google.com/issues/433758231) Fail android library publishing if consumer keep file contains -dontobfuscate | | [Issue #241955408](https://issuetracker.google.com/issues/241955408) No Options to Print Mapping for Optimized Resources | | [Issue #436595826](https://issuetracker.google.com/issues/436595826) Make it an error to call finalizeDsl after this phase has been passed | | [Issue #457089670](https://issuetracker.google.com/issues/457089670) AGP initializes jetifier config even when jetifier is disabled | | [Issue #452246814](https://issuetracker.google.com/issues/452246814) built in kotlin does not add kotlinStdlib as a compile time dependency when \`kotlin.stdlib.default.dependency\` is true to module and pom files | | [Issue #442250902](https://issuetracker.google.com/issues/442250902) New optimizaation DSL does not create configuration.txt by default | | [Issue #443587266](https://issuetracker.google.com/issues/443587266) AGP 8.13.0 fails to verify nav graph in a module | | [Issue #444260628](https://issuetracker.google.com/issues/444260628) AGP uses deprecated Gradle API: multi-string notation | | [Issue #347732357](https://issuetracker.google.com/issues/347732357) Warn users trying to use legacy multidex library with minSdkVersion \>=21 | | [Issue #333831734](https://issuetracker.google.com/issues/333831734) A build fails if there are code generation tasks | | [Issue #446123111](https://issuetracker.google.com/issues/446123111) With \`android.builtInKotlin=false\` and \`android.newDsl=false\` and \`android.enableLegacyVariantApi=false\`, using \`kotlin-android\` plugin will fail with "API 'applicationVariants' is obsolete" | | [Issue #443037365](https://issuetracker.google.com/issues/443037365) Built-in Kotlin fails to resolve unversioned kotlin-stdlib when kotlin.stdlib.default.dependency=false | | [Issue #445967244](https://issuetracker.google.com/issues/445967244) DexData opens a file without closing, preventing cleanup | | [Issue #368609737](https://issuetracker.google.com/issues/368609737) AndroidSourceDirectorySet should stop extending PatternFilterable in AGP 9.0 | | [Issue #389707041](https://issuetracker.google.com/issues/389707041) Test Fixture Error in test only modules | | [Issue #353249347](https://issuetracker.google.com/issues/353249347) Incorrect error when using context receivers in test fixtures | | [Issue #351046197](https://issuetracker.google.com/issues/351046197) Incorrect IDE errors for Kotlin code in testFixtures | | [Issue #446889652](https://issuetracker.google.com/issues/446889652) \`legacy-kapt\` plugin skips annotation processing unlike \`kotlin-kapt\` | | [Issue #446492061](https://issuetracker.google.com/issues/446492061) compileSdkSpec.minorApiLevel is not working with SettingsExtension | | [Issue #429253579](https://issuetracker.google.com/issues/429253579) \[fused lib - public\] Generated fused library does not include sources | | [Issue #149770867](https://issuetracker.google.com/issues/149770867) extractNativeLibs and useEmbeddedDex should not be coming from the manifest | | [Issue #449114518](https://issuetracker.google.com/issues/449114518) Warnings from R8 in AGP 9.0.0-alpha09 | | [Issue #368426598](https://issuetracker.google.com/issues/368426598) Remove deprecated AndroidSourceSet.jni in AGP 9.0 | | [Issue #368484483](https://issuetracker.google.com/issues/368484483) Remove Installation.installOptions() in AGP 9.0 | | [Issue #368482484](https://issuetracker.google.com/issues/368482484) Remove BuildType.isRenderscriptDebuggable in AGP 9.0. | | [Issue #428646179](https://issuetracker.google.com/issues/428646179) Remove android.defaults.buildfeatures.renderscript | | [Issue #436887358](https://issuetracker.google.com/issues/436887358) \`com.android.kotlin.multiplatform.library\` crashes with Gradle Managed Devices | | [Issue #428645763](https://issuetracker.google.com/issues/428645763) Remove \`android.defaults.buildfeatures.aidl\` defaults gradle.properties flags | | [Issue #294183018](https://issuetracker.google.com/issues/294183018) Fail build when proguard file does not exist | | [Issue #254305041](https://issuetracker.google.com/issues/254305041) remove buildconfig defaults gradle.properties flags | | [Issue #280674230](https://issuetracker.google.com/issues/280674230) Change the app's targetSdk default value to be based on compileSdk instead of minSdk | | [Issue #436878535](https://issuetracker.google.com/issues/436878535) When \`isIncludeAndroidResources\` is enabled, \`process{Variant}UnitTestManifest\` fails to merge tools:overrideLibrary usages in AGP 8.12.0 | | [Issue #411739086](https://issuetracker.google.com/issues/411739086) AGP causing deprecation warnings in Gradle for JVM test tasks | | [Issue #235457021](https://issuetracker.google.com/issues/235457021) DependencyReportTask is incompatible with the configuration cache | | [Issue #369246556](https://issuetracker.google.com/issues/369246556) Switch default source/target Java version from Java 8 to Java 11 in AGP 9.0 | | [Issue #258855275](https://issuetracker.google.com/issues/258855275) Flip android.useAndroidX default to true | | [Issue #442763200](https://issuetracker.google.com/issues/442763200) Better exception when applying kapt plugin with built-in Kotlin. | | [Issue #441679226](https://issuetracker.google.com/issues/441679226) android.proguard.failOnMissingFiles is not working for consumerProguardFiles | | [Issue #443051391](https://issuetracker.google.com/issues/443051391) Update Kotlin Gradle plugin dependency to 2.2.10 | | [Issue #429981132](https://issuetracker.google.com/issues/429981132) Create KotlinJvmAndroidCompilation using KGP API | | [Issue #442869731](https://issuetracker.google.com/issues/442869731) Kotlin explicit API mode applied to test sources | |
-| **Lint** | |---| | [Issue #430991549](https://issuetracker.google.com/issues/430991549) AGP 8.11.0: lintAnalyzeRelease task crashes when applying .gradle.kts files with apply(from = "...") | | [Issue #441536820](https://issuetracker.google.com/issues/441536820) Lint ChecksSdkIntAtLeast Check does not check if the annotated value is correct | | [Issue #446696613](https://issuetracker.google.com/issues/446696613) Built-in Kotlin does not add .kotlin_module to META-INF | | [Issue #449031505](https://issuetracker.google.com/issues/449031505) Lint classpath contains duplicate classes at different versions | | [Issue #448148350](https://issuetracker.google.com/issues/448148350) Overriding private resources workaround not working (tools:override = "true") | | [Issue #405676712](https://issuetracker.google.com/issues/405676712) Bug: removal of unused resources doesn't also remove the translations of them, and doesn't ask about it either | | [Issue #440415636](https://issuetracker.google.com/issues/440415636) Lint throwing warning "Could not clean up K2 caches" | | [Issue #440415636](https://issuetracker.google.com/issues/440415636) Lint throwing warning "Could not clean up K2 caches" | |
-| **Lint Integration** | |---| | [Issue #460068798](https://issuetracker.google.com/issues/460068798) AndroidLintAnalysisTask cache misses across different JDK vendors or minor versions due to systemPropertyInputs.javaVersion differences | | [Issue #444447002](https://issuetracker.google.com/issues/444447002) Lint automatically uses latest installed SDK despite compileSdk, doesn't register as task input and breaks caching | |
-| **Shrinker (R8)** | |---| | [Issue #454927488](https://issuetracker.google.com/issues/454927488) R8 optimized resource shrinking silently fails if using final resource IDs | |
+Fixed Issues **Android Gradle Plugin** [Issue #171293712](https://issuetracker.google.com/issues/171293712) Feature Request: Inject ideal AGP version as a property [Issue #443976533](https://issuetracker.google.com/issues/443976533) Stabilize SingleArtifact.VERSION_CONTROL_INFO_FILE [Issue #223643506](https://issuetracker.google.com/issues/223643506) androidTest connectedCheck logcat output is broken [Issue #386221070](https://issuetracker.google.com/issues/386221070) Built-in Kotlin support in AGP should not synchronize with the Kotlin sourcesets [Issue #460094802](https://issuetracker.google.com/issues/460094802) missingDimensionStrategy prefers a flavor maching its own name even from an unrelated dimension [Issue #386221070](https://issuetracker.google.com/issues/386221070) Built-in Kotlin support in AGP should not synchronize with the Kotlin sourcesets [Issue #471410336](https://issuetracker.google.com/issues/471410336) AGP 9.0.0-rc01 doesn't resolve Kotlin libraries via kotlin() function [Issue #450851465](https://issuetracker.google.com/issues/450851465) Built-in-kotlin does not publish kotlin-stdlib dependency constraint in maven pom [Issue #420592288](https://issuetracker.google.com/issues/420592288) Add a test case for divergence between compileSdk and targetSdk [Issue #449153004](https://issuetracker.google.com/issues/449153004) empty resConfigs value leads to obscure aapt error [Issue #447375921](https://issuetracker.google.com/issues/447375921) Pure Java project have dependency on kotlin stdlib. [Issue #368600704](https://issuetracker.google.com/issues/368600704) Remove deprecated KotlinMultiplatformAndroidCompilationBuilder properties in AGP 9.0 [Issue #445209309](https://issuetracker.google.com/issues/445209309) \`com.android.tools.build:gradle:9.0.0-alpha05\` should have an api dependency on KGP and gradle-api [Issue #452645779](https://issuetracker.google.com/issues/452645779) Rename com.android.experimental.built-in-kotlin Gradle plugin [Issue #446220448](https://issuetracker.google.com/issues/446220448) AGP9: \`variant.sources.kotlin!!.addGeneratedSourceDirectory()\` is not working [Issue #448450771](https://issuetracker.google.com/issues/448450771) Aar metadata checks on compile Sdk uses the old DSL [Issue #441523448](https://issuetracker.google.com/issues/441523448) Remove deprecated \`com.android.build.api.dsl.ManagedDevices.devices\` property [Issue #386221070](https://issuetracker.google.com/issues/386221070) Built-in Kotlin support in AGP should not synchronize with the Kotlin sourcesets [Issue #433758231](https://issuetracker.google.com/issues/433758231) Fail android library publishing if consumer keep file contains -dontobfuscate [Issue #241955408](https://issuetracker.google.com/issues/241955408) No Options to Print Mapping for Optimized Resources [Issue #436595826](https://issuetracker.google.com/issues/436595826) Make it an error to call finalizeDsl after this phase has been passed [Issue #457089670](https://issuetracker.google.com/issues/457089670) AGP initializes jetifier config even when jetifier is disabled [Issue #452246814](https://issuetracker.google.com/issues/452246814) built in kotlin does not add kotlinStdlib as a compile time dependency when \`kotlin.stdlib.default.dependency\` is true to module and pom files [Issue #442250902](https://issuetracker.google.com/issues/442250902) New optimizaation DSL does not create configuration.txt by default [Issue #443587266](https://issuetracker.google.com/issues/443587266) AGP 8.13.0 fails to verify nav graph in a module [Issue #444260628](https://issuetracker.google.com/issues/444260628) AGP uses deprecated Gradle API: multi-string notation [Issue #347732357](https://issuetracker.google.com/issues/347732357) Warn users trying to use legacy multidex library with minSdkVersion \>=21 [Issue #333831734](https://issuetracker.google.com/issues/333831734) A build fails if there are code generation tasks [Issue #446123111](https://issuetracker.google.com/issues/446123111) With \`android.builtInKotlin=false\` and \`android.newDsl=false\` and \`android.enableLegacyVariantApi=false\`, using \`kotlin-android\` plugin will fail with "API 'applicationVariants' is obsolete" [Issue #443037365](https://issuetracker.google.com/issues/443037365) Built-in Kotlin fails to resolve unversioned kotlin-stdlib when kotlin.stdlib.default.dependency=false [Issue #445967244](https://issuetracker.google.com/issues/445967244) DexData opens a file without closing, preventing cleanup [Issue #368609737](https://issuetracker.google.com/issues/368609737) AndroidSourceDirectorySet should stop extending PatternFilterable in AGP 9.0 [Issue #389707041](https://issuetracker.google.com/issues/389707041) Test Fixture Error in test only modules [Issue #353249347](https://issuetracker.google.com/issues/353249347) Incorrect error when using context receivers in test fixtures [Issue #351046197](https://issuetracker.google.com/issues/351046197) Incorrect IDE errors for Kotlin code in testFixtures [Issue #446889652](https://issuetracker.google.com/issues/446889652) \`legacy-kapt\` plugin skips annotation processing unlike \`kotlin-kapt\` [Issue #446492061](https://issuetracker.google.com/issues/446492061) compileSdkSpec.minorApiLevel is not working with SettingsExtension [Issue #429253579](https://issuetracker.google.com/issues/429253579) \[fused lib - public\] Generated fused library does not include sources [Issue #149770867](https://issuetracker.google.com/issues/149770867) extractNativeLibs and useEmbeddedDex should not be coming from the manifest [Issue #449114518](https://issuetracker.google.com/issues/449114518) Warnings from R8 in AGP 9.0.0-alpha09 [Issue #368426598](https://issuetracker.google.com/issues/368426598) Remove deprecated AndroidSourceSet.jni in AGP 9.0 [Issue #368484483](https://issuetracker.google.com/issues/368484483) Remove Installation.installOptions() in AGP 9.0 [Issue #368482484](https://issuetracker.google.com/issues/368482484) Remove BuildType.isRenderscriptDebuggable in AGP 9.0. [Issue #428646179](https://issuetracker.google.com/issues/428646179) Remove android.defaults.buildfeatures.renderscript [Issue #436887358](https://issuetracker.google.com/issues/436887358) \`com.android.kotlin.multiplatform.library\` crashes with Gradle Managed Devices [Issue #428645763](https://issuetracker.google.com/issues/428645763) Remove \`android.defaults.buildfeatures.aidl\` defaults gradle.properties flags [Issue #294183018](https://issuetracker.google.com/issues/294183018) Fail build when proguard file does not exist [Issue #254305041](https://issuetracker.google.com/issues/254305041) remove buildconfig defaults gradle.properties flags [Issue #280674230](https://issuetracker.google.com/issues/280674230) Change the app's targetSdk default value to be based on compileSdk instead of minSdk [Issue #436878535](https://issuetracker.google.com/issues/436878535) When \`isIncludeAndroidResources\` is enabled, \`process{Variant}UnitTestManifest\` fails to merge tools:overrideLibrary usages in AGP 8.12.0 [Issue #411739086](https://issuetracker.google.com/issues/411739086) AGP causing deprecation warnings in Gradle for JVM test tasks [Issue #235457021](https://issuetracker.google.com/issues/235457021) DependencyReportTask is incompatible with the configuration cache [Issue #369246556](https://issuetracker.google.com/issues/369246556) Switch default source/target Java version from Java 8 to Java 11 in AGP 9.0 [Issue #258855275](https://issuetracker.google.com/issues/258855275) Flip android.useAndroidX default to true [Issue #442763200](https://issuetracker.google.com/issues/442763200) Better exception when applying kapt plugin with built-in Kotlin. [Issue #441679226](https://issuetracker.google.com/issues/441679226) android.proguard.failOnMissingFiles is not working for consumerProguardFiles [Issue #443051391](https://issuetracker.google.com/issues/443051391) Update Kotlin Gradle plugin dependency to 2.2.10 [Issue #429981132](https://issuetracker.google.com/issues/429981132) Create KotlinJvmAndroidCompilation using KGP API [Issue #442869731](https://issuetracker.google.com/issues/442869731) Kotlin explicit API mode applied to test sources **Lint** [Issue #430991549](https://issuetracker.google.com/issues/430991549) AGP 8.11.0: lintAnalyzeRelease task crashes when applying .gradle.kts files with apply(from = "...") [Issue #441536820](https://issuetracker.google.com/issues/441536820) Lint ChecksSdkIntAtLeast Check does not check if the annotated value is correct [Issue #446696613](https://issuetracker.google.com/issues/446696613) Built-in Kotlin does not add .kotlin_module to META-INF [Issue #449031505](https://issuetracker.google.com/issues/449031505) Lint classpath contains duplicate classes at different versions [Issue #448148350](https://issuetracker.google.com/issues/448148350) Overriding private resources workaround not working (tools:override = "true") [Issue #405676712](https://issuetracker.google.com/issues/405676712) Bug: removal of unused resources doesn't also remove the translations of them, and doesn't ask about it either [Issue #440415636](https://issuetracker.google.com/issues/440415636) Lint throwing warning "Could not clean up K2 caches" [Issue #440415636](https://issuetracker.google.com/issues/440415636) Lint throwing warning "Could not clean up K2 caches" **Lint Integration** [Issue #460068798](https://issuetracker.google.com/issues/460068798) AndroidLintAnalysisTask cache misses across different JDK vendors or minor versions due to systemPropertyInputs.javaVersion differences [Issue #444447002](https://issuetracker.google.com/issues/444447002) Lint automatically uses latest installed SDK despite compileSdk, doesn't register as task input and breaks caching **Shrinker (R8)** [Issue #454927488](https://issuetracker.google.com/issues/454927488) R8 optimized resource shrinking silently fails if using final resource IDs
\ No newline at end of file
diff --git a/build/agp/agp-9-upgrade/references/buildconfig.md b/build-system/agp/agp-9-upgrade/references/buildconfig.md
similarity index 90%
rename from build/agp/agp-9-upgrade/references/buildconfig.md
rename to build-system/agp/agp-9-upgrade/references/buildconfig.md
index 273f876..e4e19b6 100644
--- a/build/agp/agp-9-upgrade/references/buildconfig.md
+++ b/build-system/agp/agp-9-upgrade/references/buildconfig.md
@@ -37,7 +37,7 @@ In custom build-logic using `CommonExtension`:
### Step 2: Migrate to the new API
-Use the **addCustomBuildConfigFields** recipe from the [gradle-recipes](https://developer.android.com/agents/skills/build/agp/agp-9-upgrade/references/recipes)
+Use the **addCustomBuildConfigFields** recipe from the [gradle-recipes](https://developer.android.com/agents/skills/build-system/agp/agp-9-upgrade/references/recipes)
repository.
**IMPORTANT:** For `BuildConfigField`s with a type of `String`, the `value` field
diff --git a/build/agp/agp-9-upgrade/references/ksp-kapt.md b/build-system/agp/agp-9-upgrade/references/ksp-kapt.md
similarity index 100%
rename from build/agp/agp-9-upgrade/references/ksp-kapt.md
rename to build-system/agp/agp-9-upgrade/references/ksp-kapt.md
diff --git a/build/agp/agp-9-upgrade/references/paparazzi-gradle-9.md b/build-system/agp/agp-9-upgrade/references/paparazzi-gradle-9.md
similarity index 100%
rename from build/agp/agp-9-upgrade/references/paparazzi-gradle-9.md
rename to build-system/agp/agp-9-upgrade/references/paparazzi-gradle-9.md
diff --git a/build/agp/agp-9-upgrade/references/recipes.md b/build-system/agp/agp-9-upgrade/references/recipes.md
similarity index 94%
rename from build/agp/agp-9-upgrade/references/recipes.md
rename to build-system/agp/agp-9-upgrade/references/recipes.md
index c26d85f..d3ecc74 100644
--- a/build/agp/agp-9-upgrade/references/recipes.md
+++ b/build-system/agp/agp-9-upgrade/references/recipes.md
@@ -4,8 +4,7 @@ migrated.
## Guidelines
-- **DO NOT** search the web for examples of how to do this. Use the
- **gradle-recipes** repository examples **only**.
+- **DO NOT** search the web for examples of how to do this. Use the **gradle-recipes** repository examples **only**.
- **DO NOT** use AGP internals in migrated code.
- **DO** use only public APIs in migrated code.
@@ -39,7 +38,7 @@ Additional details for each use-case follow.
### Add custom BuildConfig fields
-See the detailed guide at [BuildConfig](https://developer.android.com/agents/skills/build/agp/agp-9-upgrade/references/buildconfig).
+See the detailed guide at [BuildConfig](https://developer.android.com/agents/skills/build-system/agp/agp-9-upgrade/references/buildconfig).
### Renaming an APK
diff --git a/camera/camerax/SKILL.md b/camera/camerax/SKILL.md
new file mode 100644
index 0000000..d3962f7
--- /dev/null
+++ b/camera/camerax/SKILL.md
@@ -0,0 +1,132 @@
+---
+name: camerax
+description: Provide technical guidance for Android camera development with CameraX.
+ Use when implementing camera features, handling asynchronous recording lifecycles,
+ wiring low-level hardware interop using CameraX, or integrating ML Kit or Media3
+ effects.
+license: Complete terms in LICENSE.txt
+metadata:
+ author: Google LLC
+ last-updated: '2026-10-08'
+ keywords:
+ - recipe
+ - Android
+ - Camera
+ - Camera1
+ - Camera2
+ - CameraX
+ - migration
+ - Compose
+ - guide
+ - dependencies
+ - PreviewView
+ - CameraXViewfinder
+ - ImageCapture
+ - VideoCapture
+ - ImageAnalysis.
+---
+
+This skill provides procedural guidance and standard patterns for building
+camera applications on Android, with a focus on CameraX, including its
+`Camera2Interop` utilities, and Media3 integrations.
+
+## Core workflows
+
+### Handling immutable API patterns
+
+Various Android camera and media APIs, especially CameraX `VideoCapture`, use a
+**fluent, immutable builder-like pattern** where methods return a new instance.
+Failing to reassign these results in settings, such as audio, being ignored.
+
+**Pattern: Reassignment is required**
+
+
+```kotlin
+// WRONG
+run {
+ val pending = recorder.prepareRecording(context, opts)
+ pending.withAudioEnabled() // This returns a new instance which is ignored
+ val active = pending.start(exec, listener)
+}
+
+// CORRECT
+run {
+ val pending = recorder.prepareRecording(context, opts)
+ .withAudioEnabled() // Chaining works
+ val active = pending.start(exec, listener)
+}
+
+// ALSO CORRECT
+run {
+ var pending = recorder.prepareRecording(context, opts)
+ pending = pending.withAudioEnabled() // Reassignment
+ val active = pending.start(exec, listener)
+}
+```
+
+
+
+See [immutability](references/immutability.md) for a list of affected classes.
+
+### Migrating to CameraX
+
+When migrating legacy camera codebases to the CameraX Jetpack library:
+
+- **Camera1 to CameraX** : For migrating legacy `android.hardware.Camera` implementations, surface handling, and manual lifecycles, see the [Camera1 migration guide](references/camera1-to-camerax.md).
+- **Camera2 to CameraX** : For migrating more recent but verbose `android.hardware.camera2` implementations, session state callbacks, and interop patterns, see the [Camera2 migration guide](references/camera2-to-camerax.md).
+
+### Comprehensive feature blueprinting
+
+For multi-step features that involve multiple files and hardware-level wiring,
+follow the [Structural Blueprinting](references/expert-blueprints.md) approach to avoid
+system timeouts. Such complex features include:
+
+- **Manual controls** : Break down into the `ViewModel` state, the controller layer, and the `Camera2Interop` wiring in the session.
+- **RAW capture**: Separate JPEG and RAW output configurations into discrete build steps.
+- **Custom effects** : Prefer `Media3Effect` or `SurfaceProcessor` over manual OpenGL pipelines unless absolute performance is required.
+- **Low-light** : See [low-light](references/low-light.md) for Night Mode and LLB guidance.
+- **Foldables** : See [foldables](references/foldables.md) for handling dynamic postures and hinge states.
+- **XR, AR, and VR** : See [xr](references/xr.md) for spatial tracking, passthrough synchronization, and latency guardrails.
+- **Thermals and power** : See [thermals](references/thermals.md) for managing `StreamUseCase` optimizations and `PowerManager` thermal states.
+- **Testing and mocking** : See [testing](references/testing.md) for using `FakeCameraConfig`, handling asynchronous lifecycles, and validating analysis pipelines.
+- **ML Kit spatial analysis** : See [mlkit-spatial](references/mlkit-spatial.md) for coordinate mapping, rotation logic, and mirrored lens handling.
+- **Wear OS camera remote** : See [wear-os](references/wear-os.md) for circular UI constraints, Data Layer API syncing, and remote trigger logic.
+
+See [expert-blueprints](references/expert-blueprints.md) for step-by-step guides.
+
+### API discovery
+
+Always use higher-level abstractions instead of low-level manual wiring:
+
+- **Analysis** : Use `MlKitAnalyzer` instead of manual `ImageAnalysis.Analyzer`.
+- **Filters and effects** : Use `Media3Effect` for standard post-processing.
+- **Multi-camera** : Use `ConcurrentCamera` APIs for dual-stream setups.
+
+See [modern-apis](references/modern-apis.md) for current recommendations.
+
+### Code quality and architectural rules
+
+Adhere to the following Android ecosystem standard patterns when building your
+camera implementations:
+
+- **Testing, fakes over mocks** : Avoid mocking libraries like `Mockito`, especially for multi-step CameraX interfaces like `ImageProxy`. Build "Fakes" to verify state rather than unreliable implementation details.
+- **Google Truth assertions** : Use `assertThat` over standard `JUnit` assertions like `assertEquals` for improved readability.
+- **Explicit test runners** : Always define an explicit `@RunWith` for test classes to ensure the CI environment executes them correctly.
+- **Semantic UI merging** : When building custom camera controls in Compose, such as a button with an `Icon` and `Text`, use `semantics {
+ mergeDescendants = true }` to ensure screen readers announce them as a single, coherent unit.
+
+## Hardware and device diversity
+
+Camera apps run on a wide variety of hardware, from mobile phones and
+foldables to tablets, laptops, and even smart appliances. Have consideration
+for the specific hardware the app is running on.
+
+- **Form factors**: Account for screen size and orientation changes on foldables and tablets.
+- **Multi-camera arrays**: Some devices have a rear-facing camera and a front-facing camera. Other devices have multiple rear-facing cameras, such as wide-angle and telephoto lenses.
+- **Feature parity**: Features like flash or auto-focus behave differently across hardware. For example, CameraX handles both physical flash, back, and screen-based flash, front, and both must be considered when implementing flash functionality.
+
+## Common pitfalls
+
+- **Asynchronous lifecycles** : Check `isRecording` state before attempting to stop or pause. Handle `VideoRecordEvent.Start` for UI state updates, not just the initial call.
+- **Thread safety**: Camera callbacks often run on background executors. Dispatch UI updates on the main thread.
+- **Permission handling** : Check `CAMERA` permission; check for `RECORD_AUDIO` specifically when enabling audio in `VideoCapture`.
diff --git a/camera/camerax/references/camera1-to-camerax.md b/camera/camerax/references/camera1-to-camerax.md
new file mode 100644
index 0000000..9c6e80e
--- /dev/null
+++ b/camera/camerax/references/camera1-to-camerax.md
@@ -0,0 +1,198 @@
+## Remove `Camera1` implementation
+
+1. Delete all `android.hardware.Camera` instances.
+2. Delete `SurfaceView` and `SurfaceHolder.Callback` implementations `surfaceCreated`, `surfaceChanged`, and `surfaceDestroyed`.
+3. Remove custom lifecycle handling that opens or releases the camera in `onResume` or `onPause`.
+4. Remove manual matrix calculations for orientation.
+
+## Initialize `ProcessCameraProvider`
+
+Request the `ProcessCameraProvider` and bind use cases to the Activity or
+Fragment lifecycle.
+
+
+```kotlin
+val context = LocalContext.current
+val lifecycleOwner = LocalLifecycleOwner.current
+LaunchedEffect(context, lifecycleOwner) {
+ val cameraProvider = ProcessCameraProvider.getInstance(context).await()
+
+ val cameraSelector = CameraSelector.Builder()
+ .requireLensFacing(CameraSelector.LENS_FACING_BACK)
+ .build()
+
+ val preview = Preview.Builder().build()
+ val imageCapture = ImageCapture.Builder()
+ .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
+ .build()
+
+ cameraProvider.unbindAll() // Unbind before rebinding
+
+ val camera = cameraProvider.bindToLifecycle(
+ lifecycleOwner,
+ cameraSelector,
+ preview,
+ imageCapture
+ )
+ val cameraControl = camera.cameraControl
+}
+```
+
+
+
+## Implement the preview and tap-to-focus
+
+Choose exactly one of the following patterns based on the app's UI toolkit:
+
+### Option A: For Android Views
+
+Use `androidx.camera.view.PreviewView`.
+
+1. **Set up preview**:
+
+
+ ```kotlin
+ preview.setSurfaceProvider(previewView.surfaceProvider)
+ ```
+
+
+
+2. **Handle tap-to-focus**:
+
+
+ ```kotlin
+ val factory = previewView.meteringPointFactory
+ val point = factory.createPoint(x, y) // x, y from touch event
+ val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF).build()
+ cameraControl?.startFocusAndMetering(action)
+ ```
+
+
+
+### Option B: For Jetpack Compose
+
+Use `androidx.camera.compose.CameraXViewfinder`.
+
+1. **Set up preview and SurfaceRequest**:
+
+
+ ```kotlin
+ var surfaceRequest by remember { mutableStateOf(null) }
+ val preview = remember {
+ Preview.Builder().build().apply {
+ setSurfaceProvider { request -> surfaceRequest = request }
+ }
+ }
+ ```
+
+
+
+2. **Render viewfinder**:
+
+
+ ```kotlin
+ surfaceRequest?.let { request ->
+ CameraXViewfinder(
+ surfaceRequest = request,
+ coordinateTransformer = coordinateTransformer,
+ modifier = Modifier
+ )
+ }
+ ```
+
+
+
+3. **Handle tap-to-focus in Compose**:
+
+
+ ```kotlin
+ // Inside your tap gesture handler...
+ val surfaceCoords = with(coordinateTransformer) { offset.transform() }
+ val factory = SurfaceOrientedMeteringPointFactory(
+ request.resolution.width.toFloat(),
+ request.resolution.height.toFloat()
+ )
+ val point = factory.createPoint(surfaceCoords.x, surfaceCoords.y)
+ val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF).build()
+ cameraControl?.startFocusAndMetering(action)
+ ```
+
+
+
+4. **Update target rotation for Compose**:
+
+
+ ```kotlin
+ LaunchedEffect(configuration) {
+ if (!view.isInEditMode) {
+ val rotation = view.display?.rotation ?: Surface.ROTATION_0
+ imageCapture.targetRotation = rotation
+ preview.targetRotation = rotation
+ }
+ }
+ ```
+
+
+
+## Capture a photo
+
+Use the `ImageCapture` use case to take the picture. The `ImageProxy` handles
+rotation directly.
+
+
+```kotlin
+imageCapture.takePicture(
+ cameraExecutor,
+ object : ImageCapture.OnImageCapturedCallback() {
+ override fun onCaptureSuccess(image: ImageProxy) {
+ val buffer = image.planes[0].buffer
+ val bytes = ByteArray(buffer.remaining())
+ buffer.get(bytes)
+ val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
+
+ // Adjust rotation natively via ImageProxy
+ val matrix = Matrix()
+ matrix.postRotate(image.imageInfo.rotationDegrees.toFloat())
+ if (lensFacing == CameraSelector.LENS_FACING_FRONT) {
+ matrix.postScale(-1f, 1f) // Mirror for front camera
+ }
+
+ val rotatedBitmap = Bitmap.createBitmap(
+ bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true
+ )
+
+ // MUST close proxy
+ image.close()
+ }
+
+ override fun onError(exception: ImageCaptureException) {
+ Log.e("CameraX", "Capture failed: ${exception.message}", exception)
+ }
+ }
+)
+```
+
+
+
+## Switch cameras
+
+To flip between front and rear cameras, change the `CameraSelector` and
+retrigger the `ProcessCameraProvider` logic.
+
+
+```kotlin
+lensFacing = if (lensFacing == CameraSelector.LENS_FACING_BACK) {
+ CameraSelector.LENS_FACING_FRONT
+} else {
+ CameraSelector.LENS_FACING_BACK
+}
+```
+
+
+
+## Follow constraints
+
+- **Don't manage the camera lifecycle manually** : Bind the camera to a `LifecycleOwner` through the `ProcessCameraProvider`. Avoid manual camera open or close logic in `onResume` or `onPause`.
+- **Don't calculate focus matrices manually** : `MeteringPointFactory` handles coordinate transformations, including device rotation offsets. Avoid custom matrix implementations.
+- **Don't forget to close the `ImageProxy`** : Remember to invoke `image.close()` in the capture callback. Skipping this call locks the capture pipeline and interrupts subsequent photos.
+- **Don't wrap `PreviewView` in `AndroidView` for Compose code** : For Compose UI layouts, use `CameraXViewfinder`. Compiling `PreviewView` in an `AndroidView` is an earlier fallback option that introduces resizing issues.
\ No newline at end of file
diff --git a/camera/camerax/references/camera2-to-camerax.md b/camera/camerax/references/camera2-to-camerax.md
new file mode 100644
index 0000000..5b21f87
--- /dev/null
+++ b/camera/camerax/references/camera2-to-camerax.md
@@ -0,0 +1,193 @@
+Camera2 offers granular control but introduces boilerplate: managing
+`CameraDevice` states, `CameraCaptureSession` lifecycles, background threads,
+`HandlerThread`, and manual orientation calculations.
+
+CameraX simplifies this by binding high-level `UseCase`s such as `Preview`,
+`ImageCapture`, and `ImageAnalysis` directly to Android lifecycles, handling
+thread management and device-specific workarounds automatically.
+
+*** ** * ** ***
+
+## Remove Camera2 boilerplate
+
+Migrating to CameraX removes manual setup code:
+
+1. **Delete manual thread handling** : Remove `HandlerThread`, `Handler`, and executors dedicated to camera background tasks. CameraX manages its own threads.
+2. **Remove session and device callbacks** : Delete `CameraDevice.StateCallback` and `CameraCaptureSession.StateCallback` implementations.
+3. **Remove surface management** : Remove manual routing of `Surface` objects from `TextureView` or `SurfaceView` to the camera device.
+4. **Remove manual orientation logic** : Delete calculations involving `CameraCharacteristics.SENSOR_ORIENTATION` and display rotation for adjusting preview and capture orientation.
+
+*** ** * ** ***
+
+## Initialize `ProcessCameraProvider`
+
+Request the `ProcessCameraProvider` and bind your use cases to the
+`LifecycleOwner` activity or fragment. This replaces the
+`CameraManager.openCamera` flow.
+
+
+```kotlin
+val context = LocalContext.current
+val lifecycleOwner = LocalLifecycleOwner.current
+LaunchedEffect(context, lifecycleOwner) {
+ val cameraProvider = ProcessCameraProvider.getInstance(context).await()
+
+ val cameraSelector = CameraSelector.Builder()
+ .requireLensFacing(CameraSelector.LENS_FACING_BACK)
+ .build()
+
+ val preview = Preview.Builder().build()
+ val imageCapture = ImageCapture.Builder()
+ .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
+ .build()
+
+ // ImageAnalysis is common when migrating from Camera2 ImageReader
+ val imageAnalysis = ImageAnalysis.Builder()
+ .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
+ .build()
+
+ cameraProvider.unbindAll()
+
+ val camera = cameraProvider.bindToLifecycle(
+ lifecycleOwner,
+ cameraSelector,
+ preview,
+ imageCapture,
+ imageAnalysis
+ )
+}
+```
+
+
+
+*** ** * ** ***
+
+## Implement the preview and tap-to-focus
+
+CameraX handles surface configuration automatically. Choose based on your UI
+toolkit:
+
+### Option A: For Android Views
+
+Use `androidx.camera.view.PreviewView` in your layout, and bind it to the
+`Preview` use case.
+
+
+```kotlin
+preview.setSurfaceProvider(previewView.surfaceProvider)
+```
+
+
+
+### Option B: For Jetpack Compose
+
+Use `androidx.camera.compose.CameraXViewfinder`.
+
+
+```kotlin
+var surfaceRequest by remember { mutableStateOf(null) }
+val preview = remember {
+ Preview.Builder().build().apply {
+ setSurfaceProvider { request -> surfaceRequest = request }
+ }
+}
+```
+
+
+
+*** ** * ** ***
+
+## Capture a photo
+
+Replace `ImageReader` capture flows and
+`CaptureRequest.Builder.TEMPLATE_STILL_CAPTURE` with the `ImageCapture` use
+case. CameraX handles the rotation natively using the returned `ImageProxy`.
+
+
+```kotlin
+imageCapture.takePicture(
+ cameraExecutor,
+ object : ImageCapture.OnImageCapturedCallback() {
+ override fun onCaptureSuccess(image: ImageProxy) {
+ val buffer = image.planes[0].buffer
+ val bytes = ByteArray(buffer.remaining())
+ buffer.get(bytes)
+ val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
+ if (bitmap != null) {
+ val matrix = Matrix()
+ matrix.postRotate(image.imageInfo.rotationDegrees.toFloat())
+ if (lensFacing == CameraSelector.LENS_FACING_FRONT) {
+ matrix.postScale(-1f, 1f)
+ }
+
+ val rotatedBitmap = Bitmap.createBitmap(
+ bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true
+ )
+ }
+
+ // MUST close proxy
+ image.close()
+ }
+
+ override fun onError(exception: ImageCaptureException) {
+ Log.e("CameraX", "Capture failed: ${exception.message}", exception)
+ }
+ }
+)
+```
+
+
+
+*** ** * ** ***
+
+## Implement image analysis
+
+If you were using `ImageReader` in Camera2 to access raw frames, e.g., for QR
+scanning or ML,, replace it with the CameraX `ImageAnalysis` use case.
+
+
+```kotlin
+imageAnalysis.setAnalyzer(cameraExecutor) { imageProxy ->
+ try {
+ val rotationDegrees = imageProxy.imageInfo.rotationDegrees
+ // Process image here (e.g., run object detection)
+ // ...
+ } finally {
+ // MUST close the imageProxy to avoid blocking the pipeline
+ imageProxy.close()
+ }
+}
+```
+
+
+
+*** ** * ** ***
+
+## Use Camera2 interop
+
+If your app requires specific Camera2 configuration options, such as custom
+exposure modes or flash settings, that aren't exposed directly in CameraX,
+use `Camera2Interop` to apply them to your CameraX use cases.
+
+
+```kotlin
+// Use Camera2Interop to set Camera2-specific capture options
+val extender = Camera2Interop.Extender(imageCaptureBuilder)
+extender.setCaptureRequestOption(
+ CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_MODE_OFF
+).setCaptureRequestOption(
+ CaptureRequest.FLASH_MODE,
+ CaptureRequest.FLASH_MODE_TORCH
+)
+```
+
+
+
+*** ** * ** ***
+
+## Follow constraints
+
+- **Don't open the CameraDevice manually** : Let CameraX manage device opening and closing using `bindToLifecycle`.
+- **Don't forget to close `ImageProxy`** : In both `ImageCapture` and `ImageAnalysis` callbacks, you **must** call `image.close()`. Failure to do so will block the camera pipeline.
+- **Don't block the ImageAnalysis thread** : The analyzer runs frame-by-frame. If you need to perform heavy computations, offload them to a background worker and close the `ImageProxy` as soon as possible.
\ No newline at end of file
diff --git a/camera/camerax/references/expert-blueprints.md b/camera/camerax/references/expert-blueprints.md
new file mode 100644
index 0000000..e1f2f25
--- /dev/null
+++ b/camera/camerax/references/expert-blueprints.md
@@ -0,0 +1,58 @@
+Complex camera features often fail due to "Agent Stall" or timeouts when
+attempted in a single turn. Use these blueprints to break tasks into manageable
+phases.
+
+## Manual controls
+
+### Phase one: ViewModel and state
+
+1. Define a `ManualSettings` data class.
+2. Add a `MutableStateFlow` to your `CameraViewModel`.
+3. Implement the Jetpack Compose UI with sliders and switches to update this flow.
+
+### Phase two: Controller wiring
+
+1. In `CameraController.kt`, create a new function `updateManualSettings(settings: ManualSettings)`.
+2. Map these settings into the `CameraSystem` layer.
+
+### Phase three: CameraX `Camera2Interop` wiring
+
+1. In `CameraSession.kt`, use the `Camera2Interop.Extender` utility to access Camera2 capture request keys.
+2. Apply the hardware keys:
+ - `CaptureRequest.SENSOR_SENSITIVITY` to set ISO sensitivity.
+ - `CaptureRequest.SENSOR_EXPOSURE_TIME` to set exposure time.
+ - `CaptureRequest.LENS_FOCUS_DISTANCE` to set focus distance.
+3. **Critical** : If manual exposure is active, set `CaptureRequest.CONTROL_AE_MODE` to `CameraMetadata.CONTROL_AE_MODE_OFF`. ---
+
+## RAW and JPEG capture
+
+### Phase one: Output configuration
+
+1. Verify device support for RAW capture using `CameraInfo`.
+2. Configure `ImageCapture.Builder` with `OUTPUT_FORMAT_RAW_JPEG` or `OUTPUT_FORMAT_RAW`.
+
+### Phase two: Implementation
+
+1. Provide `ImageCapture.OutputFileOptions` for the target storage locations.
+2. Invoke `takePicture`. CameraX internally manages `DngCreator` to wrap RAW data with the required `CameraCharacteristics` and `CaptureResult` metadata.
+
+*** ** * ** ***
+
+## Apply image effects
+
+### Phase one: Effects selection
+
+1. Use the `androidx.media3:media3-effect` dependency.
+2. Use `RgbFilter` or `HslAdjustment` for standard color grading.
+
+### Phase two: Application
+
+1. Configure `Composition.Builder` or `MediaItem.Builder` with the list of effects.
+2. Inject the list of effects into the CameraX `Recorder` or `Preview` using the `setEffects` method.
+
+*** ** * ** ***
+
+## Low-light capture
+
+For guidance on Night Mode Extensions and Low Light Boost,
+[low-light.md](https://developer.android.com/agents/skills/camera/camerax/references/low-light).
\ No newline at end of file
diff --git a/camera/camerax/references/foldables.md b/camera/camerax/references/foldables.md
new file mode 100644
index 0000000..22d378a
--- /dev/null
+++ b/camera/camerax/references/foldables.md
@@ -0,0 +1,83 @@
+Foldable devices introduce unique challenges for camera applications, including
+dynamic layout changes, multiple display orientations, and physical device
+postures, such as tabletop and book modes.
+
+## Manage fold states and postures
+
+| State | Posture | User interaction | Implementation goal |
+|---|---|---|---|
+| `FLAT` | Standard | Full screen preview | Conventional mobile phone layout. |
+| `HALF_OPENED` | Tabletop | Lower half for controls | Split-screen layout, viewfinder on top, controls on bottom. |
+| `HALF_OPENED` | Book | Side-by-side | Viewfinder on one panel, gallery and controls on the other. |
+
+*** ** * ** ***
+
+## Follow the implementation guide
+
+### Detect posture changes
+
+Use the Jetpack WindowManager library to observe the device's hinge state and
+fold layout.
+
+
+```kotlin
+lifecycleScope.launch {
+ lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ windowInfoTracker.windowLayoutInfo(activity)
+ .collect { layoutInfo ->
+ val displayFeature = layoutInfo.displayFeatures
+ .filterIsInstance()
+ .firstOrNull()
+
+ updateCameraLayout(displayFeature)
+ }
+ }
+}
+```
+
+
+
+### Handle tabletop mode
+
+In Tabletop mode, horizontal fold, you should move the viewfinder to the top
+half of the screen and the controls to the bottom half to prevent the user from
+seeing a "bent" image.
+
+- **Identify orientation:** Check `FoldingFeature.orientation`.
+- **Calculate geometry:** Use `FoldingFeature.bounds` to identify the hinge's physical location on the screen.
+- **Update UI:** Apply padding or constraints to move the `PreviewView` above the hinge.
+
+### Coordinate mapping and `Viewport`
+
+When the UI layout changes due to a fold, you **must** update the `Viewport` to
+ensure that tap-to-focus and image capture coordinates remain accurate.
+
+
+```kotlin
+val viewport = ViewPort.Builder(Rational(viewfinder.width, viewfinder.height), display.rotation)
+ .setScaleType(ViewPort.FILL_CENTER)
+ .build()
+
+val useCaseGroup = UseCaseGroup.Builder()
+ .addUseCase(preview)
+ .setViewPort(viewport)
+ .build()
+```
+
+
+
+### Rear display mode
+
+Some foldables allow using the rear camera with the cover display while the
+device is unfolded.
+
+- **Verification:** If available through OEM SDKs or Android 14 (API level 34) or higher, check `DeviceState.REAR_DISPLAY_STATE`.
+- **Logic:** Handle preview detachment and reattachment on different display surfaces with varying aspect ratios.
+
+*** ** * ** ***
+
+## Foldable pitfalls
+
+- **Hinge distortion:** Don't span the camera preview across a hinge in `HALF_OPENED` state.
+- **Physical orientation:** The camera sensor's physical orientation relative to the screen often changes when you fold or unfold the device. Always rely on `CameraInfo.getSensorRotationDegrees`.
+- **Latency:** Rebinding `UseCase` objects during a fold event is expensive. Use the internal scaling of `PreviewView` before performing a full `bindToLifecycle` reconfiguration.
\ No newline at end of file
diff --git a/camera/camerax/references/immutability.md b/camera/camerax/references/immutability.md
new file mode 100644
index 0000000..1dade21
--- /dev/null
+++ b/camera/camerax/references/immutability.md
@@ -0,0 +1,60 @@
+Many Android APIs are designed with immutability in mind to prevent race
+conditions in async environments. However, this often trips up developers used
+to mutable builder patterns.
+
+## Common immutable classes
+
+The following classes use fluent APIs that **return a new instance**. You must
+reassign the variable.
+
+| Class | Methods that return a new instance | Result if not reassigned |
+|---|---|---|
+| `PendingRecording` | `withAudioEnabled`, `asPersistentRecording` | Audio isn't recorded. |
+| `ImageCapture.Builder` | `setTargetRotation`, `setTargetResolution` | The output has the wrong orientation. |
+| `Recorder.Builder` | `setQualitySelector`, `setExecutor` | The recording uses the default quality. |
+| `Viewport.Builder` | `setScaleType`, `setLayoutDirection` | The viewfinder is stretched. |
+
+## Use standard patterns
+
+### CameraX video recording
+
+To set up video recording, use the following code:
+
+
+```kotlin
+// WRONG
+run {
+ val pending = recorder.prepareRecording(context, opts)
+ pending.withAudioEnabled() // This returns a new instance which is ignored
+ val active = pending.start(exec, listener)
+}
+
+// CORRECT
+run {
+ val pending = recorder.prepareRecording(context, opts)
+ .withAudioEnabled() // Chaining works
+ val active = pending.start(exec, listener)
+}
+
+// ALSO CORRECT
+run {
+ var pending = recorder.prepareRecording(context, opts)
+ pending = pending.withAudioEnabled() // Reassignment
+ val active = pending.start(exec, listener)
+}
+```
+
+
+
+### Viewport construction
+
+To set up the viewport, use the following code:
+
+
+```kotlin
+val viewport = ViewPort.Builder(Rational(width, height), displayRotation)
+ .setScaleType(ViewPort.FILL_CENTER)
+ .build()
+```
+
+
\ No newline at end of file
diff --git a/camera/camerax/references/low-light.md b/camera/camerax/references/low-light.md
new file mode 100644
index 0000000..e03e00c
--- /dev/null
+++ b/camera/camerax/references/low-light.md
@@ -0,0 +1,124 @@
+This guide covers implementing low-light features using **Night mode
+extensions** and **Low Light Boost (LLB)**.
+
+## Choosing the right tool
+
+| Feature | Used for | Implementation | UX impact |
+|---|---|---|---|
+| **Night mode** | High-quality stills | `ExtensionsManager` | Possibly requires user to hold still for several seconds. |
+| **LLB, AE mode** | Real-time preview or video | `Camera2Interop`, CameraX utility | Hardware drops the frame rate to increase brightness. |
+| **LLB, Play services** | Real-time preview and video | `SurfaceProcessor` | Software-based brightening; maintains a higher frame rate. |
+
+*** ** * ** ***
+
+## Night mode extension
+
+CameraX Extensions provide access to the device's built-in computational
+photography pipeline.
+
+### Basic setup
+
+To set up the extension, initialize the extension manager:
+
+
+```kotlin
+// Use ListenableFuture.await() extension function for coroutine support
+val extensionsManager = ExtensionsManager.getInstanceAsync(context, cameraProvider).await()
+if (extensionsManager.isExtensionAvailable(cameraSelector, ExtensionMode.NIGHT)) {
+ val nightSelector = extensionsManager.getExtensionEnabledCameraSelector(
+ cameraSelector, ExtensionMode.NIGHT
+ )
+ cameraProvider.bindToLifecycle(lifecycleOwner, nightSelector, imageCapture, preview)
+}
+```
+
+
+
+### Comprehensive features
+
+- **Image postview** : Display a low-resolution image immediately while the multi-frame processing occurs.
+
+ ```kotlin
+ val imageCapture = ImageCapture.Builder()
+ .setPostviewEnabled(true)
+ .build()
+ ```
+- **Extension strength** : Let users control the intensity of the night effect.
+
+ ```kotlin
+ // Set the strength of the active extension (e.g. NIGHT mode intensity)
+ val extensionsManager = ExtensionsManager.getInstanceAsync(context, cameraProvider).await()
+ val extensionsControl = extensionsManager.getCameraExtensionsControl(camera.cameraControl)
+ extensionsControl?.setExtensionStrength(strength)
+ ```
+- **Capture progress** : Show a UI progress bar for long exposures.
+
+ ```kotlin
+ // Use the suspend extension function for takePicture to avoid callback boilerplate
+ try {
+ val result = imageCapture.takePicture(outputOptions)
+ // Use result.savedUri or other fields
+ } catch (e: ImageCaptureException) {
+ // Handle capture failure
+ }
+ ```
+
+*** ** * ** ***
+
+## Low-light boost
+
+LLB is designed for preview and video streams where you prefer high frame rates.
+
+### AE mode
+
+The built-in CameraX way to prioritize brightness. It modifies the hardware's
+auto-exposure algorithm.
+
+- **Activation** : Use `CameraControl.enableLowLightBoostAsync`.
+- **Implementation** :
+
+ ```kotlin
+ // Enable Low Light Boost (LLB) natively in CameraX 1.4+
+ camera.cameraControl.enableLowLightBoostAsync(true)
+ ```
+- **Monitoring** : Observe `CameraInfo.lowLightBoostState` to track when the hardware actively applies the enhancement.
+
+### Google Play services LLB
+
+It's a multi-step implementation that uses a session-based `SurfaceProcessor`.
+
+**Dependency** : `com.google.android.gms:play-services-camera-low-light-boost`
+
+To implement Google Play services LLB, follow these core steps:
+
+1. **Initialize client** : `val client = LowLightBoost.getClient`.
+2. **Implement `SurfaceProcessor`** :
+ - **Manage session** : Call `client.createSession`.
+ - **Forward required metadata** : Observe the camera's `TotalCaptureResult` stream and forward every result to the session: `session.processCaptureResult`.
+ - **Provide surface** : Get the input surface from the session, `session.getCameraSurface`, and provide it to the camera's `SurfaceRequest`.
+ - **Lifecycle** : Release the session, `session.release`, when the processor is closed or the `SurfaceRequest` completes.
+3. **Wire using `CameraEffect`** :
+
+ ```kotlin
+ val effect = SimpleCameraEffect(
+ CameraEffect.PREVIEW or CameraEffect.VIDEO_CAPTURE,
+ executor,
+ llbSurfaceProcessor
+ ) { throw it }
+
+ // Add to UseCaseGroup
+ val useCaseGroup = UseCaseGroup.Builder()
+ .addUseCase(preview)
+ .addUseCase(videoCapture)
+ .addEffect(effect)
+ .build()
+ ```
+4. **Scene detection** : Use `session.setSceneDetectorCallback` to receive `boostStrength` updates for real-time UI indicators.
+
+*** ** * ** ***
+
+## Implementation notes
+
+- **Thread safety** : Always handle `ExtensionsManager` and `LowLightBoostClient` initialization asynchronously.
+- **FPS trade-offs**: AE mode LLB often drops the frame rate significantly to increase brightness.
+- **Compatibility** : Extensions, Night Mode, possibly conflict with `ConcurrentCamera`. Always verify support before binding.
\ No newline at end of file
diff --git a/camera/camerax/references/mlkit-spatial.md b/camera/camerax/references/mlkit-spatial.md
new file mode 100644
index 0000000..833daf1
--- /dev/null
+++ b/camera/camerax/references/mlkit-spatial.md
@@ -0,0 +1,75 @@
+When you use ML Kit for features such as face mesh, object detection, or pose
+detection, the most common failure point is the coordinate disparity between the
+analysis image and the viewfinder UI.
+
+## The mapping mindset
+
+| Dimension | Analysis frame | Viewfinder UI on the screen |
+|---|---|---|
+| Resolution | Fixed, such as 640x480 | Dynamic, such as 1080x2400 |
+| Rotation | 0° for the raw buffer | 90° or 270° in portrait or landscape mode |
+| Origin | Top-left of buffer at (0,0) | Top-left of screen at (0,0) |
+
+*** ** * ** ***
+
+## Follow the implementation guide
+
+### Coordinate transformation matrix
+
+Android provides the `Viewport` and `UseCaseGroup` APIs to calculate the
+transformation matrix automatically. **Don't** calculate aspect ratio scaling
+manually.
+
+
+```kotlin
+val transform = previewView.viewPort?.let { viewPort ->
+ // Use CameraX's built-in coordinate mapper
+ viewPort.getTransformationMatrix(imageProxy.imageInfo.rotationDegrees)
+}
+```
+
+
+
+### Handling the "double rotation" bug
+
+ML Kit results, bounding boxes, are relative to the **rotated buffer**. If the
+device is in portrait, the buffer is often 480x640, landscape, but the screen
+is 1080x1920.
+
+To map the coordinates, use the following workflow:
+
+1. Query `imageProxy.imageInfo.rotationDegrees`.
+2. Pass this rotation to the ML Kit `InputImage`.
+3. Use the `MappingUtils.transformRect` method to map the result `Rect` to the screen.
+
+### Face mesh and pose normalization
+
+For high-precision spatial analysis, for example, "Is the user's hand at a
+specific screen button?", use **normalized coordinates from 0.0 to 1.0**.
+
+
+```kotlin
+// Example: Converting a Pose landmark to a Screen Coordinate
+val screenX = landmark.position.x / analysisWidth * screenWidth
+val screenY = landmark.position.y / analysisHeight * screenHeight
+```
+
+
+
+**Warning** : Always account for **mirrored lenses** . If the `LENS_FACING_FRONT`
+is used, you must flip the X-coordinate: `actualX = screenWidth - screenX`.
+
+### Overlays and canvas clipping
+
+Use a custom `GraphicOverlay` view on top of the `PreviewView`.
+
+- **Buffer lock** : Ensure your `GraphicOverlay` clears its canvas every time a new `ImageAnalysis` frame is processed to prevent "ghosting" of bounding boxes.
+
+*** ** * ** ***
+
+## Spatial pitfalls
+
+- **The "stretched box" bug** : Caused by assuming the Analysis Frame aspect ratio, 4:3, matches the screen aspect ratio, 21:9. Use `PreviewView.SCALE_TYPE_FILL_CENTER` and map coordinates accordingly.
+- **Latency** : If ML processing exceeds 50 ms, the bounding box trails behind the user's face.
+ - **Fix** : Use `ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST` to avoid queuing stale frames.
+- **Sensor vs. display rotation** : On some tablets, the sensor is mounted horizontally. Always query `display.rotation` and `cameraInfo.sensorRotationDegrees`.
\ No newline at end of file
diff --git a/camera/camerax/references/modern-apis.md b/camera/camerax/references/modern-apis.md
new file mode 100644
index 0000000..616d549
--- /dev/null
+++ b/camera/camerax/references/modern-apis.md
@@ -0,0 +1,42 @@
+Always prefer these various abstractions over legacy Camera2 or early CameraX
+implementations.
+
+## Compare APIs
+
+| Use case | Legacy or verbose way | Recommendation |
+|---|---|---|
+| QR or face scanning | `ImageAnalysis.Analyzer` and manual ByteBuffer math | **`MlKitAnalyzer`**, which automates coordinate mapping and multi-format support |
+| Post-processing | Custom OpenGL shaders or `SurfaceTexture` | **`Media3Effect`** for composable, declarative effects |
+| Dual camera | Manual binding of two UseCases | **`ConcurrentCamera`**, which provides built-in support in CameraX 1.3 and higher |
+| High dynamic range | Manual bit-depth and profile config | **`DynamicRange`** , which uses `DYNAMIC_RANGE_HLG10` or `SDR` |
+| Zoom and focus | `Camera2Interop` for CameraX-to-Camera2 mapping | **`CameraControl.setZoomRatio`** or **`setLinearZoom`** |
+
+## Hardware awareness
+
+Modern APIs abstract away the complexity of hardware diversity.
+
+- **`CameraSelector`** : Use `DEFAULT_BACK_CAMERA` or `DEFAULT_FRONT_CAMERA` instead of hardcoding camera IDs. Use `filter` if you need specific lens capabilities.
+- **Extensions** : Before enabling advanced modes, such as night, bokeh, and face retouch, use `ExtensionsManager` to query whether the device supports them.
+- **Foldables** : Observe `Lifecycle` and `Viewport` updates to handle posture changes, like a half-opened posture, on foldable devices.
+
+## Required dependencies
+
+Add the following dependencies to your `libs.versions.toml` file:
+
+ # CameraX ML Kit
+
+ androidx-camera-mlkit-vision = { group = "androidx.camera", name =
+ "camera-mlkit-vision", version.ref = "camerax" }
+
+ # Media3 effects
+
+ androidx-media3-effect = { group = "androidx.media3", name = "media3-effect",
+ version.ref = "media3" }
+
+ # Camera extensions
+
+ androidx-camera-extensions = { group = "androidx.camera", name =
+ "camera-extensions", version.ref = "camerax" }
+
+Refer to the official [CameraX Release Notes](https://developer.android.com/jetpack/androidx/releases/camera) for the
+stable versions.
\ No newline at end of file
diff --git a/camera/camerax/references/testing.md b/camera/camerax/references/testing.md
new file mode 100644
index 0000000..972f285
--- /dev/null
+++ b/camera/camerax/references/testing.md
@@ -0,0 +1,69 @@
+Automated testing for camera features is notoriously difficult because you
+can't easily mock physical hardware, lighting, or motion. This guide provides
+patterns for reliable, hermetic camera tests.
+
+## Develop a testing mindset
+
+| Challenge | Conventional approach | Camera technical approach |
+|---|---|---|
+| **Frameworks** | `Mockito` or `MockK` | **Fakes over mocks** |
+| **Assertions** | `assertEquals`, `assertTrue` | **Google Truth, `assertThat`** |
+| **Environment** | Implied environments | **Explicit `@RunWith` annotations** |
+| **Async operations** | `Thread.sleep` | **Explicit `timeoutMillis` or `IdlingResource`** |
+
+*** ** * ** ***
+
+## Follow the implementation guide
+
+### Fakes over mocks
+
+**Don't use Mockito.** Relying on mocks for complex, rapidly changing interfaces
+like `ImageProxy` or `CameraInfo` makes tests brittle. Instead, build "Fake"
+implementations that verify state rather than behavior.
+
+
+```kotlin
+// Create a Fake ImageProxy for ML Testing (Fakes over Mocks)
+val fakeImage = FakeImageProxy(w = 640, h = 480)
+
+// Feed the fake buffer into your analyzer
+```
+
+
+
+### Mock camera capabilities
+
+Use `FakeAppConfig` from `androidx.camera:camera-testing` to simulate specific
+hardware constraints in tests, such as a device without a flash.
+
+
+```kotlin
+// Use awaitInstance() extension function for coroutine-based provider retrieval
+val cameraProvider = ProcessCameraProvider.awaitInstance(context)
+```
+
+
+
+### Use Truth assertions
+
+Use Google Truth, `assertThat`, instead of standard JUnit assertions. It
+provides more readable assertion chains and useful failure messages.
+
+### Test asynchronous lifecycles
+
+Camera initialization is asynchronous. Use `IdlingResource` to ensure
+your test waits for the `UseCase` to be bound before asserting.
+
+To test asynchronous lifecycles, use the following pattern:
+
+1. Wrap the `ProcessCameraProvider` initialization in a `CountDownLatch` or `IdlingResource`.
+2. Assert only after the `cameraControl` instance is non-null.
+
+*** ** * ** ***
+
+## Testing pitfalls
+
+- **Resource leaks** : To prevent "Camera in Use" errors, in your `@After` block, call `cameraProvider.unbindAll`.
+- **The "flaky initializer"**: Camera tests often fail on CI because the "Virtual Camera" takes too long to warm up. Use a sufficient explicit timeout for the first initialization.
+- **Permission blockers** : To bypass the system permission dialogs, in your Espresso tests, use `GrantPermissionRule`.
+- **Resolution mismatch** : Tests on emulators often default to 640x480. Ensure your `ResolutionSelector` handles this low-res fallback correctly.
\ No newline at end of file
diff --git a/camera/camerax/references/thermals.md b/camera/camerax/references/thermals.md
new file mode 100644
index 0000000..41cfe19
--- /dev/null
+++ b/camera/camerax/references/thermals.md
@@ -0,0 +1,85 @@
+Camera operations are among the most power-intensive tasks on mobile devices.
+Without proactive management, the system throttle hardware, drop frames, or
+force-close the camera app.
+
+## The thermal management strategy
+
+| Priority | Strategy | Implementation |
+|---|---|---|
+| **1. Inform** | Use case hints | Provide `StreamUseCase` to allow the OS to optimize hardware. |
+| **2. Monitor** | Thermal state listener | Observe `PowerManager.addThermalStatusListener`. |
+| **3. Act** | Graceful degradation | Dynamically reduce FPS, resolution, or disable demanding effects such as HDR. |
+
+*** ** * ** ***
+
+## Follow the implementation guide
+
+### Stream use case optimization
+
+Android 13 (API level 33) introduced `StreamUseCase`. This is the **single most
+effective** way to tell the hardware how to balance quality versus power.
+
+
+```kotlin
+// In CameraX: Set the hint on your Use Case
+val preview = Preview.Builder()
+ .setTargetName("Preview")
+ .apply {
+ Camera2Interop.Extender(this).setStreamUseCase(
+ CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong()
+ )
+ }
+ .build()
+```
+
+
+
+Review the following key use cases for stream optimization:
+
+- `PREVIEW`: This option is the default and provides a balanced configuration.
+- `STILL_CAPTURE`: This option provides high-quality capture for short bursts.
+- `VIDEO_RECORD`: This option maintains sustained power and is optimized for encoding.
+- `VIDEO_CALL`: This option minimizes power consumption for long-duration sessions.
+
+### Monitor thermal status
+
+Don't wait for a crash. Monitor the `PowerManager` status and react before
+`THERMAL_STATUS_CRITICAL`.
+
+
+```kotlin
+val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
+powerManager.addThermalStatusListener { status ->
+ when (status) {
+ PowerManager.THERMAL_STATUS_MODERATE -> {
+ // Signal to UI: "Device is warming up"
+ }
+ PowerManager.THERMAL_STATUS_SEVERE -> {
+ // ACTION: Reduce Frame Rate from 60fps to 30fps
+ // ACTION: Disable HDR or High-Quality Post-processing
+ }
+ PowerManager.THERMAL_STATUS_CRITICAL -> {
+ // ACTION: Close the camera session to prevent hardware damage
+ }
+ }
+}
+```
+
+
+
+### Graceful degradation tiers
+
+| Tier | Action | User impact |
+|---|---|---|
+| **Mild** | Stop background analysis using ML Kit | Minimal |
+| **Moderate** | Cap frame rate to 30 FPS | Noticeable but smooth |
+| **Severe** | Drop resolution from 1080p to 720p | Significant visual change |
+| **Critical** | Shut down the session | App unusable in safe mode |
+
+*** ** * ** ***
+
+## Thermal pitfalls
+
+- **The "double work" bug**: Don't run two high-resolution streams---such as a preview and a video capture stream---at different aspect ratios unless necessary. This forces the image signal processor (ISP) to perform double the scaling work, which generates excessive heat.
+- **Surface overload** : Don't use multi-step `SurfaceProcessor` or `Media3Effect` chains during `THERMAL_STATUS_SEVERE`.
+- **Flash heat** : Flash or torch usage generates high thermal load. Proactively disable the flash if thermal status is `SEVERE`.
\ No newline at end of file
diff --git a/camera/camerax/references/wear-os.md b/camera/camerax/references/wear-os.md
new file mode 100644
index 0000000..4a942db
--- /dev/null
+++ b/camera/camerax/references/wear-os.md
@@ -0,0 +1,76 @@
+Developing camera features for Wear OS is rarely about the watch's own lens, if
+it even has one. It's almost always about creating a **Remote Viewfinder** to
+control the phone's camera.
+
+## The Wear OS remote mindset
+
+| Feature | Phone camera app | Wear OS remote app |
+|---|---|---|
+| **Screen** | Rectangular (Large) | **Circular and less than 2 inches in size** |
+| **Connectivity** | Local Hardware | **Bluetooth or Wi-Fi data layer API** |
+| **Latency** | Direct and less than 20 ms | **Networked, 100 ms to 500 ms** |
+| **Interaction** | Multi-touch gestures | **Rotary input or single taps** |
+
+*** ** * ** ***
+
+## Follow the implementation guide
+
+### The circular UI challenge
+
+Wear OS devices are often round. Standard rectangular layouts clip corner
+buttons.
+
+Follow these blueprint recommendations:
+
+- Use `Horologist` or `Wear Compose` libraries.
+- Use `ScalingLazyColumn` for lists so that items stay within the "safe zone" of the circular display.
+- **Preview scaling**: Crop the center of the rectangular phone viewfinder to fit the circular watch screen.
+
+### Stream the viewfinder
+
+You can't send a raw 60 fps stream over Bluetooth. compress and throttle.
+
+
+```kotlin
+// Example: Sending a viewfinder frame to the watch
+val bitmap = previewView.bitmap // Capture current frame
+if (bitmap != null) {
+ val compressed = compressToJpeg(bitmap, quality = 50)
+ val request = PutDataMapRequest.create("/camera/preview").apply {
+ dataMap.putAsset("image", Asset.createFromBytes(compressed))
+ }
+ Wearable.getDataClient(context).putDataItem(request.asPutDataRequest())
+}
+```
+
+
+
+**Optimization** : Cap the watch preview at **10-15 fps** to preserve battery and
+bandwidth.
+
+### Remote triggers and syncing
+
+Use the `MessageClient` for low-latency commands like "Take Photo" or "Switch
+Camera."
+
+
+```kotlin
+// Watch sends a trigger to the phone
+Wearable.getMessageClient(context).sendMessage(nodeId, "/camera/capture", null)
+```
+
+
+
+### Rotary input support
+
+On devices that support it, use the physical crown, Rotary Input, to control
+**Zoom** or **Exposure**.
+
+*** ** * ** ***
+
+## Wear OS pitfalls
+
+- **Corner clipping** : Placing a **Close** button in the top-right corner of a square layout makes it impossible to tap the button on a round watch display.
+- **Battery drain**: Sustained Bluetooth data transfer, viewfinder sync, drains watch battery. Proactively close the remote app if the phone screen is turned off.
+- **Node discovery** : The phone is possibly connected to multiple "Nodes" (watches, earbuds, or tablets). Ensure your `CapabilityClient` filters for the specific `camera_remote_host` capability.
+- **Disconnect handling**: If the watch disconnects, the phone camera must stop its high-power preview to save energy.
\ No newline at end of file
diff --git a/camera/camerax/references/xr.md b/camera/camerax/references/xr.md
new file mode 100644
index 0000000..b699979
--- /dev/null
+++ b/camera/camerax/references/xr.md
@@ -0,0 +1,55 @@
+Developing camera features for XR devices, headsets, and AR glasses requires a
+shift from 2D pixel-pushing to 3D spatial awareness.
+
+## Understand the XR development mindset
+
+| Concept | Mobile focus | **XR focus** |
+|---|---|---|
+| **Input** | Raw camera stream | **Spatial tracking (visual-inertial odometry (VIO) or simultaneous localization and mapping (SLAM))** |
+| **Output** | Screen viewfinder | **Stereo passthrough and occlusion** |
+| **Constraint** | Battery life | **Motion-to-photon latency (less than 20 ms)** |
+
+*** ** * ** ***
+
+## Follow the implementation guide
+
+### API selection
+
+On XR devices, standard `CameraX` implementations are often restricted or
+insufficient. Always use spatial software development kits (SDKs):
+
+- **ARCore**: Use ARCore for plane detection, depth sensing, and motion tracking.
+- **OpenXR**: Use OpenXR as the cross-platform standard for VR and AR rendering and input.
+- **OEM SDKs**: Use manufacturer-specific libraries for hardware-accelerated passthrough.
+
+### Handle spatial passthrough
+
+Unlike a 2D viewport, XR passthrough is often system-managed.
+
+**\[Key requirement\] Frame synchronization**: Synchronize your application's
+frame clock with the headset's head-mounted display (HMD) pose.
+
+
+```kotlin
+// Example: Querying the spatial pose for the current camera frame
+val headPose = xrSession.getHeadPose(frameTime)
+val projectionMatrix = headPose.getProjectionMatrix(eyeIndex)
+```
+
+
+
+### Manage depth and occlusion
+
+Digital content must respect real-world depth to ensure accurate occlusion.
+
+- **Depth map** : Access raw depth data using `ARCore` or `SurfaceProcessor` to create an occlusion mask.
+- **Hardware buffers** : Use `HardwareBuffer` to share camera frames directly with the GPU without CPU-side copies to minimize latency.
+
+*** ** * ** ***
+
+## XR pitfalls
+
+- **The nausea limit**: Any processing that delays the viewfinder by more than 20 ms causes user sickness. Don't perform image processing on the main thread.
+- **Privacy restrictions** : Some XR devices return a black frame if you attempt to record the "Passthrough" layer. Check `Session.isRecordingSupported`.
+- **Field of view (FOV)**: The camera FOV possibly doesn't match the display FOV. Use the SDK's projection matrixes instead of calculating aspect ratios manually.
+- **Front buffer rendering** : If the device supports it, use `FrontBufferRenderer` for real-time overlays to bypass standard double-buffering latency.
\ No newline at end of file
diff --git a/cars/android-for-cars-parked-apps/SKILL.md b/cars/android-for-cars-parked-apps/SKILL.md
new file mode 100644
index 0000000..51e39e5
--- /dev/null
+++ b/cars/android-for-cars-parked-apps/SKILL.md
@@ -0,0 +1,279 @@
+---
+name: android-for-cars-parked-apps
+description: Adapt existing Android video streaming apps, games, and web browsers
+ to run as parked apps on Android Auto and Android Automotive OS. Covers manifest
+ configuration, Google Play hardware feature declarations, compatibility mode, back
+ navigation affordances, orientation and display continuity, controllable system
+ bar insets, irregular display cutouts, driver distraction lifecycle handling, MediaSession
+ playback restrictions with CarUxRestrictionsManager, background audio while driving,
+ and browser sensitive data protection. Use this skill when migrating, building,
+ or auditing parked apps for cars to meet Car App Quality guidelines. Don't use for
+ media audio apps, templated Car App Library navigation, point of interest, internet
+ of things, weather, or communication apps designed for use while driving.
+license: Complete terms in LICENSE.txt
+metadata:
+ author: Google LLC
+ last-updated: '2026-10-09'
+ keywords:
+ - Android Auto
+ - Android Automotive OS
+ - parked apps
+ - video
+ - games
+ - browsers
+ - adaptive
+---
+
+Adapt existing Android video apps, games, and web browsers to run as parked
+apps on Android Auto and Android Automotive OS. Follow these steps to meet the
+car app quality guidelines for Google Play distribution.
+
+## Prerequisites
+
+- Begin with an existing adaptive Android app in one of the supported parked app categories: video streaming apps, games, or web browsers.
+
+## Glossary
+
+- **Parked apps**: Parked apps run on Android Auto and Android Automotive OS only while the vehicle is parked. The three parked categories are video, games, and browsers; other categories can't be published as parked apps.
+- **Android Auto**: Android Auto is a phone-powered projection system. Apps execute on the user's phone and are projected to the in-car display over a wired or wireless connection. Android Auto is a separate platform from Android Automotive OS.
+- **Android Automotive OS**: Android Automotive OS, or AAOS, is a variant of Android that runs directly on the vehicle itself. Android Automotive OS is a separate platform from Android Auto.
+- **Video category**: Video apps display streaming videos while parked. Generally available on Android Automotive OS; unsupported on Android Auto.
+- **Games category**: Game apps provide gameplay while parked. Generally available on Android Automotive OS and Android Auto.
+- **Browsers category**: Web browsers let users browse the web while parked. Available in beta on Android Automotive OS; unsupported on Android Auto.
+- **Media category**: Not a parked category. Media apps play audio content (music, radio, audiobooks, excluding video) while driving or parked.
+- **Android for Cars platform**: Android for Cars is an umbrella term that consists of Android Auto and Android Automotive OS.
+- **Generally available categories**: Permitted on internal testing, closed testing, open testing, and production Google Play tracks for a supported platform.
+- **Beta categories**: Permitted on internal or closed testing tracks; open testing and production tracks require acceptance into an early access program.
+- **Audio while driving**: A beta feature on Android Automotive OS that lets video apps play audio while the vehicle is in motion.
+- **Car app quality guidelines** : When auditing an app against the Tier 3 (*Car ready* ), Tier 2 (*Car optimized* ), or Tier 1 (*Car differentiated* ) criteria, **MUST** check [car app quality guidelines](references/android/docs/quality-guidelines/car-app-quality/index.md).
+- **Android Automotive OS compatibility mode** : Compatibility mode (`android.software.car.display_compatibility`) is a system feature on select vehicles providing a system back affordance, safe area rendering, density scaling, and opaque driving-mode blocking (moving activities to *Stopped*). Compatibility mode is only relevant to apps that support Android Automotive OS; it isn't relevant for apps that only support Android Auto.
+
+## Codebase exploration
+
+Search the codebase for `AndroidManifest.xml` (or create
+`Assets/Plugins/Android/AndroidManifest.xml` in Unity projects),
+`res/xml/automotive_app_desc.xml`, `build.gradle`, `build.gradle.kts`,
+or `gradle/libs.versions.toml` files. Locate existing video players,
+`MediaSession` classes, `requestedOrientation` calls, and `WindowInsets`
+handlers. Unless instructed otherwise by the user, ask permission before adding
+dependencies.
+
+## Step 1: Configure the app manifest
+
+Declare the app category, target platforms, hardware feature requirements, and
+activity configuration in `AndroidManifest.xml`.
+
+### 1.1: Identify the app category
+
+- **Video apps** : Video apps **MUST** set `android:appCategory="video"` on `` and **MUST NOT** include `app/src/main/res/xml/automotive_app_desc.xml` with `uses name="video"`.
+- **Games** : Games **MUST** set `android:appCategory="game"` on ``. Declare `android.hardware.gamepad` with `android:required="false"` if controller input is supported by the app.
+- **Browsers** : Browsers **MUST** include `` in the `MAIN` `` of an exported `` (`android:exported="true"`).
+
+### 1.2: Identify target platforms and hardware features
+
+- **Android Auto** : Apps targeting Android Auto (Android 15+) **MUST** add `` to an `` in the `AndroidManifest.xml` file. Add it to the `MAIN` `` unless the user specifies a different intent filter. **NEVER** add this category if the app is solely targeting Android Automotive OS.
+- **Android Automotive OS** : Apps targeting Android Automotive OS **MUST** declare `` (`"false"` for mobile or multi-form-factor tracks; `"true"` only if the app will be published on the dedicated Automotive OS track) in the `AndroidManifest.xml` file. **NEVER** add this feature tag if the app is solely targeting Android Auto.
+- **Unrequired hardware features (`DO-1`)** : To satisfy Google Play feature requirements and `DO-1`, declare unsupported features (`android.hardware.wifi`, `android.hardware.screen.portrait`, `android.hardware.screen.landscape`, `android.hardware.camera`) with `android:required="false"`, and remove `android:required="true"` on features absent in cars.
+- **Display continuity (`LS-C1`)** : Configure `android:configChanges` for `screenSize`, `smallestScreenSize`, `orientation`, `screenLayout`, and `density` on `` so moving to or from a distant display does not recreate the activity or stutter playback.
+- **Compatibility mode and distraction** : Declaring compatibility mode is optional and is only relevant for apps that support Android Automotive OS. **NEVER** add compatibility mode for apps that only support Android Auto. Adding `android.hardware.type.automotive` disables compatibility mode unless `` is added to ``. When an app declares `android.software.car.display_compatibility` in `AndroidManifest.xml`, the app **MUST** check `PackageManager.hasSystemFeature("android.software.car.display_compatibility")` at runtime and hide duplicate in-app back or close affordances when it returns `true`. Parked apps **MUST NOT** include `` on any `` (`DD-2`, `DD-3`).
+
+## Step 2: Provide back navigation and orientation support
+
+### 2.1: Add in-app back and close affordances
+
+Android Automotive OS vehicles aren't required to have a hardware or software
+back button or gesture navigation (`AN-1`). Provide explicit UI back and close
+controls on every non-root screen, including detail screens and full-screen
+players:
+
+- **If `AndroidManifest.xml` does not declare
+ `android.software.car.display_compatibility`**: Display explicit in-app back and close controls unconditionally.
+- **If `AndroidManifest.xml` declares
+ `android.software.car.display_compatibility` with `android:value="true"`** : Show in-app back and close controls only when `PackageManager.hasSystemFeature("android.software.car.display_compatibility")` returns `false`.
+
+### 2.2: Guard runtime orientation requests
+
+Vehicle displays have fixed orientations. Requesting landscape on a
+fixed-portrait car display causes letterboxing or flickering loops (`DO-1`).
+Remove hardcoded `android:screenOrientation` manifest attributes and check
+`PackageManager.FEATURE_SCREEN_LANDSCAPE` and
+`PackageManager.FEATURE_SCREEN_PORTRAIT` before setting `requestedOrientation`.
+
+## Step 3: Adapt to system bars and irregular display cutouts
+
+### 3.1: Track controllable system bar insets
+
+On Android Automotive OS, OEMs are permitted to lock system bars visible so
+climate and vehicle controls remain accessible (`AR-1`). Track controllable
+insets with `OnControllableInsetsChangedListener`, hide only controllable bars,
+and apply `windowInsetsPadding` for uncontrollable `statusBars`,
+`navigationBars`, and `displayCutout` insets.
+
+### 3.2: Render into irregular display cutouts safely
+
+To meet Tier 1 criterion `AR-2` on curved or non-rectangular car displays,
+**NEVER** use `LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT` (`0`) or
+`LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES` (`1`). Configure
+`LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS` (`3`) or
+`LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER` (`2`) using the `car` resource qualifier
+in `res/values-car-v30/integers.xml`:
+
+
+```xml
+3
+```
+
+
+
+Protect interactive UI, app bars, and scrollable lists with
+`WindowInsets.safeDrawing`.
+
+## Step 4: Meet driver distraction and media playback requirements
+
+### 4.1: Pause playback on lifecycle transitions and block driving resumption
+
+When a vehicle begins moving, the OS covers parked activities with a blocking
+overlay and triggers `onPause` (and `onStop` on compatibility mode vehicles). To
+satisfy `DD-2`, `DD-3`, and `MC-1`:
+
+1. Pause playback on `ON_PAUSE` or `ON_STOP` using `LifecycleEventEffect` from `androidx.lifecycle:lifecycle-runtime-compose`.
+2. Expose a `MediaSession` (`androidx.media3:media3-session`) with play, pause, or stop commands and item metadata (`MC-1`), and release it in `onCleared`.
+3. Add `useLibrary("android.car")` in `build.gradle.kts`, wrap `ExoPlayer` in `ForwardingSimpleBasePlayer`, listen to `CarUxRestrictionsManager`, pause playback when `isRequiresDistractionOptimization` becomes `true`, and remove `COMMAND_PLAY_PAUSE` in `getState` so external `MediaSession` controls can't resume playback while driving:
+
+
+```kotlin
+@UnstableApi
+class CarAwarePlayer(context: Context) :
+ ForwardingSimpleBasePlayer(ExoPlayer.Builder(context).build()) {
+ private var shouldPreventPlay = false
+ private var pausedByUxRestrictions = false
+ private lateinit var carUxRestrictionsManager: CarUxRestrictionsManager
+
+ init {
+ if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ val car = Car.createCar(context)
+ carUxRestrictionsManager =
+ car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager
+ shouldPreventPlay =
+ carUxRestrictionsManager.currentCarUxRestrictions.isRequiresDistractionOptimization
+ invalidateState()
+
+ carUxRestrictionsManager.registerListener { restrictions: CarUxRestrictions ->
+ shouldPreventPlay = restrictions.isRequiresDistractionOptimization
+ if (!shouldPreventPlay && pausedByUxRestrictions) {
+ handleSetPlayWhenReady(true)
+ invalidateState()
+ } else if (shouldPreventPlay && isPlaying) {
+ pausedByUxRestrictions = true
+ handleSetPlayWhenReady(false)
+ invalidateState()
+ }
+ }
+ }
+ }
+
+ override fun getState(): State {
+ val state = super.getState()
+ return state.buildUpon()
+ .setAvailableCommands(
+ state.availableCommands.buildUpon()
+ .removeIf(COMMAND_PLAY_PAUSE, shouldPreventPlay)
+ .build()
+ )
+ .build()
+ }
+
+ override fun handleRelease(): ListenableFuture<*> {
+ if (::carUxRestrictionsManager.isInitialized) {
+ carUxRestrictionsManager.unregisterListener()
+ }
+ return super.handleRelease()
+ }
+}
+```
+
+
+
+### 4.2: Support background audio while driving (optional beta feature)
+
+Because this is an optional beta feature, **NEVER** implement this feature
+unless the user requests it. To implement the feature:
+
+1. Declare `` in `AndroidManifest.xml`, along with `FOREGROUND_SERVICE` and `FOREGROUND_SERVICE_MEDIA_PLAYBACK` permissions.
+2. Check runtime vehicle support with `CarFeatures.isFeatureEnabled(context,
+ CarFeatures.FEATURE_BACKGROUND_AUDIO_WHILE_DRIVING)` from `androidx.car.app:app`. Only set `shouldPreventPlay` when `!isBackgroundAudioWhileDrivingSupported &&
+ restrictions.isRequiresDistractionOptimization`.
+3. Host the `MediaSession` and player inside an exported `MediaSessionService` with `android:foregroundServiceType="mediaPlayback"`, and connect from the UI using `MediaController.Builder` and `buildAsync`.
+
+## Step 5: Protect sensitive data in browser apps
+
+Because vehicles are shared devices, browsers **MUST** block access to
+saved passwords and payment methods unless protected by a profile lock (`SD-1`).
+Before syncing passwords or payment data to the vehicle, browsers **MUST**
+prompt the user to authenticate and display a notice on the car screen stating
+that their data will be synchronized to the car (`SD-2`).
+
+Check whether a PIN, pattern, or password is set using `KeyguardManager`, open
+`Settings.ACTION_SECURITY_SETTINGS` if unset, and authenticate with
+`BiometricPrompt` using `BiometricManager.Authenticators.DEVICE_CREDENTIAL`
+only (vehicles don't have biometric hardware):
+
+
+```kotlin
+val keyguardManager = context.getSystemService()
+val isDeviceSecure = keyguardManager?.isDeviceSecure == true
+lateinit var biometricPrompt: BiometricPrompt
+
+if (!isDeviceSecure) {
+ context.startActivity(
+ Intent(Settings.ACTION_SECURITY_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ )
+} else {
+ val promptInfo = BiometricPrompt.PromptInfo.Builder()
+ .setTitle(context.getString(R.string.auth_title))
+ .setSubtitle(context.getString(R.string.sync_data_to_car_notice))
+ .setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+ .build()
+ biometricPrompt.authenticate(promptInfo)
+}
+```
+
+
+
+## Step 6: Verify implementation
+
+Build the app using `./gradlew assembleDebug`. Find and fix any build errors.
+
+## Post-implementation guidance
+
+Provide this guidance to the user after implementation:
+
+- For Android Auto apps, install the app on a phone and test using the Desktop Head Unit (DHU).
+- For Android Automotive OS, install the app on the Android Automotive OS emulator in Android Studio.
+- If the app is in a beta category for the target platform, acceptance into the early access program is required before submitting the app to an open testing or production track.
+- If the app implements the beta audio while driving feature, acceptance into the early access program is required before submitting the app to an open testing or production track.
+- Test the app against the relevant criteria in the car app quality guidelines before submitting the app.
+
+## Antipatterns
+
+- **NEVER** include `` on any `` in a parked app, or include `res/xml/automotive_app_desc.xml` with `uses name="video"` in a video app.
+- **NEVER** assume a hardware or software back button exists on Android Automotive OS.
+- **NEVER** assume system bars are positioned only at the top and bottom or can always be hidden.
+- **NEVER** use `LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT` or `LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES` on Android Automotive OS.
+- **NEVER** infer UX restrictions from vehicle speed or gear state instead of reading `isRequiresDistractionOptimization` from `CarUxRestrictionsManager`.
+- **NEVER** use biometric authenticators (`BIOMETRIC_STRONG`, `BIOMETRIC_WEAK`) with `BiometricPrompt` on browser apps on Android Automotive OS; use `DEVICE_CREDENTIAL` only.
+- **NEVER** add the `CAR_LAUNCHER` intent category if the user only requests Android Automotive OS support.
+- **NEVER** add the `android.hardware.type.automotive` feature tag if the user only requests Android Auto support.
+
+## Best practices
+
+- **MUST** declare `android:required="false"` on unsupported hardware features (`wifi`, `screen.portrait`, `screen.landscape`, `camera`) and guard `requestedOrientation` calls by checking `FEATURE_SCREEN_LANDSCAPE` and `FEATURE_SCREEN_PORTRAIT` (`DO-1`).
+- **MUST** provide in-app back and close affordances (`AN-1`) for Android Automotive OS when not in compatibility mode and protect all interactive UI and text from system bars and display cutouts using `WindowInsets.safeDrawing` and `OnControllableInsetsChangedListener` (`AR-1`, `AR-2`).
+- **MUST** pause playback when UX restrictions become active and strip `COMMAND_PLAY_PAUSE` from `MediaSession` while driving unless audio while driving is supported and enabled (`DD-2`, `DD-4`, `MC-1`).
+- **MUST** require profile lock authentication and display an on-screen sync disclosure before saving, accessing, or syncing browser passwords or payment information (`SD-1`, `SD-2`).
diff --git a/cars/android-for-cars-parked-apps/references/android/docs/quality-guidelines/car-app-quality/index.md b/cars/android-for-cars-parked-apps/references/android/docs/quality-guidelines/car-app-quality/index.md
new file mode 100644
index 0000000..bda3fed
--- /dev/null
+++ b/cars/android-for-cars-parked-apps/references/android/docs/quality-guidelines/car-app-quality/index.md
@@ -0,0 +1,787 @@
+To provide a great experience for users in cars, complete the car compatibility
+checklists and tests that follow as you design and develop your app.
+
+The checklists and tests define an extensive set of quality requirements for the
+various app categories supported by Android Auto and Android Automotive OS. Many
+requirements apply only to specific categories, so be sure to filter by your
+app's category. See the [Car quality tiers](https://developer.android.com/docs/quality-guidelines/car-app-quality#car-quality-tiers) definitions
+for details on which guidelines your app must meet in order to be accepted on
+the Google Play Store.
+
+> [!NOTE]
+> **September 2026 update:** The Games app category is now generally available. See [September 2026](https://developer.android.com/docs/quality-guidelines/car-app-quality#sep-26) for details.
+
+## Test your app
+
+Test your app for the applicable criteria listed on this page before submitting
+your app to Google Play for review. As applicable, test both of the ways
+users can access your app:
+
+Android Automotive OS
+: Use the [Android Emulator](https://developer.android.com/studio/run/emulator) to validate each checklist
+ item. For more information, see [Test using the Android Automotive OS
+ emulator](https://developer.android.com/training/cars/testing/emulator).
+
+Android Auto
+: Use the Android Auto Desktop Head Unit (DHU) to validate each checklist item.
+ For more information, see [Test using the Desktop Head Unit](https://developer.android.com/training/cars/testing/dhu).
+
+## App categories
+
+> [!IMPORTANT]
+> **Key Point:** Select your app's categories to filter the guidelines on this page.
+
+| [Supported categories](https://developer.android.com/training/cars#supported-app-categories) | Android Auto | Android Automotive OS |
+|---|---|---|
+| Media | ✔ | ✔ |
+| Media (templated) labs | ✔ | ✔ |
+| Communication - messaging notifications | ✔ | |
+| Communication - templated messaging labs | ✔ | |
+| Communication - calling labs | ✔ | |
+| Navigation | ✔ | ✔ |
+| Point of Interest (POI) | ✔ | ✔ |
+| Internet of Things (IOT) | ✔ | ✔ |
+| Weather | ✔ | ✔ |
+| Video | | ✔ |
+| Games | ✔ | ✔ |
+| Browsers labs | | ✔ |
+
+## Car quality tiers
+
+The quality tiers define criteria to help you assess the level of support your
+app provides for cars. Each category builds upon those below it. That is, for an
+app to be *Car optimized* , it should also meet all of the applicable *Car ready*
+requirements.
+
+In addition to the guidelines specific to cars, each tier has associated
+guidelines from [Adaptive app quality guidelines](https://developer.android.com/docs/quality-guidelines/large-screen-app-quality) that
+are relevant for activities implemented by your app.
+
+Levels of support include the following:
+
+### Tier 3 - Car ready
+
+Your app is large screen compatible and can be used while the car is parked.
+While it may not have any car-optimized features, users can experience the app
+just as they would on any other large screen Android-powered device.
+
+This tier primarily applies to the [parked categories](https://developer.android.com/training/cars/parked) of apps,
+which must meet all of the requirements in this tier to be accepted on the
+Google Play Store.
+
+> [!NOTE]
+> **Note:** Through the [Car ready mobile apps program](https://developer.android.com/training/cars/car-ready-mobile-apps), mobile apps meeting the requirements in this tier can be made available for download from the Google Play Store on Android Automotive OS vehicles with Google built-in.
+
+### Tier 2 - Car optimized
+
+Your app provides a great experience on the car's center stack display. To
+accomplish this, your app has some car-specific capabilities that users can
+experience across driving or parked modes, depending on your app's category.
+
+Apps in [categories built for use while driving](https://developer.android.com/training/cars#supported-app-categories) must
+meet meet all of the applicable requirements in this tier to be accepted on the
+Google Play Store.
+
+### Tier 1 - Car differentiated
+
+Your app is built to work across the variety of hardware in cars and can adapt
+its experience across driving and parked modes. It provides the best user
+experience designed for the different screens in cars such as the center
+console, instrument cluster, and additional screens - like panoramic displays
+seen in many premium cars.
+
+### Car ready
+
+| Criteria | ID | Applicable Categories | Description |
+|---|---|---|---|
+| Permitted Categories | `PC-1` | All | The app must not include features outside the app types intended for cars. See [Supported app categories](https://developer.android.com/training/cars#supported-app-categories). |
+| Expected Performance | `EP-1` | All | The app must work as expected or described in the app's Google Play Store listing. |
+| Expected Performance | `EP-2` | Media, Messaging (templated), Calling, Navigation, POI, IOT, Weather, Video, Games, Browsers | When the app is relaunched from the home screen, the app must restore the app state as closely as possible to the previous state. |
+| Expected Performance | `EP-3` | Games | The app is responsive to input and does not freeze or stutter during gameplay. |
+| Expected Performance | `EP-4` | Media, Messaging (templated), Calling, Navigation, POI, IOT, Weather | **Android Automotive OS only:** The app listing on Google Play Store must include screenshots for the Android Automotive OS form factor. See [Provide Android Automotive OS screenshots](https://developer.android.com/training/cars/distribute#opt-in-aaos). Note: For apps in the parked app categories, screenshots are optional. However, if your app includes features unique to cars, or the app differs significantly from the experience on mobile or tablet, it is recommended to include screenshots in your Google Play Store listing. See [Provide Android Automotive OS screenshots](https://developer.android.com/training/cars/distribute#opt-in-aaos). |
+| Display Orientation | `DO-1` | Video, Games, Browsers | **Android Automotive OS only:** The app can be distributed to devices with fixed screen orientations. See [Required Android Automotive OS features](https://developer.android.com/training/cars/parked/automotive-os#required-features). |
+| Display Orientation | `DO-2` | Games | **Android Auto only:** The app is not significantly pillarboxed when running on landscape displays. See [Support common Android Auto screen sizes](https://developer.android.com/training/cars/parked/auto#support-screen-sizes). |
+| App Rendering | `AR-1` | All categories supported by Android Automotive OS | In activities implemented by the app, interactive UI elements must not be obstructed by system bars or display cutouts. See [Work with window insets and display cutouts](https://developer.android.com/training/cars/parked/automotive-os#insets-and-cutouts). |
+| Driver Distraction | `DD-3` | Video, Games, Browsers | The app must not be launchable or usable while driving and must not play any audio. See [Meet driver distraction requirements](https://developer.android.com/training/cars/parked/automotive-os#driver-distraction). Note: If your app plays audio, it must meet the [`DD-2`](https://developer.android.com/docs/quality-guidelines/car-app-quality#DD-2) requirement. |
+| Irrelevant notifications | `IN-2` | Video, Games, Browsers | The app must not post any [heads up notifications](https://developer.android.com/training/cars/platforms/automotive-os/notifications#hun). |
+| App Navigability | `AN-1` | Video, Games, Browsers | Users can navigate through the app without encountering any dead ends. |
+| Sensitive Data | `SD-1` | Browsers | Browsers must not save or allow access to passwords or payment information unless the [user can block access to passwords](https://developer.android.com/training/cars/parked/browser#block-sensitive) using a profile lock. |
+| Sensitive Data | `SD-2` | Browsers | Before syncing data to the car, browsers that synchronize passwords or payments data must do the following steps: 1. Prompt the user to authenticate. 2. Notify the user on the car screen their data will be synchronized to the car. For details, see [Allow users to block access to sensitive data](https://developer.android.com/training/cars/parked/browser#block-sensitive). |
+
+#### Associated large screen quality guidelines
+
+While these guidelines are relevant for all activities implemented by your app,
+they are Tier 3 requirements only for [parked apps](https://developer.android.com/training/cars/parked).
+
+| Criteria | ID | Guidance for cars |
+|---|---|---|
+| Configuration and continuity | [`LS-C1`](https://developer.android.com/docs/quality-guidelines/large-screen-app-quality#LS-C1) | Configuration changes in cars are primarily caused by moving an app between displays, such as when moving an app to or from a [distant display](https://developer.android.com/training/cars/testing/emulator/distant-display). |
+| Configuration and continuity | [`LS-C2`](https://developer.android.com/docs/quality-guidelines/large-screen-app-quality#LS-C2) | No additional guidance. |
+
+### Car optimized
+
+| Criteria | ID | Applicable Categories | Description |
+|---|---|---|---|
+| Parked Experiences | `PE-1` | Media, Navigation, POI, IOT, Weather | **Android Automotive OS only:** with the exception of providing setup, settings, and sign-in flows while parked, the app must not provide any functionality through its own activities. |
+| Screen Animation | `SA-1` | Media, Messaging, Calling, Navigation, POI, IOT, Weather | The app must not display animated elements on the screen, such as animated graphics or video. **Exception:** Canvas animations while the user is parked are allowed if they are relevant to the driving task. |
+| Visual or Text Ads | `AD-1` | Media, Messaging, Calling, Navigation, POI, IOT, Weather | The app must not display text-based advertising other than the advertiser's name or the product name. |
+| Image Usage | `IU-1` | Media, Messaging, Calling, Navigation, POI, IOT, Weather | App elements don't display any images on the screen. Exceptions include: - Apps can display a single static image for content context in the background of the consumption screen, such as album art or the advertiser's corporate logo. - Apps can display icons in the content navigation drawer. - Apps can display images and photographs to aid in driving decision-making. - Navigation apps can display an image for lane guidance or junction guidance. |
+| Visual Information on Phone | `VI-1` | Media, Messaging (templated), Calling, Navigation, POI, IOT, Weather | **Android Auto only:** If the user must go to the phone screen---for example, to act on a permission request---then the app must display a message instructing the user to only look at their phone screen when it's safe to do so. For more information, see [Handle general errors](https://developer.android.com/training/cars/media/errors) for media, and [Handle user input](https://developer.android.com/training/cars/apps#handle-user-input) for navigation, point of interest, internet of things, and weather apps. |
+| App Doesn't Crash | `AC-1` | Messaging (templated), Calling, Navigation, POI, IOT, Weather | Users must be able to complete tasks in the app using five screens or fewer. For more information, see [Template restrictions](https://developer.android.com/training/cars/apps#template-restrictions). |
+| Scrolling Text | `ST-1` | Media, Messaging, Calling, Navigation, POI, IOT, Weather | The app must not display automatically scrolling text. |
+| Voice Commands | `VC-1` | Media, Navigation | The app must support Gemini and Google Assistant voice commands. For more information, see [Support voice actions](https://developer.android.com/training/cars/media/voice-actions) for media, and [Support navigation intents](https://developer.android.com/training/cars/apps/navigation#support-navigation-intents) for navigation. |
+| Delayed Response | `DR-1` | Media, Messaging (templated), Calling, Navigation, POI, IOT, Weather | App-specific buttons must respond to user actions with no more than a two-second delay. |
+| Delayed Response | `DR-2` | Media, Messaging (templated), Calling, Navigation, POI, IOT, Weather | The app must launch in no more than 10 seconds. |
+| Delayed Response | `DR-3` | Media, Messaging (templated), Calling, Navigation, POI, IOT, Weather | The app must load content in no more than 10 seconds. |
+| Contrast | `VD-1` | Media, Messaging, Calling, Navigation, POI, IOT, Weather | The app must provide icons and colors that meet Android Auto's contrast requirements. For more information, see [Contrast](https://developers.google.com/cars/design/android-auto/design-system/color#contrast). |
+| Contrast | `VD-2` | Media | The app must provide white icon sets that the system colorizes to provide automatic contrast compensation. |
+| Contrast | `VD-3` | Media | The app must provide colors that the system can optimize for in-vehicle readability. |
+| Theming | `TH-1` | Messaging (templated), Navigation, POI, IOT, Weather | Apps that add custom theming to Car App Library components with version 1.9 or higher must support light and dark theme. See [Contrast](https://developer.android.com/develop/ui/compose/styles/theming)for more information. |
+| Grey Buttons | `GB-1` | Media | Interactive elements that are intentionally disabled must be nonfunctional. |
+| Driver Distraction | `DD-1` | Navigation | The navigation audio channel can only be used by navigation apps and for navigation instructions. For more information, see [Voice guidance](https://developer.android.com/training/cars/apps/navigation#voice-guidance). |
+| Driver Distraction | `DD-2` | Video, Games, Browsers | While driving, the app must not be launchable and the app's UI must not be visible. The app's audio must stop when the user starts driving and cannot be unpaused while driving. For more information, see [Meet driver distraction requirements](https://developer.android.com/training/cars/parked/automotive-os#driver-distraction). **Exception:** Video apps that support audio while driving can continue playback on supported devices. See [`DD-4`](https://developer.android.com/docs/quality-guidelines/car-app-quality#DD-4) . |
+| Payments | `PA-1` | Navigation, POI, IOT, Weather | The app must have simple flows if purchases are enabled, using shortcuts such as recent or favorite purchases. For more information, see [Purchase using an existing payment method](https://developers.google.com/cars/design/create-apps/sample-flows/purchase-with-existing-method). The app must not allow any of the following: - Setup of payment methods - Multiple items to be selected for purchase - Commitment to recurring payments, such as subscriptions |
+| Notification Ads | `NA-1` | Media, Messaging, Calling, Navigation, POI, IOT, Weather | The app must not present advertisements through notifications. |
+| Irrelevant Notifications | `IN-1` | Media, Messaging, Calling, Navigation, POI, IOT, Weather | The app must display notifications only when relevant to the driver's needs. Examples: Good: Notifying the user that a new message has arrived. Bad: Notifying the user about a new album release. Bad: For weather apps, your app posts notifications that are not related to imminent alerts for the current location, route, or destination. |
+| Media Autoplay | `MA-1` | Media | The app must not autoplay on startup or without user initiated action to select the app or app media. For more information, see [Implement play commands](https://developer.android.com/training/cars/media#playback-commands). |
+| Messaging Functionality | `MF-1` | Messaging | The app must receive incoming messages. For more information, see [Extend messaging notifications to Android Auto](https://developer.android.com/training/cars/communication/notification-messaging). |
+| Messaging Functionality | `MF-2` | Messaging | Messages must be properly grouped and displayed in the correct order. For more information, see [Extend messaging notifications to Android Auto](https://developer.android.com/training/cars/communication/notification-messaging). |
+| Messaging Functionality | `MF-3` | Messaging | The user can reply to a message. For more information, see [Extend messaging notifications to Android Auto](https://developer.android.com/training/cars/communication/notification-messaging). |
+| Messaging Functionality | `MF-4` | Messaging | The app must use short-form messaging app design patterns. Long-form messaging apps, such as apps for email, are not permitted. |
+| Messaging Functionality | `MF-5` | Messaging | The app must implement a peer-to-peer messaging service and not notification services, such as those for weather, stocks, and sport scores. |
+| Navigation Functionality | `NF-1` | Navigation | The app must provide turn-by-turn navigation directions. |
+| Navigation Functionality | `NF-2` | Navigation | The app draws only map content on the surface of the navigation templates. Text-based turn-by-turn directions, lane guidance, and estimated arrival time must be displayed on the relevant components of the navigation template. Additional information relevant to the drive, speed limit, road obstructions, etc., can be drawn on the safe area of the map. |
+| Navigation Functionality | `NF-3` | Navigation | When the app provides text-based turn-by-turn directions, it must also trigger navigation notifications. For more information, see [Turn-by-turn notifications](https://developer.android.com/training/cars/apps/navigation#turn-by-turn-notifications). |
+| Navigation Functionality | `NF-4` | Navigation | When the navigation app provides text-based turn-by-turn directions, it must send next-turn information to the vehicle's cluster display. For more information, see [Navigation metadata](https://developer.android.com/training/cars/apps/navigation#navigation-metadata). |
+| Navigation Functionality | `NF-5` | Navigation | The app must not provide turn-by-turn notifications, voice guidance, or cluster information when another navigation app is providing turn-by-turn instructions. For more information, see [Start, end, and stop navigation](https://developer.android.com/training/cars/apps/navigation#starting-ending-stopping-navigation). |
+| Navigation Functionality | `NF-6` | Navigation | The app must handle navigation requests from other apps. For more information, see [Support navigation intents](https://developer.android.com/training/cars/apps/navigation#support-navigation-intents). |
+| Navigation Functionality | `NF-7` | Navigation | The app must provide a "test drive" mode that simulates driving. For more information, see [Simulate navigation](https://developer.android.com/training/cars/apps/navigation#simulating-navigation). |
+| Point of Interest Functionality | `PF-1` | POI | The app must provide meaningful functionality relevant to driving. |
+| Internet of Things Functionality | `IT-1` | IOT | The IOT app may allow the following while driving: - **View the current state of devices**. For example: to view if a garage door is open or closed, a light bulb is on or off, a security system is armed or disarmed, or a washer is running or completed. - **Simple, one-touch features that control on and off functions**. For example: the ability to turn various devices on and off or open and close them, including turning lights on and off, turning a thermostat on and off, or opening and closing a garage door or curtain. This also includes turning a pre-programmed scene or routine on and off. - **Notify users about an event in the home or another location**. For example: receiving notifications for a routine or scene, a security alert, or a change in a door's open or closed status. The IOT app must not allow the following while driving: - **Tasks related to app setup of any kind**. For example: the ability to select devices, systems, or locations for use with the IOT app. - **Tasks related to creation, modification, or reordering**. For example: the ability to create, modify, or reorder a scene or routine, such as a sequence of events when departing or leaving a location, including opening and closing a garage door, or turning lights on and off. - **Tasks related to fine-grained device control**. For example: the ability to control certain functions, including adjusting thermostat temperatures or the level of lighting luminescence. |
+| Weather Functionality | `WE-1` | Weather | App must include weather related content, which must be relevant to the user's current location or a user specified location. |
+| Weather Functionality | `WE-2` | Weather | Weather information on map tiles must be readable and may not include complex legends. Apps may include a maximum of three legends. Apps with multiple legends may have a maximum of three colors, whereas single legend apps may have more than three colors. |
+| Weather Functionality | `WE-3` | Weather | Forecast information must include easily readable icons and symbols. |
+| Weather Functionality | `WE-4` | Weather | Customization of forecast intervals must not be made possible using templates. |
+| Weather Functionality | `WE-5` | Weather | Weather apps must not show more than five unique weather map annotations in a given view (for example: Temperature markers, wind speed markers, humidity, radar overlay, lightning indicators, road conditions all in the same view). |
+| Map Rendering | `MR-1` | Navigation, POI, Weather | By default, apps that [draw maps](https://developer.android.com/training/cars/apps#draw-maps) must draw a light-themed or dark-themed map when instructed to do so. For more information, see [Support dark theme](https://developer.android.com/training/cars/apps#dark-theme). Apps can let users choose to always display the app in either light or dark theme. |
+| Media Controls | `MC-1` | Video | The app integrates with media session. Depending on the content, the app must support either the play/pause or stop playback commands. Additionally, the app must provide title and thumbnail metadata for every media item. See [Control and advertise playback using a MediaSession](https://developer.android.com/media/media3/session/control-playback). |
+| Deep Links | `DL-1` | Video, Games, Browsers | **Android Automotive OS only:** The app supports deep links. See [Handling Android App Links](https://developer.android.com/training/app-links). |
+| Media Functionality (templated) | `MFT-1` | Media (templated) | User must be able to navigate to the `MediaPlaybackTemplate` from media browsing views or support the minimized control panel for apps using the Car App Library Version 1.9 or higher. See [Build templated media apps](https://developer.android.com/training/cars/apps/media) for more details. |
+| Media Functionality (templated) | `MFT-3` | Media (templated) | Handle `SHOW_MEDIA_PLAYBACK` intents: Templated Media apps must handle the `androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK` intent action to direct the user to the `MediaPlaybackTemplate`. See [handle system media playback intents](https://developer.android.com/training/cars/apps/media#sys-media-playback-intents) for more details. |
+
+#### Associated large screen quality guidelines
+
+While these guidelines are relevant for all activities implemented by your app,
+they are Tier 2 requirements only for [parked apps](https://developer.android.com/training/cars/parked).
+
+| Criteria | ID | Guidance for cars |
+|---|---|---|
+| Multi-window and multi-resume | [`LS-M2`](https://developer.android.com/docs/quality-guidelines/large-screen-app-quality#LS-M2) | When an activity is on a [distant display](https://developer.android.com/training/cars/testing/emulator/distant-display), it loses the top resumed activity position when the user interacts with an activity on the main display. |
+| UX | [`LS-U1`](https://developer.android.com/docs/quality-guidelines/large-screen-app-quality#LS-U1) | Some cars have landscape screens with much greater aspect ratios than are common on other large screen devices. You can use the [Automotive Ultrawide](https://developer.android.com/training/cars/testing/emulator#bundled-profiles) hardware profile to test on such a display. |
+| UX | [`LS-U2`](https://developer.android.com/docs/quality-guidelines/large-screen-app-quality#LS-U2) | Same as for LS-U1 |
+| UX | [`LS-U3`](https://developer.android.com/docs/quality-guidelines/large-screen-app-quality#LS-U3) | To qualify as a car optimized app, your app must meet the LS-U3 requirement for touch target sizes. To qualify as car differentiated, it must meet the car [`UX-1`](https://developer.android.com/docs/quality-guidelines/car-app-quality#UX-1) requirement. |
+
+### Car differentiated
+
+| Criteria | ID | Applicable Categories | Description |
+|---|---|---|---|
+| Navigation Functionality | `NF-9` | Navigation | The app supports rendering on cluster displays, and only renders map tiles on these displays. See [Drawing to the cluster display](https://developer.android.com/training/cars/apps/navigation#drawing_to_the_cluster_display). **Warning:** Navigation apps that also support weather functionality must not include any weather-related information on the cluster display. |
+| Templated Messaging Functionality | `TMF-1` | Messaging (templated) | The app displays conversations as described in [Display conversations](https://developer.android.com/training/cars/communication/templated-messaging#display-conversations): - The self property of each conversation is set accurately. - Users can send a message using the button provided for each conversation. - Conversations are refreshed to include new messages as they are sent and received. |
+| Calling Functionality | `CF-1` | Calling | The app can be used to initiate, accept, decline, and leave calls. See [Build calling experiences for Android Auto](https://developer.android.com/training/cars/communication/calling#integrate-telecom). |
+| Calling Functionality | `CF-2` | Calling | During a call, the app provides the following: - A call name and icon. - For meetings/group calls, the number of participants and the actively speaking participant. - The ability for a user to mute themselves. |
+| Calling Functionality | `CF-3` | Calling | Calls initiated while Android Auto is not connected continue when a user connects to Android Auto and are displayed on Android Auto. |
+| Deep Links | `DL-2` | Media, Navigation, POI, IOT, Weather | **Android Automotive OS only:** The app supports deep links: - For media apps, see [Add support for Android Automotive OS to your media app](https://developer.android.com/training/cars/media/automotive-os#support-deep-links). - For templated apps, see [Add support for Android Automotive OS to your templated app](https://developer.android.com/training/cars/apps/automotive-os#support-deep-links). |
+| Driver distraction | `DD-4` | Video | The app supports audio while driving on capable devices. See [Support audio while driving](https://developer.android.com/training/cars/parked/video#audio-while-driving) for additional details. **Important:** The app must meet [`DD-2`](https://developer.android.com/docs/quality-guidelines/car-app-quality#DD-2) on devices that don't support audio while driving. |
+| App Rendering | `AR-2` | Video, Games, Browsers | The app renders into display cutouts to fully make use of the screen space available while maintaining the ability for users to continue interacting with all UI elements on the screen. See [Adapt to irregularly shaped displays](https://developer.android.com/training/cars/parked/automotive-os#irregular-displays) for more details. |
+| Media Functionality (templated) | `MFT-2` | Media (templated) | App provides a differentiated experience by using templates for scenarios beyond basic browsing and playback. For example, by providing expanded playback controls or by allowing users to adjust driving relevant app settings. **Tip:** Try creating a car version of a feature that's unique to your mobile app. See [Build templated media apps](https://developer.android.com/training/cars/apps/media) for more details. |
+| User Experience | `UX-1` | Video, Games, Browsers | Touch targets are at least 64dp. |
+| User Experience | `UX-2` | Video, Games, Browsers | Touch targets are at least 24dp apart from each other and 24dp away from screen edges. |
+| User Experience | `UX-3` | Video, Games, Browsers | Font sizes should be at least 24sp. |
+
+#### Associated large screen quality guidelines
+
+While these guidelines are relevant for all activities implemented by your app,
+they are Tier 1 requirements only for [parked apps](https://developer.android.com/training/cars/parked).
+
+| Criteria | ID | Guidance for cars |
+|---|---|---|
+| UX | [`LS-U4`](https://developer.android.com/docs/quality-guidelines/large-screen-app-quality#LS-U4) | Although keyboards are supported by Android Automotive OS, they're less common input methods in cars in comparison to other large screen devices. However, some cars have rotary input devices that rely on the same APIs as tab navigation with a keyboard, so they also require LS-U4 to be met to function properly. |
+| Keyboard, mouse, and trackpad | [`LS-I3`](https://developer.android.com/docs/quality-guidelines/large-screen-app-quality#LS-I3) | Same as for LS-U4 |
+
+## Frequently asked questions
+
+**What happens after I submit my app?**
+
+Apps for cars are subject to an additional manual review beyond normal Play
+Store review processes. Your app is tested to ensure compliance against the
+applicable criteria.
+
+**After submitting my app, how will I find out if my app does not meet all the
+requirements for Android Auto or Android Automotive OS?**
+
+If your app does not meet the app quality requirements described on this page,
+the Play Store team contacts you through the email address specified in the
+[Google Play Console](https://play.google.com/console/) account associated with the app.
+
+> [!NOTE]
+> **Note:** For information about how to publish your app in Google Play, see [Distribute to cars](https://developer.android.com/training/cars/distribute).
+
+**How do I manage policy violations and appeals?**
+
+You can learn more about
+[managing policy violations and appeals](https://support.google.com/googleplay/android-developer/answer/9899142)
+in the Google Play policy center.
+
+**My app targets more than just Android Auto or Android Automotive OS. If my app
+does not meet the car requirements, will my new or updated app still appear on
+Google Play for other devices?**
+
+No. When Google begins the approval process, your app undergoes an app quality
+review. Any subsequent updates are not available for distribution until the app
+is approved. If you need to make updates to your app for other devices, consider
+creating a separate release from the updates to your car app.
+
+> [!CAUTION]
+> **Caution:** Due to this restriction, you shouldn't use your production APK for Android Auto support prototyping.
+
+## Change notes
+
+### September 2026
+
+- The Games parked app category has graduated from beta to general availability. Developers can now publish games to all track types on Google Play. See [Supported app categories](https://developer.android.com/training/cars#supported-app-categories) for more information.
+
+### August 2026
+
+- Expected Performance
+
+ - Categories affected: Media, Messaging (templated), Calling, Navigation, POI, IOT, or Weather
+ - New criterion:
+ [`EP-4`](https://developer.android.com/docs/quality-guidelines/car-app-quality#EP-4)
+
+
+ **Android Automotive OS only:** The app listing on
+ Google Play Store must include screenshots for the Android
+ Automotive OS form factor. See
+ [Provide Android
+ Automotive OS screenshots](https://developer.android.com/training/cars/distribute#opt-in-aaos).
+
+
+ Note: For apps in the parked app categories, screenshots are
+ optional. However, if your app includes features unique to cars,
+ or the app differs significantly from the experience on mobile or
+ tablet, it is recommended to include screenshots in your Google
+ Play Store listing. See
+ [Provide Android
+ Automotive OS screenshots](https://developer.android.com/training/cars/distribute#opt-in-aaos).
+
+### May 2026
+
+- Voice Commands
+
+ - Categories affected: Media, Media (templated), Navigation
+ - Updated criterion: [`VC-1`](https://developer.android.com/docs/quality-guidelines/car-app-quality#VC-1)
+ - Previous text:
+
+ The app must support Google Assistant voice commands. For more
+ information, see [Support voice actions](https://developer.android.com/training/cars/media/voice-actions) for
+ media, and [Support navigation intents](https://developer.android.com/training/cars/apps/navigation#support-navigation-intents) for
+ navigation.
+ - New text:
+
+ The app must support Gemini and Google Assistant voice commands. For
+ more information, see [Support voice actions](https://developer.android.com/training/cars/media/voice-actions)
+ for media, and [Support navigation intents](https://developer.android.com/training/cars/apps/navigation#support-navigation-intents) for
+ navigation.
+- Theming
+
+ - Categories affected: Media (templated), Messaging (templated), Navigation, POI, IOT, Weather
+ - New criterion:
+ [`TH-1`](https://developer.android.com/docs/quality-guidelines/car-app-quality#TH-1)
+
+ Apps that add custom theming to Car App Library components with version
+ 1.9 or higher must support light and dark theme. See
+ [Contrast](https://developer.android.com/develop/ui/compose/styles/theming) for more information.
+- Media Functionality (templated)
+
+ - Categories affected: Media (templated)
+ - Updated criterion: [`MFT-1`](https://developer.android.com/docs/quality-guidelines/car-app-quality#MFT-1)
+ - Previous text:
+
+ User must be able to navigate to the `MediaPlaybackTemplate` from media
+ browsing views. See [Build templated media
+ apps](https://developer.android.com/training/cars/apps/media) for more details.
+ - New text:
+
+ User must be able to navigate to the `MediaPlaybackTemplate` from media
+ browsing views or support the minimized control panel for apps using the
+ Car App Library Version 1.9 or higher. See [Build templated media
+ apps](https://developer.android.com/training/cars/apps/media) for more details.
+ - Categories affected: Media (templated)
+
+ - New criterion:
+ [`MFT-3`](https://developer.android.com/docs/quality-guidelines/car-app-quality#MFT-3)
+
+ Handle `SHOW_MEDIA_PLAYBACK` intents: Templated Media apps must
+ handle the `androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK` intent
+ action to direct the user to the `MediaPlaybackTemplate`. See [handle
+ system media playback intents](https://developer.android.com/training/cars/apps/media#sys-media-playback-intents) for more
+ details.
+
+### May 2025
+
+- [App Categories](https://developer.android.com/docs/quality-guidelines/car-app-quality#app-categories): "Communication --- messaging notifications" replaces the "Messaging" category. "Communication --- templated messaging" and "Communication --- calling" have been added, including the related "Templated Messaging Functionality" and "Calling Functionality" criteria.
+- Expected Performance
+
+ - Categories affected: Games
+ - New criterion:
+ [`EP-3`](https://developer.android.com/docs/quality-guidelines/car-app-quality#EP-3)
+
+ The app is responsive to input and does not freeze or stutter during
+ gameplay.
+- Display Orientation
+
+ - Categories affected: Games, Video, Browser
+ - Updated criterion: [`DO-1`](https://developer.android.com/docs/quality-guidelines/car-app-quality#DO-1)
+ - Previous text:
+
+ The app can be distributed to devices with fixed screen orientations.
+ See [Required Android Automotive OS features](https://developer.android.com/training/cars/parked#required-features).
+ - New text:
+
+ Android Automotive OS only: The app can be distributed to devices
+ with fixed screen orientations. See [Required Android Automotive OS
+ features](https://developer.android.com/training/cars/parked/automotive-os#required-features).
+ - Categories affected: Games
+
+ - New criterion:
+ [`DO-2`](https://developer.android.com/docs/quality-guidelines/car-app-quality#DO-2)
+
+ Android Auto only: The app is not significantly pillarboxed when
+ running on landscape displays. See [Add support for Android Auto to your
+ parked app](https://developer.android.com/training/cars/parked/auto).
+- Deep Links
+
+ - Categories affected: Video, Games, Browsers
+ - New criterion:
+ [`DL-1`](https://developer.android.com/docs/quality-guidelines/car-app-quality#DL-1)
+
+ Android Automotive OS only: The app supports deep links. See
+ [Handling Android App Links](https://developer.android.com/training/app-links).
+ - Categories affected: Media, Navigation, POI, IOT, Weather
+
+ - New criterion:
+ [`DL-2`](https://developer.android.com/docs/quality-guidelines/car-app-quality#DL-2)
+
+ Android Automotive OS only: The app supports deep links:
+ - For media apps, see [Add support for Android Automotive OS to your
+ media app](https://developer.android.com/training/cars/media/automotive-os#support-deep-links).
+ - For templated apps, see [Add support for Android Automotive OS to
+ your templated app](https://developer.android.com/training/cars/apps/automotive-os#support-deep-links).
+- CPU Architectures
+
+ - Categories affected: Video, Games, Browsers
+ - Removed criterion: `CP-1`
+
+ The app must support both x86_64 and ARM CPUs.
+- Driver Distraction
+
+ - Categories affected: Video
+ - New criterion:
+ [`DD-4`](https://developer.android.com/docs/quality-guidelines/car-app-quality#DD-4)
+
+ The app supports audio while driving on capable devices. See [Support
+ audio while driving](https://developer.android.com/training/cars/parked/video#audio-while-driving) for additional details.
+
+ > [!IMPORTANT]
+ > **Important:** The app must meet [`DD-2`](https://developer.android.com/docs/quality-guidelines/car-app-quality#DD-2) on devices that don't support audio while driving.
+
+### December 2024
+
+- [App Categories](https://developer.android.com/docs/quality-guidelines/car-app-quality#app-categories): "Weather" has been added, including the introduction of the Weather Functionality criteria.
+- Map Rendering
+
+ - Categories affected: Navigation, POI, Weather
+ - New criterion:
+ [`MR-1`](https://developer.android.com/docs/quality-guidelines/car-app-quality#MR-1)
+
+ By default, apps that [draw maps](https://developer.android.com/training/cars/apps#draw-maps) must draw a
+ light-themed or dark-themed map when instructed to do so. For more
+ information, see [Support dark theme](https://developer.android.com/training/cars#dark-theme).
+
+ Apps can let users choose to always display the app in either light or
+ dark theme.
+- Navigation Functionality
+
+ - Removed criterion: `NF-8`
+ - Categories affected: Navigation Replacement of criterion NF-8 by the [`MR-1`](https://developer.android.com/docs/quality-guidelines/car-app-quality#MR-1) criterion.
+ - Updated criterion: [`NF-9`](https://developer.android.com/docs/quality-guidelines/car-app-quality#NF-9)
+ - Categories affected: Navigation
+ - Previous text:
+
+ The app supports rendering on cluster displays, and only renders map
+ tiles on these displays. See
+ [Drawing to the cluster display](https://developer.android.com/training/cars/apps/navigation#drawing-cluster-display).
+ - New text:
+
+ The app supports rendering on cluster displays, and only renders map
+ tiles on these displays. See
+ [Drawing to the cluster display](https://developer.android.com/training/cars/apps/navigation#drawing-cluster-display).
+
+ Navigation apps that also support weather functionality must not include
+ any weather-related information on the cluster display.
+- Screen Animation
+
+ - Updated criterion: [`SA-1`](https://developer.android.com/docs/quality-guidelines/car-app-quality#SA-1)
+ - Categories affected: Media, Messaging, Navigation, Point of Interest, Weather
+ - Previous text:
+
+ The app must not display animated elements on the screen, such as
+ animated graphics or video.
+ - New text:
+
+ The app must not display animated elements on the screen, such as
+ animated graphics or video.
+
+ Exception: Canvas animations while the user is parked are allowed if
+ they are relevant to the driving task.
+
+### May 2024
+
+- Introduction of the [Car quality tiers](https://developer.android.com/docs/quality-guidelines/car-app-quality#car-quality-tiers)
+- Driver Distraction
+
+ - Categories affected: Browsers, Games, Video
+ - New criterion:
+ [`DD-3`](https://developer.android.com/docs/quality-guidelines/car-app-quality#DD-3)
+
+ The app must not be launchable or usable while driving and must not play
+ any audio. See
+
+ [Ensure there are no distraction optimized
+ activities](https://developer.android.com/training/cars/parked#ensure_there_are_no_distraction-optimized_activities).
+
+ > [!NOTE]
+ > **Note:** If your app plays audio, it must meet the [`DD-2`](https://developer.android.com/docs/quality-guidelines/car-app-quality#DD-2) requirement.
+
+- App Rendering
+
+ - Categories affected: All categories supported by Android Automotive OS
+ - New criterion:
+ [`AR-1`](https://developer.android.com/docs/quality-guidelines/car-app-quality#AR-1)
+
+ In activities implemented by the app, interactive UI elements must not
+ be obstructed by system bars or display cutouts. See [Work with window
+ insets and display cutouts](https://developer.android.com/training/cars/parked/automotive-os#insets-and-cutouts).
+ - New criterion:
+ [`AR-2`](https://developer.android.com/docs/quality-guidelines/car-app-quality#AR-2)
+
+ The app renders into display cutouts to fully make use of the screen
+ space available while ensuring interactive UI elements remain
+ accessible. See
+ [Adapt to irregularly shaped displays](https://developer.android.com/training/cars/parked/automotive-os#irregular-displays) for
+ more details.
+- Media Controls
+
+ - Categories affected: Video
+ - New criterion:
+ [`MC-1`](https://developer.android.com/docs/quality-guidelines/car-app-quality#MC-1)
+
+ The app integrates with media session. Depending on the content, the app
+ must support either the play/pause or stop playback commands.
+ Additionally, the app must provide title and thumbnail metadata for
+ every media item. See [Control and advertise playback using a
+ MediaSession](https://developer.android.com/media/media3/session/control-playback).
+- UX
+
+ - Categories affected: Browsers, Games, Video
+ - New criterion:
+ [`UX-1`](https://developer.android.com/docs/quality-guidelines/car-app-quality#UX-1)
+
+ Touch targets are at least 64dp.
+ - New criterion:
+ [`UX-2`](https://developer.android.com/docs/quality-guidelines/car-app-quality#UX-2)
+
+ Touch targets are at least 24dp apart from each other and 24dp away from
+ screen edges.
+ - New criterion:
+ [`UX-3`](https://developer.android.com/docs/quality-guidelines/car-app-quality#UX-3)
+
+ Font sizes should be at least 24sp.
+- Permitted Experiences
+
+ - Categories affected: Media, Navigation, POI, IOT
+ - New criterion:
+ [`PE-1`](https://developer.android.com/docs/quality-guidelines/car-app-quality#PE-1)
+
+ Android Automotive OS only: with the exception of providing setup,
+ settings, and sign-in flows while parked, the app must not provide any
+ functionality through its own activities.
+- Navigation functionality
+
+ - Categories affected: Navigation
+ - New criterion:
+ [`NF-9`](https://developer.android.com/docs/quality-guidelines/car-app-quality#NF-9)
+
+ The app supports rendering on cluster displays, and only renders map
+ tiles on these displays. See
+ [Drawing to the cluster display](https://developer.android.com/training/cars/apps/navigation#drawing-cluster-display).
+- Delayed Response
+
+ - Categories affected: Video
+ - Removal of criteria [`DR-1`](https://developer.android.com/docs/quality-guidelines/car-app-quality#DR-1) , [`DR-2`](https://developer.android.com/docs/quality-guidelines/car-app-quality#DR-2) , and [`DR-3`](https://developer.android.com/docs/quality-guidelines/car-app-quality#DR-3) for the video category
+
+### July 2023
+
+- [App Categories](https://developer.android.com/docs/quality-guidelines/car-app-quality#app-categories): "Browsers" has been added
+- Sensitive Data
+
+ - Categories affected: Browsers
+ - New criterion:
+ [`SD-1`](https://developer.android.com/docs/quality-guidelines/car-app-quality#SD-1)
+
+ Browsers must not save or allow access to passwords or payment
+ information unless the user can block access to passwords using a
+ profile lock. [Learn how](https://developer.android.com/training/cars/parked/browser#block-sensitive).
+ - New criterion:
+ [`SD-2`](https://developer.android.com/docs/quality-guidelines/car-app-quality#SD-2)
+
+ Before syncing data to the car, browsers that synchronize passwords or
+ payments data must do the following steps:
+ 1. Prompt the user to authenticate.
+ 2. Notify the user on the car screen their data will be synchronized to the car.
+
+### April 2023
+
+- [App Categories](https://developer.android.com/docs/quality-guidelines/car-app-quality#app-categories): "Games" has been added
+- Irrelevant Notifications
+
+ - Categories affected: Games, Video
+ - New criterion:
+ [`IN-2`](https://developer.android.com/docs/quality-guidelines/car-app-quality#IN-2)
+
+ The app must not provide any heads up notifications.
+- Settings Flow
+
+ - Categories affected: Video
+ - Removed criterion: `SF-1`
+
+ The app must proceed to the home page after sign-in or attempted
+ sign-in.
+- Automotive Functionality
+
+ - Categories affected: Video
+ - Removed criterion: `AF-1`
+
+ The app must not contain any distraction-optimized activities.
+
+### March 2023
+
+- Grammar and formatting changes.
+
+### December 2022
+
+- Internet of Things Functionality
+
+ - Categories affected: Internet of Things
+ - New criteria:
+
+ The IOT app may allow the following while driving:
+ - View the current state of devices. For example: to view if a garage door is open or closed, a light bulb is on or off, a security system is armed or disarmed, or a washer is running or completed.
+ - Simple, one-touch features that control on and off functions. For example: the ability to turn various devices on and off or open and close them, including turning lights on and off, turning a thermostat on and off, or opening and closing a garage door or curtain. This also includes turning a pre-programmed scene or routine on and off.
+ - Notify users about an event in the home or another location. For example: receiving notifications for a routine or scene, a security alert, or a change in a door's open or closed status.
+
+ The IOT app must not allow the following while driving:
+ - Tasks related to app setup of any kind. For example: the ability to select devices, systems, or locations for use with the IOT app.
+ - Tasks related to creation, modification, or reordering. For example: the ability to create, modify, or reorder a scene or routine, such as a sequence of events when departing or leaving a location, including opening and closing a garage door, or turning lights on and off.
+ - Tasks related to fine-grained device control. For example: the ability to control certain functions, including adjusting thermostat temperatures or the level of lighting luminescence.
+
+### May 2022
+
+- [App Categories](https://developer.android.com/docs/quality-guidelines/car-app-quality#app-categories): "Parking \& Charging" has been replaced with "Point of Interest"
+- Screen Animation
+
+ - Categories affected: Media, Messaging, Navigation, and Point of Interest
+ - Previous text:
+
+ The app does not display animated elements on the screen such as
+ animated graphics, video, or progress bars.
+ - New text:
+
+ The app does not display animated elements on the screen such as
+ animated graphics or video.
+- Image Usage
+
+ - Categories affected: Media, Messaging, Navigation, and Point of Interest
+ - Previous text:
+
+ App elements don't display any images on the screen. Exceptions
+ include:
+ - Apps may display a single static image for content context in the background of the consumption screen, such as album art or the advertiser's corporate logo.
+ - Apps may display icons in the content navigation drawer.
+ - Navigation, parking, and charging apps may display images and photographs to aid in driving decision-making.
+ - Navigation apps may display an image for lane guidance or junction guidance.
+ - New text:
+
+ App elements don't display any images on the screen. Exceptions
+ include:
+ - Apps may display a single static image for content context in the background of the consumption screen, such as album art or the advertiser's corporate logo.
+ - Apps may display icons in the content navigation drawer.
+ - Apps may display images and photographs to aid in driving decision-making.
+ - Navigation apps may display an image for lane guidance or junction guidance.
+- Since cars are large screen devices, apps should fill the categories
+
+ - Categories affected: Media, Messaging, Navigation, Point of Interest, and Video
+ - Previous text:
+
+ The app does not include games or other features outside the app types
+ intended for cars. For more information, see [Supported app
+ categories](https://developer.android.com/training/cars#supported-app-categories).
+ - New text:
+
+ The app does not include features outside the app types intended for
+ cars. For more information, see
+ [Supported app categories](https://developer.android.com/training/cars#supported-app-categories).
+- Visual Information on Phone
+
+ - Categories affected: Media, Navigation, and Point of Interest
+ - Previous text:
+
+ Android Auto only: If the user must go to the phone screen --- for
+ example, to act on a permission request --- then the app must display a
+ message instructing the user to only look at their phone screen when
+ it's safe to do so. For more information, see
+ [Handle general errors](https://developer.android.com/training/cars/media#errors) for media, and [Handle
+ user input](https://developer.android.com/training/cars/apps#handle-user-input) for navigation, parking, and
+ charging.
+ - New text:
+
+ Android Auto only: If the user must go to the phone screen --- for
+ example, to act on a permission request --- then the app must display a
+ message instructing the user to only look at their phone screen when
+ it's safe to do so. For more information, see
+ [Handle general errors](https://developer.android.com/training/cars/media#errors) for media, and [Handle
+ user input](https://developer.android.com/training/cars/apps#handle-user-input) for navigation and point of
+ interest apps.
+- Navigation Functionality
+
+ - Categories affected: Navigation
+ - Previous text:
+
+ The app draws only map content on the surface of the navigation
+ templates. Text-based turn-by-turn directions, lane guidance, and
+ estimated arrival time must be displayed on the relevant components of
+ the Navigation template. Additional information relevant to the drive ---
+ for example, speed limit and road obstructions --- can be drawn on the
+ right side of the map.
+ - New text:
+
+ The app draws only map content on the surface of the navigation
+ templates. Text-based turn-by-turn directions, lane guidance, and
+ estimated arrival time must be displayed on the relevant components of
+ the Navigation template. Additional information relevant to the drive ---
+ speed limit and road obstructions --- can be drawn on the safe area of the
+ map.
+- Point of Interest Functionality
+
+ - Categories affected: Point of Interest
+ - Previous text:
+
+ The app must provide meaningful functionality in its category relevant
+ to driving.
+ - New text:
+
+ The app must provide meaningful functionality relevant to driving.
+ - Removed criteria:
+
+ If a parking app is displaying a list of locations next to a map --- for
+ example, the Place List Map template --- then the locations in the list
+ can only be parking spots.
+
+ Similarly, for a charging app, the locations must be charging stations.
+
+### April 2022
+
+- Display Orientation
+
+ - Categories affected: Video
+ - New criteria:
+
+ UIs drawn by the app support both landscape and portrait screens.
+- Driver Distraction
+
+ - Categories affected: Video
+ - New criteria:
+
+ While driving, the video app must not be launchable and the video app's
+ screen must not be visible. The video app's audio must stop when the
+ user starts driving.
+- Settings Flow
+
+ - Categories affected: Video
+ - New criteria:
+
+ App must proceed to the home page after (attempted) sign-in.
+- Automotive Functionality
+
+ - Categories affected: Video
+ - New criteria:
+
+ App does not contain driver optimized activities.
+- CPU Architectures
+
+ - Categories affected: Video
+ - New criteria:
+
+ App supports both x86_64 and ARM CPUs.
+- App Navigability
+
+ - Categories affected: Video
+ - New criteria:
+
+ App allows users to navigate through the app without encountering any
+ dead ends.
+
+### November 2021
+
+- Visual Information on Phone
+
+ - Categories affected: Media, Navigation, and Parking \& Charging
+ - Previous text:
+
+ While the app is interacting with the car screen and the car is not
+ parked, the app does not activate the phone screen to present any form
+ of visual information such as notifications, toasts, video, images,
+ advertising, or similar. For more information, see [Build a navigation
+ app](https://developer.android.com/training/cars/apps/navigation#handle-user-input) for navigation, parking, and charging.
+ Similarly, while the app is running Android Auto UI on the phone screen,
+ the app does not present any visual information on the phone screen that
+ is unrelated to Android Auto.
+
+ If the user must go to the phone screen --- for example, to act on a
+ permission request --- then the app must display a message instructing the
+ user to only look at their phone screen when it's safe to do so.
+ - New text:
+
+ Android Auto only: If the user must go to the phone screen --- for
+ example, to act on a permission request --- then the app must display a
+ message instructing the user to only look at their phone screen when
+ it's safe to do so. For more information, see
+ [Handle general errors](https://developer.android.com/training/cars/media#errors) for media, and [Handle
+ user input](https://developer.android.com/training/cars/apps#handle-user-input) for navigation, parking, and
+ charging.
+- Payments
+
+ - Categories affected: Navigation, Parking \& Charging
+ - Previous text:
+
+ The app must have simple flows if purchases are enabled.
+ - New text:
+
+ The app must have simple flows if purchases are enabled, using shortcuts
+ such as recent or favorite purchases. For more information, see
+ [Purchase using existing payment method](https://developers.google.com/cars/design/create-apps/sample-flows/purchase-with-existing-method).
+
+ The app must not allow any of the following:
+ - Setup of payment methods
+ - Multiple items to be selected for purchase
+ - The user to commit to recurring payments --- for example, subscriptions.
\ No newline at end of file
diff --git a/device-ai/appfunctions/SKILL.md b/device-ai/appfunctions/SKILL.md
new file mode 100644
index 0000000..17f3f91
--- /dev/null
+++ b/device-ai/appfunctions/SKILL.md
@@ -0,0 +1,71 @@
+---
+name: appfunctions
+description: Analyzes Android apps to identify key user workflows for AppFunctions
+ such as creating a note, playing media, or sending an automated or AI agent triggered
+ message, voice commands, or system shortcuts, without needing to open the app UI.
+ Generates Kotlin code to expose these workflows to the Android system, allowing
+ agents to discover and execute them on-device. Also refines KDoc documentation to
+ ensure AI agents correctly understand and use the provided functionality.
+license: Complete terms in LICENSE.txt
+metadata:
+ author: Google LLC
+ last-updated: '2026-08-14'
+ keywords:
+ - AppFunctions
+ - Kotlin
+ - KSP
+ - ADB
+ - AI
+ - LLM
+ - MCP
+---
+
+Analyzes Android apps to identify key user workflows for AppFunctions such as
+creating a note, playing media, or sending an automated or AI agent triggered
+message, voice commands, or system shortcuts, without needing to open the app
+UI.
+
+Generates Kotlin code to expose these workflows to the Android system,
+allowing agents to discover and execute them on-device.
+
+Also refines KDoc documentation to ensure AI agents correctly
+understand and use the provided functionality.
+
+## Prerequisites
+
+The app must **`targetSdk 36`** or newer and use **`compileSdk 37`** or newer as
+AppFunctions, part of the Android platform API, are available from Android 16
+onwards.
+Always use the Jetpack library because it handles backward compatibility.
+
+## Workflows
+
+This skill enables the caller to discover features that will be provided to
+system agents, implement these with AppFunctions, improve function description
+for agents, and use ADB commands for local evaluation and testing.
+
+The full AppFunction development flow consists of these four steps:
+
+- *[Step 1: Discovery](references/feature-discovery-analysis.md)*: Analyze Android codebases to identify and recommend potential AppFunctions. Use this step when a user asks to "discover AppFunctions," "find features for AI," or "analyze my app for agentic tools."
+- *[Step 2: Implementation and configuration](references/implementation-configuration.md)*: Generate Kotlin implementations of AppFunctions, manage system-wide configuration, and configure build dependencies. Use this step when a user asks to "implement AppFunctions," "set up the AppFunctions framework," or "configure Hilt for AppFunctions."
+- *[Step 3: KDoc refinement](references/kdoc-refinement-optimization.md)*: Optimize AppFunction KDoc for AI agents and Model Context Protocol. If a user asks to "write KDoc", "optimize for MCP", or "refactor tool descriptions for LLMs", use this step.
+- *[Step 4: Testing and debugging](references/adb-interaction-testing.md)*: Provide commands to interact with AppFunctions using ADB for testing and debugging. If a user wants to "list app functions", "invoke an app function", or "verify app function registration" on a device, use this step.
+
+If users request a subset of steps, you must encourage them to use all steps.
+
+If they apply, you must load the following references:
+
+- *[Context and terminology](references/context.md)*: Defines the ubiquitous language, architecture definitions, and design patterns across the AppFunctions skill suite. Load when you need to understand core architecture terminology or check the distinctions between modern and legacy AppFunctions APIs.
+- *[Migration to service entry point](references/migrate-to-service-entry-point.md)* : Documents the systematic procedure for migrating Android applications using AppFunctions versions 1.0.0-alpha09 and lower to `AppFunctionServiceEntryPoint` architecture introduced in version 1.0.0-alpha10. Load when a user asks to migrate or upgrade existing AppFunctions code, or when you encounter legacy `AppFunctionConfiguration.Provider` implementations.
+
+## Critical constraints
+
+- **Modular consistency**: You must refine KDocs immediately after you generate AppFunction implementations.
+- **Security**: Don't expose sensitive data or run destructive actions without user confirmation.
+
+## Troubleshooting
+
+- If you encounter build-time errors such as Kotlin Symbol Processing (KSP) issues, see [Implementation and configuration](references/implementation-configuration.md).
+- If you encounter runtime errors such as missing services or execution failures, see [Testing and debugging](references/adb-interaction-testing.md).
+- If you need architecture definitions and vocabulary, see [Context and terminology](references/context.md).
+- If you encounter issues when upgrading legacy configurations, see [Migration to service entry point](references/migrate-to-service-entry-point.md).
diff --git a/device-ai/appfunctions/references/adb-interaction-testing.md b/device-ai/appfunctions/references/adb-interaction-testing.md
new file mode 100644
index 0000000..b487923
--- /dev/null
+++ b/device-ai/appfunctions/references/adb-interaction-testing.md
@@ -0,0 +1,95 @@
+Provides commands to interact with AppFunctions on a connected device or
+emulator using ADB for AppFunction testing and debugging.
+
+## Instructions
+
+### Scenario 1: List app functions
+
+Use this scenario when you want to see which app functions are registered on
+the device.
+
+1. **List all functions** : To view all registered app functions in JSON format, run `adb shell cmd app_function list-app-functions`.
+2. **Filter by package** : To view functions for a specific package, pipe the output to `grep` or a JSON tool: `adb shell cmd app_function
+ list-app-functions | grep `.
+
+### Scenario 2: Invoke app functions
+
+If you want to test the execution of an app function, use this scenario.
+
+1. **Analyze description** : Before invoking, you must read the `description` field for the function in the `list-app-functions` output. This often contains critical usage constraints, required workflows, or disambiguation rules.
+2. **Follow constraints**: Follow all instructions in the description, such as asking the user to disambiguate or calling another tool first.
+3. **Format parameters** : Format the `--parameters` argument as a valid JSON string that represents the function's input arguments.
+4. **Execute function** : Use `adb shell cmd app_function execute-app-function
+ --package --function
+ --parameters ''`.
+5. **Handle response** : The command returns the result as a JSON string. To get brief YAML output, use `--brief-yaml`.
+
+### Scenario 3: Manage function state
+
+If you need to enable or disable an app function for testing, use this scenario.
+
+1. **Set enabled state** : Use `adb shell cmd app_function set-enabled
+ --package
+ --function
+ --state `.
+
+## Critical constraints
+
+### Follow metadata descriptions
+
+**Mandatory** : The `description` field in the app function metadata is a set of
+instructions for the LLM. If a description says to "disambiguate with the user"
+or "call another function first," you must perform those steps before execution.
+
+### JSON escaping
+
+**Critical** : When passing JSON using `adb shell`, always wrap the JSON string
+in single quotes to prevent the shell from interpreting special characters or
+spaces. Example: `--parameters '{"key": "value"}'`.
+
+### Device availability
+
+The `app_function` service must be available on the device. If `cmd: Can't find
+service: app_function` is returned, the device doesn't support this feature.
+
+## Examples
+
+### Example 1: Verify app function service availability on connected device
+
+ adb shell cmd app_function help
+
+If executing the preceding command returns a help page, use the commands and
+parameters provided to guide the ADB interaction testing tool interactions.
+
+### Example 2: List all registered app functions
+
+ adb shell cmd app_function list-app-functions
+
+### Example 3: Execute a "send message" function
+
+ adb shell cmd app_function execute-app-function \
+ --package com.example.messaging \
+ --function
+ 'com.example.messaging.appfunctions.MessagingAppFunctionService#sendMessage' \
+ --parameters '{"recipient": "Alice", "message": "Hello!"}'
+
+### Example 4: Disable a specific function
+
+ adb shell cmd app_function set-enabled --package com.example.app \
+ --function 'com.example.app.appfunctions.SomeAppFunctionService#someFunction'
+ --state disable
+
+## Troubleshooting
+
+### Error: "Unknown command"
+
+**Cause**: You are likely using an older version of the instructions.
+
+**Solution**: Look up supported commands using "Example 1".
+
+### Error: "Function not found"
+
+**Cause**: The function ID or package name is incorrect.
+
+**Solution** : Run `list-app-functions` and search for the relevant identifiers
+in the JSON output.
\ No newline at end of file
diff --git a/device-ai/appfunctions/references/context.md b/device-ai/appfunctions/references/context.md
new file mode 100644
index 0000000..dc49d25
--- /dev/null
+++ b/device-ai/appfunctions/references/context.md
@@ -0,0 +1,26 @@
+Defines the ubiquitous language for the Android AppFunctions skill suite.
+
+## Architecture and versioning
+
+`AppFunctionServiceEntryPoint` API:
+This compile-time AppFunctions architecture was introduced in version
+1.0.0-alpha10. You annotate a wrapper service extending
+`AppFunctionService` with `@AppFunctionServiceEntryPoint`. KSP then generates
+XML metadata and routes services at compile time.
+
+Legacy manual provider API:
+This deprecated AppFunctions architecture applies to version
+1.0.0-alpha09 and earlier. Applications implement
+`AppFunctionConfiguration.Provider` on the `Application` class. Methods
+require `AppFunctionContext` as the first parameter. Projects depend on
+a standalone `appfunctions-service` library.
+
+## Patterns
+
+Service entry point pattern:
+Version 1.0.0-alpha10 introduced this architectural pattern. You
+declare `@AppFunction` methods directly inside an abstract class extending
+`AppFunctionService`. This class uses `@AppFunctionServiceEntryPoint` and
+`@AndroidEntryPoint` annotations. These annotations let you inject data
+sources or repositories directly, without an intermediate business logic
+delegation layer.
\ No newline at end of file
diff --git a/device-ai/appfunctions/references/feature-discovery-analysis.md b/device-ai/appfunctions/references/feature-discovery-analysis.md
new file mode 100644
index 0000000..583f67c
--- /dev/null
+++ b/device-ai/appfunctions/references/feature-discovery-analysis.md
@@ -0,0 +1,71 @@
+Analyzes Android codebases to identify and recommend high-value AppFunctions.
+
+## Instructions
+
+### Discover features
+
+1. **Analyze manifest and entry points** : Scan `AndroidManifest.xml` and Activity, Fragment, and Service classes to identify core user journeys, for example, search, create, or share.
+2. **Identify atomic tasks**: Look for methods or logic that represent distinct, self-contained user outcomes.
+3. **Evaluate AI value**: Prioritize tasks that are frequently used or difficult to navigate using touch UI, but instead be expressed using voice or text, for example "Remind me to call Alice when I get home."
+4. **Recommend and justify**: List recommendations with a rationale. Focus on how an AI assistant adds value, such as efficiency, hands-free use, or multi-step automation.
+
+## Critical constraints
+
+### Tool-first thinking
+
+Avoid recommending functions that are purely informational or redundant with
+existing system actions. Focus on "mutations" (writing data) or "rich queries"
+(finding specific entities).
+
+### Security and privacy
+
+Don't recommend exposing functions that handle raw credentials, financial
+secrets, or irreversible destructive actions without explicit user confirmation
+steps.
+
+## Examples
+
+### Example 1: Media app discovery
+
+**Recommended AppFunction** : `playArtistRadio`
+
+**Rationale**: Lets you start a personalized music stream using a voice
+command, bypassing several layers of navigation in the "Search" and "Artist"
+menus.
+
+**Input required** : `artistName` as a `String`.
+
+### Example 2: Chat app contact discovery
+
+**Recommended AppFunction** : `searchContacts`
+
+**Rationale** : Serves as a "rich query" to resolve a human-readable contact
+name, email, or chat group to a unique identifier (`endpointValue`), which is a
+prerequisite before executing actions like sending messages or
+initiating calls. Also allows retrieving recently contacted entities when given
+a blank query.
+
+**Input required** : Query as a `String`, and Filter Type as a `String`
+constrained to `"INDIVIDUAL"` or `"GROUP"`.
+
+### Example 3: Chat app message sending
+
+**Recommended AppFunction** : `send`
+
+**Rationale**: This mutation function lets you send text messages and
+optional image attachments to a contact or group using natural language
+commands, for example, "Tell Alice I'm running 5 minutes late." This
+eliminates multi-step UI navigation across contact lists and conversation
+threads.
+
+**Input required** : Endpoint Value as a `String`, Message Body as a `String`,
+and Image URIs as an optional `List` of URIs.
+
+### Example 4: Chat app voice calling
+
+**Recommended AppFunction** : `makeCall`
+
+**Rationale**: Lets you initiate voice calls hands-free to a contact or
+group using an AI agent without navigating the app's UI.
+
+**Input required** : `endpointValue` as a `String`.
\ No newline at end of file
diff --git a/device-ai/appfunctions/references/implementation-configuration.md b/device-ai/appfunctions/references/implementation-configuration.md
new file mode 100644
index 0000000..fb9292c
--- /dev/null
+++ b/device-ai/appfunctions/references/implementation-configuration.md
@@ -0,0 +1,282 @@
+Specialized instructions for generating Kotlin implementations of AppFunctions,
+handling system-wide configuration, and managing build dependencies.
+
+## Instructions
+
+### Step 1: Configure Gradle dependencies and KSP
+
+Add the following to `build.gradle.kts`. App Functions requires the KSP (Kotlin
+Symbol Processing) plugin.
+
+1. **Version check** : Use library version `1.0.0-alpha10` or later from maven.google.com.
+
+
+```kotlin
+implementation(libs.androidx.appfunctions)
+ksp(libs.androidx.appfunctions.compiler)
+```
+
+
+
+
+
+
+
+### Step 2: Set up app metadata XML
+
+Describe the app's capabilities to the LLM by defining
+`res/xml/app_metadata.xml`.
+
+
+```xml
+
+```
+
+
+
+Register the service and reference the app metadata in `AndroidManifest.xml`
+within the `` tag:
+
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+
+
+### Step 3: Implement functions
+
+When generating Kotlin code for AppFunctions, you MUST adhere to these rules:
+
+1. **Annotations** :
+ - Annotate the function with `@AppFunction(isDescribedByKDoc = true)`.
+ - Annotate associated data classes with `@AppFunctionSerializable(isDescribedByKDoc = true)`.
+2. **Parameter strategy** :
+ - **Specificity**: Keep parameters specific. State objects must be unambiguous.
+ - **Optionality**: If a parameter isn't essential, make it optional with a default value.
+3. **Execution and threading** :
+ - Use `suspend` functions.
+ - To avoid blocking the Android UI thread, always run AppFunction implementations on a background dispatcher, such as `withContext(Dispatchers.IO)`.
+4. **Supported types** :
+ - **Primitives** : `Int`, `Long`, `Float`, `Double`, `Boolean`
+ - **Arrays** : `IntArray`, `LongArray`, `FloatArray`, `DoubleArray`, `BooleanArray`
+ - **Native types** : `String`, `PendingIntent`, `Uri`, `LocalTime`, `LocalDate`, `LocalDateTime`, `Instant`. Prefer using `LocalDateTime` or `Instant` for date and time fields.
+ - **Custom objects** : Classes annotated with `@AppFunctionSerializable`.
+ - **Collections** : `List` of any supported non-primitive type
+5. **Default values** :
+ - Use defaults that align with the type's empty state, such as `0` for `Int`, `null` for nullable objects, and `emptyList()` for `List`.
+6. **Error handling** :
+ - Throw subclasses of `androidx.appfunctions.AppFunctionException` to report errors to callers.
+7. **Security** :
+ - Don't expose highly sensitive user data, such as passwords or financial details.
+ - Don't expose irreversible destructive actions without confirmation steps.
+
+### Step 4: Set up dependency injection and service entry points
+
+In version 1.0.0-alpha10 and later, App Functions use the compile-time
+`@AppFunctionServiceEntryPoint` architecture. Create an abstract class extending
+`AppFunctionService` annotated with `@AppFunctionServiceEntryPoint`. KSP
+generates the concrete service class and XML schema.
+
+#### Recommended approach with Hilt
+
+Annotate your service with `@AndroidEntryPoint` and inject your data
+repositories or use cases using standard `@Inject internal lateinit var`:
+
+
+```kotlin
+@RequiresApi(36)
+@AndroidEntryPoint
+@AppFunctionServiceEntryPoint(
+ serviceName = "ConfigAppFunctionServiceHeader",
+ appFunctionXmlFileName = "config_app_function_service_header",
+)
+abstract class BaseAppFunctionServiceHeader : AppFunctionService() {
+ @Inject internal lateinit var messageRepository: MessageRepository
+
+ @AppFunction(isDescribedByKDoc = true)
+ internal suspend fun send(
+ name: String,
+ endpointValue: String,
+ messageBody: String,
+ ): MessageResult {
+ return messageRepository.send(name, endpointValue, messageBody)
+ }
+}
+```
+
+
+
+#### Framework-agnostic approach with alternative dependency injection or service locators
+
+While Hilt is recommended, many Android applications implement AppFunctions with
+alternative dependency injection frameworks (like Koin, Anvil, or manual Service
+Locators). Because `AppFunctionService` inherits from Android
+`android.app.Service` (and therefore `Context`), you are able access your
+application's DI container directly through `applicationContext` in property
+getters or during service lifecycle execution:
+
+
+```kotlin
+@RequiresApi(36)
+@AppFunctionServiceEntryPoint(
+ serviceName = "ServiceLocatorConfigAppFunctionService",
+ appFunctionXmlFileName = "service_locator_config_app_function_service",
+)
+abstract class BaseAppFunctionServiceLocator : AppFunctionService() {
+ // Example using a manual Service Locator / Application container
+ private val messageRepository by lazy {
+ (applicationContext as AppFunctionApplication).appContainer.messageRepository
+ }
+ // Or with Koin / alternative DI locators:
+ // private val messageRepository: MessageRepository by inject()
+
+ @AppFunction(isDescribedByKDoc = true)
+ internal suspend fun send(
+ name: String,
+ endpointValue: String,
+ messageBody: String,
+ ): MessageResult {
+ return messageRepository.send(name, endpointValue, messageBody)
+ }
+}
+```
+
+
+
+### Step 5: Architectural cleanliness
+
+Don't attempt to make an `AppFunction` class or method OS-agnostic---App Functions are inherently part of the Android platform integration in `androidx.appfunctions`. For architectural cleanliness, use existing
+application functionality (such as existing repositories, use cases, or domain
+orchestrators) to execute the behavior within your `@AppFunction` methods rather
+than creating redundant abstraction layers around the OS service.
+
+
+
+## Critical constraints
+
+### KSP compliance for serializables
+
+**Critical constraints** : For `@AppFunctionSerializable` data classes, KSP
+only extracts documentation if it's written as inline KDoc directly for each
+property definition. Don't use class-level `@param` or `@property` tags.
+
+### Package integrity
+
+Configuration APIs and the `@AppFunction` annotation are located in
+`androidx.appfunctions`.
+
+## Examples
+
+### Example: Serializable with inline KDoc
+
+
+```kotlin
+/** The parameter to create the task. */
+@AppFunctionSerializable(isDescribedByKDoc = true)
+data class CreateTaskParams(
+ /** The title of the task. */
+ val title: String?,
+ /** The content of the task. */
+ val content: String?,
+)
+
+/** The user-created task. */
+@AppFunctionSerializable(isDescribedByKDoc = true)
+data class Task(
+ /** The ID of the task. */
+ val id: String,
+ /** The title of the task. */
+ val title: String,
+ /** The content of the task. */
+ val content: String,
+)
+```
+
+
+
+
+
+### Example: Implementation detail
+
+
+```kotlin
+@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)
+}
+```
+
+
+
+
+
+## Troubleshooting
+
+### Error: "AppFunction unavailable" or "Metadata missing"
+
+**Cause**: The AppSearch indexing failed to extract the schema correctly.
+
+**Solution**:
+
+1. Verify `@AppFunctionSerializable` classes use inline KDoc comments, not class-level `@param` tags.
+2. Check that the `assets/.xml` file exists in the APK.
+3. Confirm the `ksp("androidx.appfunctions:appfunctions-compiler")` dependency is correctly applied.
+4. Ensure the `ksp` argument `appfunctions:aggregateAppFunctions` is set to `"true"`.
\ No newline at end of file
diff --git a/device-ai/appfunctions/references/kdoc-refinement-optimization.md b/device-ai/appfunctions/references/kdoc-refinement-optimization.md
new file mode 100644
index 0000000..c0a3209
--- /dev/null
+++ b/device-ai/appfunctions/references/kdoc-refinement-optimization.md
@@ -0,0 +1,75 @@
+Optimizes AppFunction KDoc for AI agents and Model Context Protocol.
+
+## Instructions
+
+### Workflow: Agent-centric documentation
+
+1. **Identify the core outcome** : Start the description with a strong imperative verb, for example, "Search", "Create", or "Update". Focus on the *user benefit*, not the code implementation.
+2. **Workflow dependencies** : Explicitly state if another function must be called first using the standard phrase: **Required workflow: Call "Function
+ A" first to "Objective"**.
+3. **Parameter documentation** :
+ - For **functions** : Use specific `@param` tags. Isolate validation rules and default values here.
+ - For **serializables** : Use inline KDoc directly for each property declaration. KSP **won't** extract documentation from class-level tags.
+4. **Error surface mapping** : Rewrite `@throws` descriptions to provide useful recovery steps for the AI agent, for example "If "Error", suggest the user check their internet connection."
+
+### Workflow: Global app description for server instructions
+
+When writing the `appfn:description` for `app_metadata.xml`, follow these
+instructions:
+
+1. **Capture cross-function relationships**: Explain dependencies or sequences between tools, for example, "Always call 'authenticate' before fetching data.".
+2. **Document operational patterns**: Guide the LLM on token-conserving usage, for example, "Use 'batch_update' over multiple 'update' calls."
+3. **Specify constraints**: Define clear boundaries, for example, "File operations limited to workspace" or "Rate limit: 10 requests per minute."
+4. **Anti-patterns** :
+ - Don't repeat individual function descriptions.
+ - Don't include marketing claims or subjective praise.
+ - Don't attempt to prompt model personality or conversation style.
+
+## Critical constraints
+
+### Descriptive, not imperative
+
+Describe what the function *does* , not what the LLM *must* do. Avoid phrases
+like "You must call this..." in favor of "This function provides...".
+
+### No "fluff"
+
+Remove conversational padding like "This method is used to..." or "Helpful
+for...". Be concise and technical.
+
+### Inline KDoc for serializables
+
+**Mandatory** : For `@AppFunctionSerializable` classes, documentation must be
+inline for each property. KSP ignores class-level `@param` or `@property` tags
+for these classes.
+
+## Examples
+
+### Example: MCP refactoring
+
+**Original**:
+
+`/** This function helps you find people. */`
+
+**Refined**:
+
+ /**
+ * Search for message recipients by name or email.
+ * Required workflow: Call this before "sendMessage" to obtain valid recipient IDs.
+ * @param query Search string for name/email. If null, returns 3 most recent contacts.
+ * @return List of "Recipient" objects matching the query.
+ */
+
+### Example: Global app description
+
+**Refined**:
+
+ This app provides functions for task management and team collaboration.
+
+ Operational Patterns:
+ - Always use 'searchUsers' to resolve user handles to internal IDs before calling 'assignTask'.
+ - Prefer 'batchUpdateStatus' when modifying more than three tasks simultaneously to reduce latency.
+
+ Constraints:
+ - Task titles are limited to 100 characters.
+ - Attachment uploads are limited to 5 MB.
\ No newline at end of file
diff --git a/device-ai/appfunctions/references/migrate-to-service-entry-point.md b/device-ai/appfunctions/references/migrate-to-service-entry-point.md
new file mode 100644
index 0000000..f350f61
--- /dev/null
+++ b/device-ai/appfunctions/references/migrate-to-service-entry-point.md
@@ -0,0 +1,226 @@
+Follow this systematic procedure to migrate Android applications that use the
+AppFunctions API in version 1.0.0-alpha09 and lower to the compile-time
+`@AppFunctionServiceEntryPoint` architecture introduced in version
+`1.0.0-alpha10`.
+
+*** ** * ** ***
+
+## Architectural shift overview
+
+In lower versions of the AppFunctions API, for example version `1.0.0-alpha09`:
+
+- Applications require separate dependencies for core functionality, specifically `androidx.appfunctions:appfunctions`, and service components, specifically `androidx.appfunctions:appfunctions-service`.
+- The application implements `AppFunctionConfiguration.Provider` on its `Application` class.
+- You manually register enclosing class instantiation using `AppFunctionConfiguration.Builder().addEnclosingClassFactory(...)`.
+- You place metadata property tags directly under ``.
+
+In version `1.0.0-alpha10` featuring `@AppFunctionServiceEntryPoint`:
+
+- The core and service dependencies are consolidated into a single runtime artifact, `androidx.appfunctions:appfunctions`, which eliminates the need for the standalone `appfunctions-service` library.
+- A dedicated wrapper class extending `AppFunctionService` is annotated with `@AppFunctionServiceEntryPoint` and Hilt's `@AndroidEntryPoint` or an alternative dependency injection framework.
+- The KSP compiler generates a concrete service subclass and an XML metadata schema file in `assets/`.
+- The OS discovers and routes executions using a consolidated `` and `app_metadata` declaration in `AndroidManifest.xml`.
+
+### Strict migration requirements from 1.0.0-alpha09 to 1.0.0-alpha10
+
+When focusing solely on the mandatory API changes required by the new
+`@AppFunctionServiceEntryPoint` architecture, the migration consists of four
+strict requirements that you must complete:
+
+1. **Build dependency consolidation** : Remove the merged `appfunctions-service` dependency while retaining core `appfunctions` and the KSP compiler.
+2. **Service wrapper creation** : Replace the legacy `AppFunctionConfiguration.Provider` on the `Application` class with an abstract class extending `AppFunctionService`, annotated with `@AppFunctionServiceEntryPoint`.
+3. **Annotation and context decoupling** : Move `@AppFunction` annotations to the new wrapper methods and drop `AppFunctionContext` parameters because `AppFunctionService` inherits directly from `Context`.
+4. **Manifest registration** : Register the KSP-generated concrete service in `AndroidManifest.xml` with `BIND_APP_FUNCTION_SERVICE`, the `AppFunctionService` intent filter, and metadata property tags.
+
+*** ** * ** ***
+
+## Systematic migration steps
+
+### Consolidate AppFunctions build dependencies
+
+Remove the standalone `appfunctions-service` library from your module build
+files like `build.gradle.kts` and version catalog like `libs.versions.toml`. In
+version `1.0.0-alpha10`, all core service capabilities are consolidated directly
+within the main `appfunctions` artifact.
+
+ // build.gradle.kts
+ dependencies {
+ implementation(libs.androidx.appfunctions)
+ - implementation(libs.androidx.appfunctions.service)
+ ksp(libs.androidx.appfunctions.compiler)
+ }
+
+ # libs.versions.toml
+ [versions]
+ appfunctions = "1.0.0-alpha10"
+
+ [libraries]
+ androidx-appfunctions = { module = "androidx.appfunctions:appfunctions", version.ref = "appfunctions" }
+ - androidx-appfunctions-service = { module = "androidx.appfunctions:appfunctions-service", version.ref = "appfunctions" }
+ androidx-appfunctions-compiler = { module = "androidx.appfunctions:appfunctions-compiler", version.ref = "appfunctions" }
+
+> [!NOTE]
+> **Note:** If another dependency in your project uses snapshot builds like `1.0.0-SNAPSHOT` or custom snapshot repositories from `https://androidx.dev/snapshots/...`, preserve your custom snapshot repository configuration in `settings.gradle.kts`. Otherwise, standard Google Maven repositories resolve `1.0.0-alpha10` directly.
+
+*** ** * ** ***
+
+### Create a dedicated wrapper service extending `AppFunctionService`
+
+Instead of annotating standalone business logic classes or implementing manual
+configuration providers, create an abstract service wrapper across your project,
+for example `BaseAppFunctionService`, extending `AppFunctionService` and
+annotated with `@AppFunctionServiceEntryPoint`.
+
+#### Recommended approach using Hilt
+
+Annotate your service with `@AndroidEntryPoint` and inject your data
+repositories or use cases using standard `@Inject internal lateinit var`:
+
+
+```kotlin
+@RequiresApi(36)
+@AndroidEntryPoint
+@AppFunctionServiceEntryPoint(
+ serviceName = "MyAppFunctionService",
+ appFunctionXmlFileName = "my_app_function_service", // Do NOT include .xml extension
+)
+abstract class BaseAppFunctionService : AppFunctionService() {
+ @Inject internal lateinit var messageRepository: MessageRepository
+
+ @AppFunction(isDescribedByKDoc = true)
+ internal suspend fun send(
+ name: String,
+ endpointValue: String,
+ messageBody: String,
+ ): MessageResult {
+ return messageRepository.send(name, endpointValue, messageBody)
+ }
+}
+```
+
+
+
+#### Framework-agnostic approach using alternative dependency injection or a service locator
+
+While Hilt is recommended, many Android applications implement AppFunctions with
+alternative dependency injection frameworks like Koin, Anvil, or manual Service
+Locators. Because `AppFunctionService` inherits from Android
+`android.app.Service` and therefore `Context`, you are able access your
+application's DI container directly through `applicationContext` in property
+getters or during service lifecycle execution:
+
+
+```kotlin
+@RequiresApi(36)
+@AppFunctionServiceEntryPoint(
+ serviceName = "ServiceLocatorMyAppFunctionService",
+ appFunctionXmlFileName = "service_locator_my_app_function_service",
+)
+abstract class ServiceLocatorBaseAppFunctionService : AppFunctionService() {
+ // Example using a manual Service Locator or Application container
+ private val messageRepository by lazy {
+ (applicationContext as AppFunctionApplication).appContainer.messageRepository
+ }
+ // Or with Koin / alternative DI locators:
+ // private val messageRepository: MessageRepository by inject()
+
+ @AppFunction(isDescribedByKDoc = true)
+ internal suspend fun send(
+ name: String,
+ endpointValue: String,
+ messageBody: String,
+ ): MessageResult {
+ return messageRepository.send(name, endpointValue, messageBody)
+ }
+}
+```
+
+
+
+> [!IMPORTANT]
+> **Important:** The `appFunctionXmlFileName` parameter, for example `"my_app_function_service"`, mustn't include the `.xml` extension, as the KSP compiler automatically appends `.xml`. Passing `"my_app_function_service.xml"` results in the asset being named `"my_app_function_service.xml.xml"`.
+
+*** ** * ** ***
+
+### Simplify method signatures and decouple context
+
+Remove legacy `AppFunctionContext` parameters from your core methods. When a
+method requires an Android `Context`, for example when constructing a
+`PendingIntent`, access `this` directly from your `AppFunctionService` wrapper
+because the wrapper inherently extends `android.content.Context`.
+
+ - suspend fun makeCall(appFunctionContext: AppFunctionContext, contactName: String?): PendingIntent
+ + suspend fun makeCall(contactName: String?): PendingIntent
+
+*** ** * ** ***
+
+### Remove legacy configuration provider
+
+Update your `Application` class by removing
+`AppFunctionConfiguration.Provider` and its associated builder entry points:
+
+ - abstract class BaseChatApplication : Application(), AppFunctionConfiguration.Provider { ... }
+ + abstract class BaseChatApplication : Application()
+
+*** ** * ** ***
+
+### Avoid redundant abstraction layers
+
+Don't attempt to make an `AppFunction` class or method OS-agnostic---AppFunctions
+are inherently part of the Android platform integration through the
+`androidx.appfunctions` package. For architectural cleanliness, use existing
+application functionality, such as existing repositories, use cases, or domain
+orchestrators, to execute the behavior within your `@AppFunction` methods rather
+than creating redundant abstraction layers around the OS service.
+
+*** ** * ** ***
+
+### Consolidate service and metadata manifest declarations
+
+Register the KSP-generated service declaration and `app_metadata` property
+inside your module manifest, for example in `src/main/AndroidManifest.xml`
+within the `` tag:
+
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+
+
+*** ** * ** ***
+
+## Verification and troubleshooting
+
+1. **Clean rebuild and deploy** : `bash
+ ./gradlew clean installDebug`
+2. **Verify AppSearch discovery / indexing** : Run the following ADB command to
+ confirm the OS successfully discovered and indexed your functions:
+ `bash
+ adb shell cmd app_function list-app-functions`
+ *If your package doesn't appear, confirm that `android.app.appfunctions.v2`
+ matches the exact asset name generated in `assets/`.*
+
+3. **Verify execution using ADB** :
+ `bash
+ adb shell "cmd app_function execute-app-function \
+ --package com.example.chatapp \
+ --function 'com.example.chatapp.appfunctions.BaseAppFunctionService#send' \
+ --parameters '{\"name\": \"Alice\", \"endpointValue\": \"1\", \"messageBody\": \"Hello Alice!\"}'"`
\ No newline at end of file
diff --git a/device-ai/litert/litert-runtime/SKILL.md b/device-ai/litert/litert-runtime/SKILL.md
new file mode 100644
index 0000000..3148fc1
--- /dev/null
+++ b/device-ai/litert/litert-runtime/SKILL.md
@@ -0,0 +1,358 @@
+---
+name: litert-runtime
+description: Configures and implements LiteRT (.tflite) on-device inference on Android
+ in Kotlin using the CompiledModel API on CPU and GPU. Use this skill for any Android
+ LiteRT or .tflite task, including Gradle setup, image classification or vision/audio/embedding
+ helpers, TensorBuffer reuse, Bitmap preprocessing, AndroidViewModel GPU-to-CPU fallback
+ with warm-up on a single-thread dispatcher, migrating from legacy org.tensorflow.lite.Interpreter
+ or GpuDelegate, and latency benchmarking. Don't use for running generative text
+ LLMs with .litertlm files or for Python model conversion.
+license: Complete terms in LICENSE.txt
+metadata:
+ author: Google LLC
+ last-updated: '2026-10-13'
+ keywords:
+ - LiteRT
+ - CompiledModel
+ - tflite
+ - Android app
+ - GPU
+ - TensorBuffer
+---
+
+This skill provides step-by-step guidance for building an Android app that runs
+a `.tflite` model with the [LiteRT CompiledModel API](https://ai.google.dev/edge/litert/android)
+(`com.google.ai.edge.litert:litert` 2.x; [source repository](https://github.com/google-ai-edge/litert/blob/main/README.md)) on the CPU or
+the GPU. Don't use the deprecated `org.tensorflow.lite.Interpreter` API. For
+open text language models using `.litertlm` files, suggest the `litert-lm`
+skill.
+
+## Prerequisites
+
+- A Kotlin Android project (`minSdk` must be `24` or higher, as declared by the LiteRT 2.3.0 AARs).
+- The dependencies in the app-level `build.gradle.kts` file from Google Maven: `implementation("com.google.ai.edge.litert:litert:2.3.0")` and `implementation("com.google.ai.edge.litert:litert-gpu:2.3.0")`. From 2.3.0 the GPU accelerator is the second artifact: with `litert` alone the app builds, and `CompiledModel.create` with `Accelerator.GPU` throws `LiteRtException` because the APK has no GPU accelerator library (Logcat logs `GPU accelerator
+ could not be loaded and registered`). No manifest entry is needed: `litert-api`, which `litert` depends on, declares the OpenCL driver libraries (`uses-native-library`) in its own manifest.
+- A verified `.tflite` model and its input contract (input dimensions, mean, standard deviation, channel order, and tensor layout). For models with a verified recipe, inspect the [LiteRT model recipes](https://github.com/google-ai-edge/litert-samples/blob/main/models/README.md).
+
+## Critical mandates
+
+- **Single-thread confinement** : One single-thread executor or confined coroutine dispatcher must own the `CompiledModel` instance. Creation, inference, and closing must execute on that same background thread, never on the main thread.
+- **Buffer lifecycle** : Allocate `TensorBuffer` instances once using `createInputBuffers` and `createOutputBuffers`, reuse them across inferences, and close all input and output buffers before closing the `CompiledModel`.
+- **Synchronous timing** : `model.run` enqueues work on the GPU and returns before GPU execution completes. Always measure `model.run` and output buffer readback (`outputs[0].readFloat`) together.
+- **Warm-up and exception safety** : Execute one warm-up inference inside the helper constructor's `init` block so first-run GPU shader compilation doesn't block the first user interaction, and call `close` in `catch` before rethrowing so neither `CompiledModel` nor `TensorBuffer` handles leak if buffer allocation or warm-up throws.
+
+## Detailed steps
+
+### Step 1: Configure Gradle and place the model file
+
+Search the project for `libs.versions.toml`, `gradle.properties`,
+`app/build.gradle.kts`, and any existing `org.tensorflow.lite` imports before
+editing build files. Configure `minSdk` and add the LiteRT runtime and GPU
+accelerator dependencies in `app/build.gradle.kts`:
+
+ android {
+ defaultConfig {
+ minSdk = 24
+ }
+ }
+
+ dependencies {
+ implementation("com.google.ai.edge.litert:litert:2.3.0")
+ implementation("com.google.ai.edge.litert:litert-gpu:2.3.0")
+ }
+
+Place bundled models at `app/src/main/assets/model.tflite` (AGP stores `.tflite`
+files uncompressed by default). Deliver a model that exceeds the base APK size
+limit through [Play for On-Device AI](references/android/google/play/on-device-ai.md) as an AI pack, or download it into
+`context.filesDir` (`bundletool --local-testing` installs AI packs locally
+without Google Play). Read an install-time AI pack through `AssetManager`. Load
+a fast-follow or on-demand AI pack or a downloaded file from its on-disk
+`filePath` using `CompiledModel.create(filePath,
+CompiledModel.Options(accelerator), environment)` in place of the asset call in
+Step 2. Obtain the pack's directory from the AI Delivery library
+(`com.google.android.play:ai-delivery`) using
+`AiPackManagerFactory.getInstance(context).getPackLocation(name)?.assetsPath()`,
+which returns `null` until the pack has finished downloading.
+
+With 2.3.0 nothing is added to `gradle.properties`: `litert` 2.3.0 declares the
+namespace `com.google.ai.edge.litert.impl` and its dependency `litert-api` 2.3.0
+declares `com.google.ai.edge.litert`, so AGP 9.x builds the project. With 2.2.0
+or 2.1.6 both libraries declare `com.google.ai.edge.litert`, and AGP 9.x stops
+at `processDebugMainManifest` (AGP 8.x reports it as a warning and builds); for
+those two versions add `android.uniquePackageNames=false` to
+`gradle.properties`, and remove it when moving to 2.3.0 because it also hides
+the same clash between any other two libraries. Excluding `litert-api` instead
+doesn't work: `CompiledModel` and `Accelerator` are in `litert-api`, and the
+build then fails at `compileDebugKotlin`.
+
+### Step 2: Implement the inference helper class
+
+Isolate `CompiledModel` and its `TensorBuffer` instances in a dedicated
+`AutoCloseable` helper class free of Android UI types:
+
+ import android.content.Context
+ import com.google.ai.edge.litert.Accelerator
+ import com.google.ai.edge.litert.CompiledModel
+ import com.google.ai.edge.litert.Environment
+ import com.google.ai.edge.litert.TensorBuffer
+
+ const val INPUT_SIZE = 224 * 224 * 3
+ private val environment by lazy { Environment.create() }
+
+ class Classifier(context: Context, accelerator: Accelerator) : AutoCloseable {
+ private val model = CompiledModel.create(
+ context.assets,
+ "model.tflite",
+ CompiledModel.Options(accelerator),
+ environment
+ )
+ private val inputs = mutableListOf()
+ private val outputs = mutableListOf()
+
+ init {
+ try {
+ inputs += model.createInputBuffers()
+ outputs += model.createOutputBuffers()
+ infer(FloatArray(INPUT_SIZE))
+ } catch (e: Exception) {
+ close()
+ throw e
+ }
+ }
+
+ fun infer(input: FloatArray): FloatArray {
+ inputs[0].writeFloat(input)
+ model.run(inputs, outputs)
+ return outputs[0].readFloat()
+ }
+
+ override fun close() {
+ (inputs + outputs).forEach { it.close() }
+ model.close()
+ }
+ }
+
+`Accelerator.CPU` requires nothing from the device. `Accelerator.GPU` compiles
+the graph for the GPU; when the GPU can't execute the model, the constructor
+throws `LiteRtException` (from `create` for an operator the GPU doesn't support,
+and on the Android emulator from `create` or from buffer allocation). A missing
+asset or a file that isn't a valid model also throws `LiteRtException` from
+`create`.
+
+The buffers are created once with the model, reused for every inference, and
+closed before the model. One `Environment` serves every model in the process and
+stays open, so LiteRT loads the GPU accelerator library once rather than on
+every `create` call. The constructor ends with one warm-up inference (the first
+GPU run includes shader compilation) and closes the model and its buffers if any
+step in `init` throws. `INPUT_SIZE` is the model's input element count:
+`writeFloat` throws on a longer array and writes a shorter one without an error,
+so a wrong size produces an incorrect result. For `int8` or `uint8` tensors, use
+`writeInt8` and `readInt8` on `TensorBuffer` instead of `writeFloat` and
+`readFloat`.
+
+### Step 3: Wire the ViewModel and Compose screen
+
+Drive the helper from a single-thread executor inside an `AndroidViewModel`,
+fall back to `Accelerator.CPU` when GPU compilation fails, and expose immutable
+state to the Compose UI:
+
+ import android.app.Application
+ import androidx.lifecycle.AndroidViewModel
+ import com.google.ai.edge.litert.Accelerator
+ import com.google.ai.edge.litert.LiteRtException
+ import java.util.concurrent.Executors
+ import kotlinx.coroutines.CoroutineScope
+ import kotlinx.coroutines.SupervisorJob
+ import kotlinx.coroutines.asCoroutineDispatcher
+ import kotlinx.coroutines.cancel
+ import kotlinx.coroutines.flow.MutableStateFlow
+ import kotlinx.coroutines.flow.StateFlow
+ import kotlinx.coroutines.flow.asStateFlow
+ import kotlinx.coroutines.flow.update
+ import kotlinx.coroutines.launch
+
+ data class UiState(
+ val ready: Boolean = false,
+ val accelerator: Accelerator = Accelerator.CPU,
+ val result: List? = null,
+ val error: String? = null
+ )
+
+ class MainViewModel(app: Application) : AndroidViewModel(app) {
+ private val executor = Executors.newSingleThreadExecutor()
+ private val scope = CoroutineScope(
+ SupervisorJob() + executor.asCoroutineDispatcher()
+ )
+ private var classifier: Classifier? = null
+ private val _uiState = MutableStateFlow(UiState())
+ val uiState: StateFlow = _uiState.asStateFlow()
+
+ fun load(preferred: Accelerator = Accelerator.GPU) {
+ scope.launch {
+ if (classifier != null) return@launch
+ val appContext = getApplication()
+ val selected = try {
+ classifier = Classifier(appContext, preferred)
+ preferred
+ } catch (e: LiteRtException) {
+ if (preferred != Accelerator.CPU) {
+ try {
+ classifier = Classifier(appContext, Accelerator.CPU)
+ Accelerator.CPU
+ } catch (cpuError: LiteRtException) {
+ _uiState.value = UiState(error = cpuError.message)
+ return@launch
+ }
+ } else {
+ _uiState.value = UiState(error = e.message)
+ return@launch
+ }
+ }
+ _uiState.value = UiState(ready = true, accelerator = selected)
+ }
+ }
+
+ fun classify(input: FloatArray) {
+ scope.launch {
+ try {
+ classifier?.infer(input)?.let { result ->
+ _uiState.update {
+ it.copy(result = result.toList(), error = null)
+ }
+ }
+ } catch (e: LiteRtException) {
+ _uiState.update { it.copy(error = e.message) }
+ }
+ }
+ }
+
+ override fun onCleared() {
+ scope.launch { classifier?.close() }.invokeOnCompletion {
+ scope.cancel()
+ }
+ executor.shutdown()
+ }
+ }
+
+One single-thread executor owns the model: creation, inference, and closing
+execute only on it, never on the main thread. The `ViewModel` keeps its own
+scope because `viewModelScope` is cancelled before `onCleared` runs, so a close
+launched there wouldn't run; `onCleared` cancels `scope` once `close` completes.
+`load` is a no-op when a model is already loaded across configuration changes; a
+failed `load` leaves `classifier` `null`. `classify` reports any
+`LiteRtException` (in package `com.google.ai.edge.litert`) in `error` and keeps
+the model.
+
+The following Compose screen takes the picture (`bitmap`) from the photo picker
+or CameraX and runs `preprocess` in the click handler (a few milliseconds for
+224x224; move `preprocess` onto the executor for images exceeding 224x224).
+`viewModel` and `collectAsStateWithLifecycle` come from
+`androidx.lifecycle:lifecycle-viewmodel-compose` and
+`androidx.lifecycle:lifecycle-runtime-compose` (2.10.0):
+
+ import android.graphics.Bitmap
+ import androidx.compose.foundation.layout.Column
+ import androidx.compose.material3.Button
+ import androidx.compose.material3.Text
+ import androidx.compose.runtime.Composable
+ import androidx.compose.runtime.LaunchedEffect
+ import androidx.compose.runtime.getValue
+ import androidx.lifecycle.compose.collectAsStateWithLifecycle
+ import androidx.lifecycle.viewmodel.compose.viewModel
+ import com.google.ai.edge.litert.Accelerator
+
+ @Composable
+ fun MainScreen(
+ bitmap: Bitmap,
+ viewModel: MainViewModel = viewModel()
+ ) {
+ val state by viewModel.uiState.collectAsStateWithLifecycle()
+ LaunchedEffect(Unit) { viewModel.load(Accelerator.GPU) }
+ Column {
+ state.error?.let { Text(it) }
+ Button(
+ onClick = { viewModel.classify(preprocess(bitmap)) },
+ enabled = state.ready
+ ) {
+ Text("Run (${state.accelerator})")
+ }
+ state.result?.let { scores ->
+ Text("Top class: ${scores.indices.maxBy { i -> scores[i] }}")
+ }
+ }
+ }
+
+### Step 4: Preprocess inputs to match the model contract
+
+Convert the `Bitmap` into the model's input with `preprocess`. Match the
+model's exact input dimensions, normalization mean and standard deviation,
+channel order, and memory layout (this example uses NHWC, RGB, scaled to
+`-1.0..1.0`). Mismatched `mean` or `std` values produce corrupted predictions
+that look like a broken model:
+
+ import android.graphics.Bitmap
+
+ fun preprocess(
+ bitmap: Bitmap,
+ size: Int = 224,
+ mean: Float = 127.5f,
+ std: Float = 127.5f
+ ): FloatArray {
+ val scaled = checkNotNull(
+ Bitmap.createScaledBitmap(bitmap, size, size, true)
+ .copy(Bitmap.Config.ARGB_8888, false)
+ )
+ val pixels = IntArray(size * size).also {
+ scaled.getPixels(it, 0, size, 0, 0, size, size)
+ }
+ val out = FloatArray(size * size * 3)
+ pixels.forEachIndexed { i, p ->
+ out[i * 3] = ((p shr 16 and 0xFF) - mean) / std
+ out[i * 3 + 1] = ((p shr 8 and 0xFF) - mean) / std
+ out[i * 3 + 2] = ((p and 0xFF) - mean) / std
+ }
+ return out
+ }
+
+The scaled bitmap is copied to `Bitmap.Config.ARGB_8888` because `ImageDecoder`
+returns `Bitmap.Config.HARDWARE` bitmaps by default on Android 9 and higher
+(including from a photo picker `Uri`), scaling preserves that config, and
+`getPixels` can't read `HARDWARE` bitmaps; the copy is of the 224x224 scaled
+bitmap rather than the full image. Scaling doesn't preserve the aspect ratio;
+crop first if the model requires center cropping. An audio or embedding model
+replaces `preprocess` with its own input encoding and keeps the inference class.
+
+### Step 5: Verify output on a physical device
+
+Run the app on a physical device that runs Android from Android Studio. Compare
+the app's output with the model's reference output for one fixed input on
+`Accelerator.CPU` first and then on `Accelerator.GPU`; when verifying model
+outputs on a physical device, you must follow [device verification](references/verify.md).
+
+## Migrating from legacy TFLite Interpreter
+
+When upgrading an existing project from `org.tensorflow.lite.Interpreter`,
+replace legacy constructs with their `CompiledModel` equivalents:
+
+- **Gradle dependencies** : Remove all `org.tensorflow:tensorflow-lite*` artifacts (`org.tensorflow:tensorflow-lite`, `org.tensorflow:tensorflow-lite-gpu`, `org.tensorflow:tensorflow-lite-support`, and `org.tensorflow:tensorflow-lite-metadata`). Replace them with `com.google.ai.edge.litert:litert:2.3.0` and `com.google.ai.edge.litert:litert-gpu:2.3.0`. If you still need Support or Metadata utilities (such as `ImageProcessor`, `TensorImage`, or `MetadataExtractor`), use `com.google.ai.edge.litert:litert-support:2.3.0` (or `litert-support-api:2.3.0` alongside `litert:2.3.0`) and `com.google.ai.edge.litert:litert-metadata:2.3.0`, and rename package imports from `org.tensorflow.lite.support.*` to `com.google.ai.edge.litert.support.*` per the [LiteRT Support migration guide](https://github.com/google-ai-edge/LiteRT/blob/main/litert/support_lib/MIGRATION_GUIDE.md).
+- **Model loading** : Replace `FileUtil.loadMappedFile` and `Interpreter` with `CompiledModel.create(context.assets, name, options, environment)`.
+- **GPU delegation** : Replace `GpuDelegate` on `Interpreter.Options` with `CompiledModel.Options(Accelerator.GPU)` and the `com.google.ai.edge.litert:litert-gpu:2.3.0` dependency.
+- **Execution** : Replace `interpreter.run` on `ByteBuffer` inputs with `inputs[0].writeFloat(input)`, `model.run(inputs, outputs)`, and `outputs[0].readFloat`.
+- **Label loading** : Replace `FileUtil.loadLabels` with `bufferedReader` and `readLines` on `context.assets.open(name)` (or `com.google.ai.edge.litert.support.common.FileUtil.loadLabels` if using `litert-support:2.3.0`).
+
+## Antipatterns
+
+- **Never** create, run, or close `CompiledModel` on the Android main thread.
+- **Never** allocate new `TensorBuffer` instances on every inference call; failing to close per-frame `TensorBuffer` objects leaks C++ heap memory.
+- **Never** benchmark `model.run` without including output buffer readback (`outputs[0].readFloat`).
+- **Never** mix `org.tensorflow:tensorflow-lite*` artifacts (such as `org.tensorflow:tensorflow-lite-support`) with `com.google.ai.edge.litert:*` artifacts; both ship the `org.tensorflow.lite.*` classes and cause duplicate class build failures.
+- **Never** use `Accelerator.GPU` on LiteRT 2.3.0 without adding the `com.google.ai.edge.litert:litert-gpu:2.3.0` dependency alongside `com.google.ai.edge.litert:litert:2.3.0`.
+
+## Checklist
+
+- \[ \] Is `minSdk` set to `24` or higher and are both `com.google.ai.edge.litert:litert:2.3.0` and `com.google.ai.edge.litert:litert-gpu:2.3.0` added in `app/build.gradle.kts`?
+- \[ \] Are all legacy `org.tensorflow:tensorflow-lite*` dependencies and `org.tensorflow.lite.*` imports removed?
+- \[ \] Do `CompiledModel.create`, `model.run`, and `close` all execute on the same single-thread background dispatcher?
+- \[ \] Are `TensorBuffer` instances allocated once, reused across calls, and closed before `model.close`, including when `init` or warm-up throws?
+- \[ \] Does `load` execute one warm-up inference and fall back to `Accelerator.CPU` if `Accelerator.GPU` throws `LiteRtException`?
+- \[ \] Does the project compile with `./gradlew assembleDebug`?
diff --git a/device-ai/litert/litert-runtime/references/android/google/play/on-device-ai.md b/device-ai/litert/litert-runtime/references/android/google/play/on-device-ai.md
new file mode 100644
index 0000000..f781d3b
--- /dev/null
+++ b/device-ai/litert/litert-runtime/references/android/google/play/on-device-ai.md
@@ -0,0 +1,818 @@
+## Introduction
+
+Play for On-device AI brings the benefits of
+[Android App Bundles](https://developer.android.com/guide/app-bundle) and Google Play delivery to custom ML
+model distribution so you can improve model performance with less device
+ecosystem complexity at no additional cost. It lets you publish a single
+artifact to Play containing your code, assets, and ML models and to choose
+from a number of delivery modes and targeting options.
+
+### Benefits
+
+- Upload a single publishing artifact to Google Play and delegate hosting, delivery, updates, and targeting to Play at no additional cost.
+- Deliver your ML models at install-time, fast-follow, or on-demand.
+ - Install-time delivery can guarantee that a very large model is present when your app is opened. Your model will be installed as an APK.
+ - Fast-follow delivery occurs automatically in the background after your app has been installed. Users may open your app before your model has been fully downloaded. Your model will be downloaded to your app's internal storage space.
+ - On-demand delivery lets you request the model at runtime, which is useful if the model is only required for certain user-flows. Your model will be downloaded to your app's internal storage space.
+- Deliver variants of your ML models that are targeted to specific devices based on device model, system properties, or RAM.
+- Keep app updates small and optimized with Play's automatic patching, which means only the differences in files need to be downloaded.
+
+### Considerations
+
+- By using Play for On-device AI you agree to the terms in the [Google Play Developer Distribution Agreement](https://play.google.com/about/developer-distribution-agreement.html) and the [Play Core Software Development Kit Terms of Service](https://developer.android.com/guide/playcore#license).
+- Models downloaded by Play for On-device AI should only be used by your apps. Models shouldn't be offered to other apps.
+- Individual AI packs can be up to 1.5GB, based on their compressed download sizes. The maximum cumulative app size of any version of your app generated from your app bundle is 4GB.
+- Apps over 1GB in size must set min SDK Level to 21 or higher.
+
+### How to use Play for On-device AI
+
+Play for On-device AI uses AI packs. You package custom models that are ready
+for distribution in AI packs in your app bundle. You can choose whether the AI
+pack should be delivered at install-time, fast-follow, or on-demand.
+
+By packaging AI packs with your app bundle, you can use all of Play's existing
+testing and release tools, such as test tracks and staged rollouts to manage
+your app's distribution with your custom models.
+
+AI packs are updated together with the app binary. If your new app release
+doesn't make changes to an AI pack, then Play's automatic patching process will
+ensure the user doesn't have to re-download it. Play will just download what's
+changed when it updates the app.
+
+AI packs only contain models. Java/Kotlin and native libraries are not allowed.
+If you need to ship libraries or code to run your ML model, move it into the
+base module or a [feature module](https://developer.android.com/guide/playcore/feature-delivery). You can
+configure your feature module so that it has the same download and targeting
+settings as the AI pack.
+
+> [!NOTE]
+> **Note:** AI packs don't contain Java/Kotlin or native libraries. You can ship libraries or code to run your ML models in [feature modules](https://developer.android.com/guide/playcore/feature-delivery).
+
+### Use LiteRT and MediaPipe with AI packs
+
+You can use LiteRT and MediaPipe with AI packs. Package your model in an AI pack
+and then access it using the instructions for
+[install-time packs](https://developer.android.com/google/play/on-device-ai#install-time-delivery) or for
+[fast-follow and on-demand packs](https://developer.android.com/google/play/on-device-ai#ff-od-delivery).
+
+Further reading:
+
+- [Getting started with LiteRT](https://ai.google.dev/edge/litert/android)
+ - The [sample app](https://developer.android.com/google/play/on-device-ai#example-app) shows how you can package a LiteRT model in an AI pack and load it at runtime.
+ - There are many [pretrained LiteRT models](https://ai.google.dev/edge/litert/models/trained) that you can use in AI packs to get started.
+- [Getting started with MediaPipe](https://ai.google.dev/edge/mediapipe/framework/getting_started/android)
+ - For fast-follow and on-demand packs, you can use [AssetCache.java](https://github.com/google-ai-edge/mediapipe/blob/master/mediapipe/java/com/google/mediapipe/framework/AssetCache.java) to load your assets (eg. `.binarypb` files) by their file paths.
+ - For install-time packs, you can use [AndroidAssetUtil.java](https://github.com/google-ai-edge/mediapipe/blob/master/mediapipe/java/com/google/mediapipe/framework/AndroidAssetUtil.java#L56).
+
+## Get started with AI packs
+
+At a high level, here's how you can start using Play for On-device AI:
+
+1. Package your models into AI packs into your Android App Bundle and specify how the AI packs should be delivered.
+2. \[Optional\] If you want to deliver different models to different devices, you can [configure device targeting](https://developer.android.com/google/play/on-device-ai#device-targeting-configuration) for your AI packs. For example, you could deliver AI pack A to a specific device model, AI pack B to devices with at least 6GB of RAM, and all other devices could receive no model.
+3. \[Optional\] If you're using on-demand or fast-follow delivery, integrate the Play AI Delivery Library into your app to download your AI packs as needed.
+4. Test and release your app bundle to Google Play.
+
+### Check Android Gradle Plugin version
+
+To use AI packs, ensure that your Android Gradle Plugin (AGP) version is at
+least 8.8. This version is packaged with Android Studio Ladybug 2.
+
+### Extract your model into an AI pack
+
+Android Studio is not required for the following steps.
+
+1. In the top-level directory of your project, create a directory for the AI pack. This directory name is used as the AI pack name. AI pack names must start with a letter and can only contain letters, numbers, and underscores.
+2. In the AI pack directory, create a `build.gradle` file and add the following
+ code. Make sure to specify the name of the AI pack and only one delivery
+ type:
+
+ // In the AI pack's build.gradle file:
+ plugins {
+ id 'com.android.ai-pack'
+ }
+
+ aiPack {
+ packName = "ai-pack-name" // Directory name for the AI pack
+ dynamicDelivery {
+ deliveryType = "[ install-time | fast-follow | on-demand ]"
+ }
+ }
+
+3. In the project's app `build.gradle` file, add the name of every AI pack in
+ your project as shown below:
+
+ // In the app build.gradle file:
+ android {
+ ...
+ assetPacks = [":ai-pack-name", ":ai-pack2-name"]
+ }
+
+4. In the project's `settings.gradle` file, include all AI packs in your
+ project as shown below:
+
+ // In the settings.gradle file:
+ include ':app'
+ include ':ai-pack-name'
+ include ':ai-pack2-name'
+
+5. Inside your AI pack, create a `src/main/assets/` directory.
+
+6. Place your models in the `src/main/assets` directory. You can create
+ subdirectories in here as well. The directory structure for your app should
+ now look like the following:
+
+ - `build.gradle`
+ - `settings.gradle`
+ - `app/`
+ - `ai-pack-name/build.gradle`
+ - `ai-pack-name/src/main/assets/your-model-directories`
+7. Add code to load and run your models. How you do this will depend on the
+ delivery mode of your AI packs. See instructions for
+ [install-time](https://developer.android.com/google/play/on-device-ai#install-time-delivery) and
+ [fast-follow/on-demand](https://developer.android.com/google/play/on-device-ai#ff-od-delivery) below.
+
+8. \[Optional\] [Configure device targeting](https://developer.android.com/google/play/on-device-ai#device-targeting-configuration)
+ to deliver different models to different devices.
+
+9. [Build the Android App Bundle with Gradle](https://developer.android.com/studio/build/building-cmdline#build_bundle).
+ In the generated app bundle, the root-level directory now includes the
+ following:
+
+ - `ai-pack-name/manifest/AndroidManifest.xml`: Configures the AI pack's identifier and delivery mode
+ - `ai-pack-name/assets/your-model-directories`: Directory that contains all assets delivered as part of the AI pack
+
+ Gradle generates the manifest for each AI pack and outputs the `assets/`
+ directory for you.
+
+### Configure install-time delivery
+
+AI packs configured as install-time are immediately available at app launch. Use
+the Java AssetManager API to access AI packs served in this mode:
+
+```java
+import android.content.res.AssetManager;
+...
+Context context = createPackageContext("com.example.app", 0);
+AssetManager assetManager = context.getAssets();
+InputStream is = assetManager.open("model-name");
+```
+
+### Configure fast-follow and on-demand delivery
+
+To download AI packs with fast-follow or on-demand delivery, use the Play AI
+Delivery Library.
+
+#### Declare dependency on the Play AI Delivery Library
+
+In your app's `build.gradle` file, declare a dependency on the Play AI Delivery
+Library:
+
+ dependencies {
+ ...
+ implementation "com.google.android.play:ai-delivery:0.1.1-alpha01"
+ }
+
+#### Check status
+
+Each AI pack is stored in a separate folder in the app's internal storage. Use
+the
+[`getPackLocation()`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackManager#getPackLocation(java.lang.String))
+method to determine the root folder of an AI pack. This method returns the
+following values:
+
+| Return value | Status |
+|---|---|
+| A valid [`AiPackLocation`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackLocation) object | AI pack root folder is ready for immediate access at [`assetsPath()`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackLocation#assetsPath) |
+| `null` | Unknown AI pack or AI packs are not available |
+
+> [!NOTE]
+> **Note:** Don't rely on cached AI pack locations between app launches. The app should always check for the existence of AI packs at every launch. AI packs may become invalid due to app updates or if the user clears the app data.
+
+#### Get download information about AI packs
+
+Use the
+
+[`getPackStates()`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackManager#getPackStates(java.util.List%3Cjava.lang.String%3E))
+method to determine the size of the download and whether the pack is already
+downloading.
+
+```java
+Task getPackStates(List packNames)
+```
+
+`getPackStates()` is an asynchronous method that returns a `Task`.
+The
+[`packStates()`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackStates#packStates())
+method of an `AiPackStates` object returns a `Map`. This
+map contains the state of each requested AI pack, keyed by its name:
+
+```java
+Map AiPackStates#packStates()
+```
+
+The final request is shown by the following:
+
+```java
+final String aiPackName = "myAiPackName";
+
+aiPackManager
+ .getPackStates(Collections.singletonList(aiPackName))
+ .addOnCompleteListener(new OnCompleteListener() {
+ @Override
+ public void onComplete(Task task) {
+ AiPackStates aiPackStates;
+ try {
+ aiPackStates = task.getResult();
+ AiPackState aiPackState =
+ aiPackStates.packStates().get(aiPackName);
+ } catch (RuntimeExecutionException e) {
+ Log.d("MainActivity", e.getMessage());
+ return;
+ });
+```
+
+The following
+[`AiPackState`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackState)
+methods provide the size of the AI pack, the downloaded amount so far (if
+requested), and the amount already transferred to the app:
+
+- [`totalBytesToDownload()`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackState#totalBytesToDownload())
+- [`bytesDownloaded()`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackState#bytesDownloaded())
+- [`transferProgressPercentage()`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackState#transferProgressPercentage())
+
+To get the status of an AI pack, use the
+[`status()`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackState#status())
+method, which returns the status as an integer that corresponds to a constant
+field in the
+[`AiPackStatus`](https://developer.android.com/reference/com/google/android/play/core/aipacks/model/AiPackStatus)
+class. An AI pack that's not installed yet has the status
+`AiPackStatus.NOT_INSTALLED`.
+
+If a request fails, use the
+[`errorCode()`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackState#errorCode())
+method, whose return value corresponds to a constant field in the
+[`AiPackErrorCode`](https://developer.android.com/reference/com/google/android/play/core/aipacks/model/AiPackErrorCode)
+class.
+
+#### Install
+
+Use the
+[`fetch()`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackManager#fetch(java.util.List%3Cjava.lang.String%3E))
+method to download an AI pack for the first time or call for the update of an AI
+pack to complete:
+
+```java
+Task fetch(List packNames)
+```
+
+This method returns an
+[`AiPackStates`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackStates)
+object containing a list of packs and their initial download states and sizes.
+If an AI pack requested via `fetch()` is already downloading, the download
+status is returned and no additional download is started.
+
+> [!NOTE]
+> **Note:** In most cases, you implement a `listener` to track the download and installation process as covered in the next section.
+
+#### Monitor download states
+
+You should implement an
+[`AiPackStateUpdateListener`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackStateUpdateListener)
+to track the installation progress of AI packs. The status updates are broken
+down per pack to support tracking the status of individual AI packs. You can
+start using available AI packs before all other downloads for your request have
+completed.
+
+```java
+void registerListener(AiPackStateUpdateListener listener)
+void unregisterListener(AiPackStateUpdateListener listener)
+```
+
+> [!NOTE]
+> **Note:** The Play Store automatically triggers the download of any `fast-follow` packs after the user installs or updates the app. However, these packs may not be ready to use immediately. You must check the status of the `fast-follow` packs at every app launch. If the download is in progress, monitor it with a listener. If the download is cancelled or paused, you can resume it by using the `fetch()` method, as covered in the [Install](https://developer.android.com/google/play/on-device-ai#install) section.
+
+##### Large downloads
+
+If the download is larger than 200 MB and the user is not on Wi-Fi, the download
+does not start until the user explicitly gives their consent to proceed with the
+download using a mobile data connection. Similarly, if the download is large and
+the user loses Wi-Fi, the download is paused and explicit consent is required to
+proceed using a mobile data connection. A paused pack has state
+`WAITING_FOR_WIFI`. To trigger the UI flow to prompt the user for consent, use
+the
+[`showConfirmationDialog()`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackManager#showConfirmationDialog(androidx.activity.result.ActivityResultLauncher%3Candroidx.activity.result.IntentSenderRequest%3E))
+method.
+
+Note that if the app does not call this method, the download is paused and will
+resume automatically only when the user is back on a Wi-Fi connection.
+
+##### Required user confirmation
+
+If a pack has the `REQUIRES_USER_CONFIRMATION` status, the download won't
+proceed until the user accepts the dialog that is shown with
+[`showConfirmationDialog()`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackManager#showConfirmationDialog(androidx.activity.result.ActivityResultLauncher%3Candroidx.activity.result.IntentSenderRequest%3E)).
+This status can occur when the app is not recognized by Play---for example, if the
+app was sideloaded. Note that calling
+[`showConfirmationDialog()`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackManager#showConfirmationDialog(androidx.activity.result.ActivityResultLauncher%3Candroidx.activity.result.IntentSenderRequest%3E))
+in this case will cause the app to be updated. After the update, you will need
+to request the AI packs again.
+
+The following is an example implementation of a listener:
+
+```java
+AiPackStateUpdateListener aiPackStateUpdateListener = new AiPackStateUpdateListener() {
+ private final ActivityResultLauncher activityResultLauncher =
+ registerForActivityResult(
+ new ActivityResultContracts.StartIntentSenderForResult(),
+ new ActivityResultCallback() {
+ @Override
+ public void onActivityResult(ActivityResult result) {
+ if (result.getResultCode() == RESULT_OK) {
+ Log.d(TAG, "Confirmation dialog has been accepted.");
+ } else if (result.getResultCode() == RESULT_CANCELED) {
+ Log.d(TAG, "Confirmation dialog has been denied by the user.");
+ }
+ }
+ });
+
+ @Override
+ public void onStateUpdate(AiPackState aiPackState) {
+ switch (aiPackState.status()) {
+ case AiPackStatus.PENDING:
+ Log.i(TAG, "Pending");
+ break;
+
+ case AiPackStatus.DOWNLOADING:
+ long downloaded = aiPackState.bytesDownloaded();
+ long totalSize = aiPackState.totalBytesToDownload();
+ double percent = 100.0 * downloaded / totalSize;
+
+ Log.i(TAG, "PercentDone=" + String.format("%.2f", percent));
+ break;
+
+ case AiPackStatus.TRANSFERRING:
+ // 100% downloaded and assets are being transferred.
+ // Notify user to wait until transfer is complete.
+ break;
+
+ case AiPackStatus.COMPLETED:
+ // AI pack is ready to use. Run the model.
+ break;
+
+ case AiPackStatus.FAILED:
+ // Request failed. Notify user.
+ Log.e(TAG, aiPackState.errorCode());
+ break;
+
+ case AiPackStatus.CANCELED:
+ // Request canceled. Notify user.
+ break;
+
+ case AiPackStatus.WAITING_FOR_WIFI:
+ case AiPackStatus.REQUIRES_USER_CONFIRMATION:
+ if (!confirmationDialogShown) {
+ aiPackManager.showConfirmationDialog(activityResultLauncher);
+ confirmationDialogShown = true;
+ }
+ break;
+
+ case AiPackStatus.NOT_INSTALLED:
+ // AI pack is not downloaded yet.
+ break;
+ case AiPackStatus.UNKNOWN:
+ Log.wtf(TAG, "AI pack status unknown")
+ break;
+ }
+ }
+}
+```
+
+Alternatively, you can use the
+[`getPackStates()`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackManager#getPackStates())
+method to get the status of current downloads.
+[`AiPackStates`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackStates)
+contains the download progress, download status, and any failure error codes.
+
+#### Access AI packs
+
+You can access an AI pack using file system calls after the download request
+reaches the
+[`COMPLETED`](https://developer.android.com/reference/com/google/android/play/core/aipacks/model/AiPackStatus#completed())
+state. Use the
+[`getPackLocation()`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackManager#getPackLocation(java.lang.String))
+method to get the root folder of the AI pack.
+
+AI packs are stored in the `assets` directory within the AI pack root directory.
+You can get the path to the `assets` directory by using the convenience method
+[`assetsPath()`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackLocation#assetsPath()).
+Use the following method to get the path to a specific asset:
+
+```java
+private String getAbsoluteAiAssetPath(String aiPack, String relativeAiAssetPath) {
+ AiPackLocation aiPackPath = aiPackManager.getPackLocation(aiPack);
+
+ if (aiPackPath == null) {
+ // AI pack is not ready
+ return null;
+ }
+
+ String aiAssetsFolderPath = aiPackPath.assetsPath();
+ // equivalent to: FilenameUtils.concat(aiPackPath.path(), "assets");
+ String aiAssetPath = FilenameUtils.concat(aiAssetsFolderPath, relativeAiAssetPath);
+ return aiAssetPath;
+}
+```
+
+### Configure device targeting
+
+You can follow the
+[device targeting instructions](https://developer.android.com/google/play/on-device-ai#device-targeting-configuration) to specify
+devices or groups of devices that should receive your AI packs.
+
+### Other Play AI Delivery API methods
+
+The following are some additional API methods you may want to use in your app.
+
+#### Cancel request
+
+Use
+[`cancel()`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackManager#cancel(java.util.List%3Cjava.lang.String%3E))
+to cancel an active AI pack request. Note that this request is a best-effort
+operation.
+
+#### Remove an AI pack
+
+Use
+[`removePack()`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackManager#removePack(java.lang.String))
+to schedule the removal of an AI pack.
+
+#### Get locations of multiple AI packs
+
+Use
+[`getPackLocations()`](https://developer.android.com/reference/com/google/android/play/core/aipacks/AiPackManager#getPackLocations())
+to query the status of multiple AI packs in bulk, which returns a map of AI
+packs and their locations. The map returned by `getPackLocations()` contains an
+entry for each pack that is currently downloaded and up-to-date.
+
+## Device targeting
+
+Device targeting gives you finer control over which parts of your app bundle are
+delivered to specific devices. For example, you could ensure that a large model
+is only delivered to devices with high RAM or you could deliver different
+versions of a model to different devices.
+
+You can target device properties such as:
+
+- System on chip
+- Device model
+- Device RAM
+- [System features](https://developer.android.com/guide/topics/manifest/uses-feature-element#features-reference)
+
+### Overview of required steps
+
+The following steps are required to enable device targeting:
+
+1. Define your device groups in an XML file.
+2. Specify which parts of your bundle should go to which device groups.
+3. \[Optional\] Test your configuration locally.
+4. Upload your bundle (containing the XML file) to Google Play.
+
+### Check Android Gradle Plugin version
+
+To use device targeting, ensure that your Android Gradle Plugin (AGP) version is
+at least 8.10.0. This is packaged with Android Studio (Meerkat 2 and later).
+Download the latest [stable version of Android Studio](https://developer.android.com/studio).
+
+### Enable this feature in Android Gradle Plugin
+
+Device targeting must be enabled explicitly in your `gradle.properties` file:
+
+ android.experimental.enableDeviceTargetingConfigApi=true
+
+### Create a device targeting configuration XML file
+
+The device targeting configuration file is an XML file in which you define your
+custom device groups. For example, you could define a device group called
+`qti_v79` that contains all devices with the Qualcomm SM8750 system on chip:
+
+
+
+
+
+
+
+
+
+
+
+A **device group** is composed of up to 5 **device selectors**. A device is
+included in a device group if it satisfies any of its device selectors.
+
+A **device selector** can have one or more **device properties**. A device is
+selected if it matches all of the selector's device properties.
+
+If a device matches multiple groups, it will be served the content for the group
+that is defined first in the XML file. The order you define groups in the XML
+file is your priority order.
+
+If a device matches no groups, it will receive the default "other" group. This
+group is automatically generated and shouldn't be defined explicitly.
+
+#### Available device properties
+
+- **device_ram** : Device RAM requirements
+ - **min_bytes** (*inclusive)*: Minimum required RAM (in bytes)
+ - **max_bytes** (*exclusive)*: Maximum required RAM (in bytes)
+- **included_device_ids** : Device models to be included in this selector **(max of 10000 device_ids per group)** . This property is satisfied if the device matches any device_id in the list.
+ - **build_brand**: Device manufacturer
+ - **build_device**: Device model code
+- **excluded_device_ids** : Device models to be excluded in this selector **(max of 10000 device_ids per group)** . This property is satisfied if the device matches no device_id in the list.
+ - **build_brand**: Device manufacturer
+ - **build_device**: Device model code
+- **required_system_features** : Features that a device needs to have to be
+ included by this selector **(max of 100 features per group)**. A device
+ needs to have all system features in this list to satisfy this property.
+
+ System
+ [feature reference](https://developer.android.com/guide/topics/manifest/uses-feature-element#features-reference)
+ - **name**: A system feature
+- **forbidden_system_features** : Features that a device mustn't have to be
+ included by this selector **(max of 100 features per group)**. If a device
+ has any of the system features in this list it doesn't satisfy this
+ property.
+
+ System
+ [feature reference](https://developer.android.com/guide/topics/manifest/uses-feature-element#features-reference)
+ - **name**: A system feature
+- **system-on-chip**: System on chips to be included in this selector. A
+ device needs to have any chip in this list to satisfy this property.
+
+ - **manufacturer** : [System on chip manufacturer](https://developer.android.com/reference/android/os/Build#SOC_MANUFACTURER)
+ - **model** : [System on chip model](https://developer.android.com/reference/android/os/Build#SOC_MODEL)
+
+> [!TIP]
+> **Tip:** Including multiple properties in a single
+> selector creates a logical AND, for example:
+>
+> ```xml
+>
+>
+>
+>
+> ```
+>
+> will create the condition for all devices with \> 7GB of RAM AND it
+> is a Pixel 4 - also written as follows:
+>
+> 
+>
+> If you want an OR condition, create separate
+> selectors in a single device group, for example:
+>
+> ```xml
+>
+>
+>
+>
+>
+> ```
+>
+> will create the condition for all devices with \> 7GB of RAM OR
+> it is a Pixel 4 - also written as follows:
+>
+> 
+
+Here is an example showing all possible device properties:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#### Official device manufacturer and device model codes
+
+You can find the correct formatting for the device manufacturer and model code
+by using the Device Catalog on the Google Play Console, by either:
+
+- Inspecting individual devices using the Device Catalog, and finding the
+ manufacturer and model code in the locations as shown in the example below
+ (For a Google Pixel 4a, the manufacturer is "Google" and the model code is
+ "sunfish")'
+
+ 
+
+ 
+- Downloading a CSV of supported devices, and using the *Manufacturer* and
+ *Model Code* for the *build_brand* and *build_device* fields,
+ respectively.
+
+#### Include your device targeting configuration file in your app bundle
+
+Add the following to your main module's `build.gradle` file:
+
+ android {
+ ...
+ bundle {
+ deviceTargetingConfig = file('device_targeting_config.xml')
+ deviceGroup {
+ enableSplit = true // split bundle by #group
+ defaultGroup = "other" // group used for standalone APKs
+ }
+ }
+ ...
+ }
+
+`device_targeting_config.xml` is the path of your configuration file relative to
+the main module. This ensures that your configuration file is packaged with your
+app bundle.
+
+The `deviceGroup` clause ensures that the APKs generated from your bundle are
+split by device groups.
+
+### Use device targeting for your AI packs
+
+You can keep size optimized on devices by only delivering your large models to
+devices that can run them.
+
+Subdivide your AI packs by device groups by taking the **existing** AI pack
+directories created in the last step, and post-fixing the appropriate folders
+(as described below) with #group_myCustomGroup1, #group_myCustomGroup2, etc.
+When using the AI packs in your app, you won't need to address folders by
+postfix (in other words, the postfix is automatically stripped during the build
+process).
+
+After the previous step, this might look like:
+
+ ...
+ .../ai-pack-name/src/main/assets/image-classifier#group_myCustomGroup1/
+ .../ai-pack-name/src/main/assets/image-classifier#group_myCustomGroup2/
+ ...
+
+In this example, you would reference `ai-pack-name/assets/image-classifier/`
+without any postfixes.
+
+Devices in `myCustomGroup1` will receive all the assets under
+`image-classifier#group_myCustomGroup1/`, while devices in `myCustomGroup2` will
+receive all the assets under `image-classifier#group_myCustomGroup2/`.
+
+Devices that don't belong to either `myCustomGroup1` or `myCustomGroup2` will
+receive an empty `ai-pack-name` pack.
+
+This is because devices that don't match any device group will receive the
+default variant of your AI pack. This includes anything that is not inside a
+directory with a `#group_suffix`.
+
+Once you have downloaded the AI pack, you can check whether your model is
+present by using the [AssetManager](https://developer.android.com/google/play/on-device-ai#install-time-delivery) for install-time
+packs or the [AiPackManager](https://developer.android.com/google/play/on-device-ai#access-AI-packs) for fast-follow and on-demand
+packs. Examples for doing this are shown for all delivery modes in the
+[sample app](https://developer.android.com/google/play/on-device-ai#example-app).
+
+> [!IMPORTANT]
+> **Important:** It's not possible to prevent **any** variant of your AI pack being delivered to certain devices. Non-targeted devices will always receive the default variant.
+
+### Use device targeting for your feature modules
+
+You can also use device targeting for feature modules. Instead of subdividing
+feature modules by device group, you specify whether the entire module should be
+delivered based on device group membership.
+
+To deliver a feature module to devices that belong to either `myCustomGroup1` or
+`myCustomGroup2`, modify its `AndroidManifest.xml`:
+
+
+ ...
+
+
+
+
+
+
+
+
+ ...
+
+
+
+
+ ...
+
+
+> [!NOTE]
+> **Note:** Devices that are not targeted won't receive the feature module at all. This is different from [targeting for AI packs](https://developer.android.com/google/play/on-device-ai#targeting-for-ai-packs), where devices that are not targeted receive a default, empty variant of the AI pack.
+
+## Test locally
+
+Before creating a release for your new bundle, you can test locally with either
+Internal App Sharing or Bundletool.
+
+### Internal App Sharing
+
+Internal App Sharing lets you use an app bundle to quickly generate a URL that
+you can tap on a local device to install exactly what Google Play would install
+for that device if that version of the app was live in a test or prod track.
+
+Take a look at the
+[internal app sharing instructions](https://support.google.com/googleplay/android-developer/answer/9844679).
+
+### Bundletool
+
+Alternatively, you can generate APKs using
+[`bundletool`](https://developer.android.com/studio/command-line/bundletool) (1.18.0 or above) and sideload
+them onto your device. Follow these steps to test your app locally using
+bundletool:
+
+1. Build your app bundle with Android Studio or bundletool.
+
+2. Generate APKs with the `--local-testing` flag:
+
+ ```
+ java -jar bundletool-all.jar build-apks --bundle=path/to/your/bundle.aab \
+ --output=output.apks --local-testing
+ ```
+3. Connect a device and run `bundletool` to sideload the APKs:
+
+ ```
+ # Example without Device Targeting Configuration
+ java -jar bundletool.jar install-apks --apks=output.apks
+ ```
+
+ ```
+ # Example with Device Targeting Configuration (you must specify which groups the connected device belongs to)
+ java -jar bundletool.jar install-apks --apks=output.apks --device-groups=myCustomGroup1,myCustomGroup2
+ ```
+
+#### Limitations of local testing with bundletool
+
+The following are limitations of local testing with bundletool:
+
+- `fast-follow` packs behave as `on-demand` packs. That is, they won't be automatically fetched when the app is sideloaded. Developers need to request them manually when the app starts; this does not require any code changes in your app.
+- Packs fetch from external storage instead of Play, so you cannot test how your code behaves in the case of network errors.
+- Local testing does not cover the wait-for-Wi-Fi scenario.
+- Updates are not supported. Before installing a new version of your build, manually uninstall the previous version.
+
+### Verify that the correct APKs are being installed
+
+Use the following method to ensure only the correct APKs are installed on the
+device
+
+ adb shell pm path {packageName}
+
+You should see something like:
+
+ package:{...}/base.apk
+ package:{...}/split_config.en.apk
+ package:{...}/split_config.xxhdpi.apk
+ package:{...}/split_main_ai-pack-name.apk
+ package:{...}/split_main_ai-pack-name.config.group_myCustomGroup1.apk
+
+Note that you will only see APKs in this list, which are made from feature
+modules and install-time AI packs. On-demand and fast-follow AI packs are not
+installed as APKs.
+
+## Test and release on Google Play
+
+We recommend that you test your app end to end on Google Play with an
+[internal test track](https://support.google.com/googleplay/android-developer/answer/9845334).
+
+Once you've done this, you can incrementally release your app update to
+production with
+[staged roll-outs](https://support.google.com/googleplay/android-developer/answer/6346149).
+
+## Sample app using Play for On-device AI
+
+Download the
+[sample app](https://drive.google.com/drive/folders/1zb2O2mDvEh4vOmv1QbjeLU-O25KHrfKJ).
+
+It demonstrates how to use each of the delivery modes as well as the device
+targeting configuration. See the [local testing](https://developer.android.com/google/play/on-device-ai#local-testing) section to get
+started.
+
+## Related content
+
+Learn more about [Android App Bundles](https://developer.android.com/guide/app-bundle) and read the
+references for the
+[AI Delivery SDK](https://developer.android.com/reference/com/google/android/play/core/packages-ai_delivery).
\ No newline at end of file
diff --git a/device-ai/litert/litert-runtime/references/verify.md b/device-ai/litert/litert-runtime/references/verify.md
new file mode 100644
index 0000000..b9272f3
--- /dev/null
+++ b/device-ai/litert/litert-runtime/references/verify.md
@@ -0,0 +1,39 @@
+## Reference first
+
+Keep the output of one fixed input from the source model in its own framework,
+or from the same `.tflite` file on the workstation using the Python package
+`ai-edge-litert` (using the same `CompiledModel` API:
+`CompiledModel.from_file(path)`, then write, run, and read). The `qwen3_tts`
+recipe in `litert-samples` ships such source-model dumps as `dump_*_ref.py`.
+This output serves as the ground truth for every run on a device that runs
+Android.
+
+## Device CPU, then the GPU
+
+Run the fixed input with `Accelerator.CPU` first and compare with the reference;
+a difference here is in the model file or preprocessing, not in the device.
+Next, run `Accelerator.GPU` and compare in two ways:
+
+1. **Numbers**: Compute maximum absolute difference and Pearson correlation against the reference dump.
+2. **Task result**: Check task-level agreement (top-1 argmax, segmentation mask, or output text). Because the GPU computes in FP16, the least significant digits move slightly, whereas the task result shouldn't change.
+
+## Diagnostic indicators
+
+- **First inference slow**: GPU shader compilation runs during the first execution. The helper constructor's warm-up call absorbs this cost; never include the first run in timing metrics.
+- **Timing GPU execution** : On the GPU, measure `model.run` and `readFloat` together, because the output buffer readback is where the app waits for the GPU to finish.
+
+## Device-only failures
+
+- **`CompiledModel.create` with `Accelerator.GPU` throws `Failed to compile
+ model`** : Caused by an operator the GPU doesn't support (Logcat logs `Following operations are not supported by GPU delegate`). Run the model on `Accelerator.CPU`, or re-export the model without that operator.
+- **`NaN` or corrupted values appear only on `Accelerator.GPU`** : Caused by an FP16 range overflow in the model. Run the model on `Accelerator.CPU`, or re-export it with an FP16-safe graph rewrite.
+- **Process terminates while loading an unquantized FP32 model exceeding 100
+ MB**: Insufficient device RAM. Quantize or split the model before loading.
+
+## Record
+
+Record the device model, LiteRT version, accelerator, maximum absolute
+difference, and task result (one row per device in the app's `README`). When the
+`Accelerator.CPU` run differs from the reference output, inspect the model
+itself (such as the converted or quantized model accuracy, GPU operator
+residency, or device-only failures such as FP16 range overflows).
\ No newline at end of file
diff --git a/device-ai/ml-kit-genai-prompt-api/SKILL.md b/device-ai/ml-kit-genai-prompt-api/SKILL.md
new file mode 100644
index 0000000..9ac0ca4
--- /dev/null
+++ b/device-ai/ml-kit-genai-prompt-api/SKILL.md
@@ -0,0 +1,121 @@
+---
+name: ml-kit-genai-prompt-api
+description: Analyzes Android codebases to implement ML Kit GenAI Prompt API. Use
+ this skill to send natural language requests on-device to Gemini Nano, use structured
+ output with Prompt API, implement prefix caching, optimize the current prompt, or
+ apply best practices."
+license: Complete terms in LICENSE.txt
+metadata:
+ author: Google LLC
+ last-updated: '2026-09-03'
+ keywords:
+ - ML Kit
+ - Prompt API
+ - Structured Output
+ - Prefix Caching
+ - Gemini Nano
+---
+
+This skill provides step-by-step guidance for integrating and optimizing the ML
+Kit GenAI Prompt API in Android apps.
+
+## Prerequisites
+
+- Android API level must be 26 or higher. If `minSdk` is below 26, update it to 26.
+- Add the ML Kit GenAI Prompt API dependency (`com.google.mlkit:genai-prompt`) to the app-level `build.gradle` file, with version at least `1.0.0-beta4`.
+- If `com.google.mlkit:genai-schema-compiler` dependency is used and KSP plugin version is below 2.3.6, update it to 2.3.6.
+
+## Detailed steps
+
+### 1. Prompt optimization
+
+To optimize prompts for use with the ML Kit Prompt API, follow the
+[prompt optimization guide](https://developer.android.com/agents/skills/device-ai/prompt-api/references/prompt-optimization).
+
+### 2. Prefix caching optimization
+
+If the prompt is more than 200 words, implement the [prefix caching API](https://developer.android.com/agents/skills/device-ai/prompt-api/references/prefix-caching).
+
+### 3. Lifecycle and best practices
+
+- The model must be fully downloaded and available before calling the first inference. Follow the guide on [implementing a generative model](https://developer.android.com/agents/skills/device-ai/prompt-api/references/get-started) to check that the `FeatureStatus` of a model is `AVAILABLE` before making an inference.
+- Release ML Kit instances by calling `close()` when an `Activity`,
+ `Fragment`, or `ViewModel` is destroyed. Example:
+
+
+ ```kotlin
+ // Instantiating model in activity, fragment, or ViewModel
+ val generativeModel = Generation.getClient()
+
+ // When activity, fragment, or ViewModel is destroyed
+ generativeModel.close()
+ ```
+
+
+
+### 4. Structured output
+
+When implementing or refactoring a prompt to use structured output, follow
+these rules:
+
+1. **Check for API availability:** Verify Structured Output feature is available on the device with `isStructuredOutputFeatureAvailable()` before using it. Refer to the [Structured Output API guide](https://developer.android.com/agents/skills/device-ai/prompt-api/references/structured-output) for full instructions.
+2. **Return type:** Return the `@Generable` typed object from the function
+ signature instead of a `String` or JSON string.
+
+ For example:
+
+ fun parseEmail(email: String): String {
+ ...
+ }
+
+ should be refactored to:
+
+ fun parseEmail(email: String): ParsedEmail? {
+ ...
+ }
+
+3. **Example:**
+
+ This is the example code before refactoring:
+
+
+ ```kotlin
+ suspend fun parseEmail(email: String): String {
+ val parseEmailPrompt = "Parse this email and return the sender, title, and short summary of the email less than 10 words: "
+
+ val parsedEmail = generativeModel.generateContent(parseEmailPrompt + email)
+
+ return parsedEmail.candidates[0].text
+ }
+ ```
+
+
+
+ This is the example code after using Structured Output API:
+
+
+ ```kotlin
+ @Generable
+ data class ParsedEmail(
+ @Guide(description = "Sender of the email")
+ var sender: String = "",
+
+ @Guide(description = "Title of the email")
+ var title: String = "",
+
+ @Guide(description = "Summary of the email less than 10 words")
+ var summary: String = ""
+ )
+
+ suspend fun parseEmail(email: String): ParsedEmail? {
+ val parseEmailPrompt =
+ "Parse this email: $email"
+
+ val baseRequest = GenerateContentRequest.Builder(TextPart(parseEmailPrompt)).build()
+ val typedRequest = generateTypedContentRequest(baseRequest, ParsedEmail::class)
+ val typedResponse = generativeModel.generateContent(typedRequest)
+ return typedResponse.candidates[0].response
+ }
+ ```
+
+
diff --git a/device-ai/ml-kit-genai-prompt-api/references/get-started.md b/device-ai/ml-kit-genai-prompt-api/references/get-started.md
new file mode 100644
index 0000000..ba6598a
--- /dev/null
+++ b/device-ai/ml-kit-genai-prompt-api/references/get-started.md
@@ -0,0 +1,272 @@
+This page describes how to do the following:
+
+- Configure your project to use Prompt API
+- Provide text-only input and receive a response
+- Provide an image input with related text input and receive a response
+
+For more details about the Prompt API, see the
+reference documentation for Kotlin ([com.google.mlkit.genai.prompt](https://developer.android.com/android/reference/kotlin/com/google/mlkit/genai/prompt/package-summary)) and
+Java ([com.google.mlkit.genai.prompt.java](https://developer.android.com/android/reference/com/google/mlkit/genai/prompt/java/package-summary),
+[com.google.mlkit.genai.prompt](https://developer.android.com/android/reference/com/google/mlkit/genai/prompt/package-summary)).
+
+## Configure project
+
+> [!NOTE]
+> **Note:** This API requires Android API level 26 or higher.
+
+Add the ML Kit Prompt API as a dependency in your `build.gradle` configuration:
+
+ implementation("com.google.mlkit:genai-prompt:1.0.0-beta2")
+
+If you need your responses in a certain format using the Structured Output API,
+you need to configure KSP and add additional dependencies. For details, see
+[Generate structured output](https://developer.android.com/agents/skills/device-ai/prompt-api/references/structured-output).
+
+## Implement generative model
+
+To implement the code in your project, follow these steps:
+
+- Create a `generativeModel` object:
+
+ ### Kotlin
+
+ // Get a GenerativeModel instance
+ val generativeModel = Generation.getClient()
+
+ ### Java
+
+ // Get a GenerativeModel instance
+ GenerativeModelFutures generativeModelFutures = GenerativeModelFutures
+ .from(Generation.INSTANCE.getClient());
+
+- Check if Gemini Nano is `AVAILABLE,` `DOWNLOADABLE`, or `UNAVAILABLE`. Then,
+ download the feature if it is downloadable:
+
+ ### Kotlin
+
+ val status = generativeModel.checkStatus()
+ when (status) {
+ FeatureStatus.UNAVAILABLE -> {
+ // Gemini Nano not supported on this device or device hasn't fetched the latest configuration to support it
+ }
+
+ FeatureStatus.DOWNLOADABLE -> {
+ // Gemini Nano can be downloaded on this device, but is not currently downloaded
+ generativeModel.download().collect { status ->
+ when (status) {
+ is DownloadStatus.DownloadStarted ->
+ Log.d(TAG, "starting download for Gemini Nano")
+
+ is DownloadStatus.DownloadProgress ->
+ Log.d(TAG, "Nano ${status.totalBytesDownloaded} bytes downloaded")
+
+ DownloadStatus.DownloadCompleted -> {
+ Log.d(TAG, "Gemini Nano download complete")
+ modelDownloaded = true
+ }
+
+ is DownloadStatus.DownloadFailed -> {
+ Log.e(TAG, "Nano download failed ${status.e.message}")
+ }
+ }
+ }
+ }
+
+ FeatureStatus.DOWNLOADING -> {
+ // Gemini Nano currently being downloaded
+ }
+
+ FeatureStatus.AVAILABLE -> {
+ // Gemini Nano currently downloaded and available to use on this device
+ }
+ }
+
+ ### Java
+
+ ListenableFuture status = generativeModelFutures.checkStatus();
+ Futures.addCallback(generativeModelFutures.checkStatus(), new FutureCallback<>() {
+ @Override
+ public void onSuccess(Integer featureStatus) {
+ switch (featureStatus) {
+ case FeatureStatus.AVAILABLE -> {
+ // Gemini Nano currently downloaded and available to use on this device
+ }
+ case FeatureStatus.UNAVAILABLE -> {
+ // Gemini Nano not supported on this device or device hasn't fetched the latest configuration to support it
+ }
+ case FeatureStatus.DOWNLOADING -> {
+ // Gemini Nano currently being downloaded
+ }
+ case FeatureStatus.DOWNLOADABLE -> {
+ generativeModelFutures.download(new DownloadCallback() {
+ @Override
+ public void onDownloadStarted(long l) {
+ Log.d(TAG, "starting download for Gemini Nano");
+ }
+ @Override
+ public void onDownloadProgress(long l) {
+ Log.d(TAG, "Nano " + l + " bytes downloaded");
+ }
+ @Override
+ public void onDownloadCompleted() {
+ Log.d(TAG, "Gemini Nano download complete");
+ }
+ @Override
+ public void onDownloadFailed(@NonNull GenAiException e) {
+ Log.e(TAG, "Nano download failed: " + e.getMessage());
+ }
+ });
+ }
+ }
+ }
+ @Override
+ public void onFailure(@NonNull Throwable t) {
+ // Failed to check status
+ }
+ }, ContextCompat.getMainExecutor(context));
+
+## Provide text-only input
+
+### Kotlin
+
+ val response = generativeModel.generateContent("Write a 3 sentence story about a magical dog.")
+
+### Java
+
+ GenerateContentResponse response = generativeModelFutures.generateContent(
+ new GenerateContentRequest.Builder(
+ new TextPart("Write a 3 sentence story about a magical dog."))
+ .build())
+ .get();
+
+Alternatively, add optional parameters:
+
+### Kotlin
+
+ val response = generativeModel.generateContent(
+ generateContentRequest(
+ TextPart("Write a 3 sentence story about a magical dog."),
+ ) {
+ // Optional parameters
+ temperature = 0.2f
+ topK = 10
+ candidateCount = 3
+ },
+ )
+
+### Java
+
+ GenerateContentRequest.Builder requestBuilder =
+ new GenerateContentRequest.Builder(
+ new TextPart("Write a 3 sentence story about a magical dog."));
+ requestBuilder.setTemperature(.2f);
+ requestBuilder.setTopK(10);
+ requestBuilder.setCandidateCount(3);
+
+ GenerateContentResponse response =
+ generativeModelFutures.generateContent(requestBuilder.build()).get();
+
+For more information about the optional parameters, see [Optional
+configurations](https://developer.android.com/agents/skills/device-ai/ml-kit-genai-prompt-api/references/get-started#optional-configurations).
+
+## Provide multimodal (image and text) input
+
+Bundle an image and a text input together in the `generateContentRequest()`
+function, with the text prompt being a question or command related to the
+image. You can bundle multiple images and text together in the same request.
+
+### Kotlin
+
+ val response = generativeModel.generateContent(
+ generateContentRequest(ImagePart(bitmap), TextPart(textPrompt)) {
+ // optional parameters
+ ...
+ },
+ )
+
+### Java
+
+ GenerateContentResponse response = generativeModelFutures.generateContent(
+ new GenerateContentRequest.Builder(
+ new ImagePart(bitmap),
+ new TextPart("textPrompt"))
+ // optional parameters
+ .build())
+ .get();
+
+## Process inference result
+
+- Run the inference and retrieve the result. You can choose to either wait for
+ the full result or stream the response as it's generated for both text-only
+ and multimodal prompts.
+
+ - This uses non-streaming inference, which retrieves the entire result from
+ the AI model before returning the result:
+
+ ### Kotlin
+
+ // Call the AI model to generate content and store the complete
+ // in a new variable named 'response' once it's finished
+ val response = generativeModel.generateContent("Write a 3 sentence story about a magical dog")
+
+ ### Java
+
+ GenerateContentResponse response = generativeModelFutures.generateContent(
+ new GenerateContentRequest.Builder(
+ new TextPart("Write a 3 sentence story about a magical dog."))
+ .build())
+ .get();
+
+ - The following snippets are examples of using streaming inference, which
+ retrieves the result in chunks as it's being generated:
+
+ ### Kotlin
+
+ // Streaming inference
+ var fullResponse = ""
+ generativeModel.generateContentStream("Write a 3 sentence story about a magical dog").collect { chunk ->
+ val newChunkReceived = chunk.candidates[0].text
+ print(newChunkReceived)
+ fullResponse += newChunkReceived
+ }
+
+ ### Java
+
+ // Streaming inference
+ StringBuilder fullResponse = new StringBuilder();
+ generativeModelFutures.generateContent(new GenerateContentRequest.Builder(
+ (new TextPart("Write a 3 sentence story about a magical dog"))).build(),
+ chunk -> {
+ Log.d(TAG, chunk);
+ fullResponse.append(chunk);
+ });
+
+For more information about streaming and non-streaming inference, see [Streaming
+versus non-streaming](https://developer.android.com/ml-kit/genai#streaming-vs-non).
+
+## Latency optimization
+
+To optimize for the first inference call, your application may optionally call
+`warmup()`. This loads Gemini Nano into memory and initializes runtime
+components.
+
+## Optional configurations
+
+As part of each `GenerateContentRequest`, you can set the following optional
+parameters:
+
+- `temperature` : Controls the degree of randomness in token selection.
+- `seed` : Enables generating stable and deterministic results.
+- `topK` : Controls randomness and diversity in results.
+- `candidateCount` : Requests the number of unique responses returned. Note that the exact number of responses may not be the same as `candidateCount` because duplicate responses are automatically removed.
+- `maxOutputTokens` : Defines the maximum number of tokens that can be generated in the response.
+
+For more guidance on setting optional configurations, see
+[`GenerateContentRequest`](https://developer.android.com/android/reference/kotlin/com/google/mlkit/genai/prompt/GenerateContentRequest).
+
+## Supported features and limitations
+
+- Input must be under 4000 tokens (or approximately 3000 English words). For more information, see the [`countTokens`](https://developer.android.com/android/reference/com/google/mlkit/genai/prompt/GenerativeModel#countTokens(com.google.mlkit.genai.prompt.GenerateContentRequest)) reference.
+- Use cases that require long output (more than 4K tokens) should be avoided.
+- AICore enforces an inference quota per app. For more information, see [Quota
+ per application](https://developer.android.com/ml-kit/genai#quota-per).
\ No newline at end of file
diff --git a/device-ai/ml-kit-genai-prompt-api/references/prefix-caching.md b/device-ai/ml-kit-genai-prompt-api/references/prefix-caching.md
new file mode 100644
index 0000000..13eb571
--- /dev/null
+++ b/device-ai/ml-kit-genai-prompt-api/references/prefix-caching.md
@@ -0,0 +1,146 @@
+> [!NOTE]
+> **Note:** Prefix caching is experimental and may change in the future. This feature is only available on a subset of the supported devices for Prompt API, with support for more devices coming soon. We encourage you to experiment with this API on a Pixel device to understand how it can improve inference latency speeds for your specific use case.
+
+*Prefix caching* is a feature that reduces inference time by storing and reusing
+the intermediate LLM state of processing a shared and recurring prompt prefix
+part. To enable prefix caching, you only have to separate the static prefix from
+the dynamic suffix in your API request.
+
+Prefix caching currently only supports text-only input, so you shouldn't use
+this feature if you're providing an image in your prompt.
+
+There are two approaches to implement prefix caching: implicit or explicit:
+
+- [Implicit (automatic) prefix caching](https://developer.android.com/agents/skills/device-ai/ml-kit-genai-prompt-api/references/prefix-caching#implicit) is a lightweight approach where the application only needs to define a shared portion of the prompt.
+- [Explicit (manual) prefix caching](https://developer.android.com/agents/skills/device-ai/ml-kit-genai-prompt-api/references/prefix-caching#explicit) allows applications to have more control over caches, including cache creation, querying, and deletion.
+
+## Use prefix caching implicitly
+
+To enable prefix caching, add the shared portion of the prompt to the
+`promptPrefix` field, as shown in the following code snippets:
+
+### Kotlin
+
+ val promptPrefix = "Reverse the given sentence: "
+ val dynamicSuffix = "Hello World"
+
+ val result = generativeModel.generateContent(
+ generateContentRequest(TextPart(dynamicSuffix)) {
+ promptPrefix = PromptPrefix(promptPrefix)
+ }
+ )
+
+### Java
+
+ String promptPrefix = "Reverse the given sentence: ";
+ String dynamicSuffix = "Hello World";
+
+ GenerateContentResponse response = generativeModelFutures.generateContent(
+ new GenerateContentRequest.Builder(new TextPart(dynamicSuffix))
+ .setPromptPrefix(new PromptPrefix(promptPrefix))
+ .build())
+ .get();
+
+In the preceding snippet, the `dynamicSuffix` is passed as the main content, and
+the `promptPrefix` is provided separately.
+
+### Estimated performance gains
+
+|---|---|---|
+| | **Without prefix caching** | **With prefix cache-hit** (Prefix cache-miss may occur when prefix is used for the first time) |
+| Pixel 9 with 300-token fixed prefix and a 50-token dynamic suffix prompt | 0.82 seconds | 0.45 seconds |
+| Pixel 9 with a 1,000-token fixed prefix and a 100-token dynamic suffix prompt | 2.11 seconds | 0.5 seconds |
+
+### Storage considerations
+
+With implicit prefix caching, cache files are saved on the client application's
+private storage, which increases your app's storage usage. Encrypted cache files
+and their associated metadata, including original prefix text, are stored. Keep
+the following storage considerations in mind:
+
+- The number of caches is managed by an LRU (Least Recently Used) mechanism. Least used caches are deleted automatically when exceeding the max total cache amount.
+- Prompt cache sizes are dependent on the length of the prefix.
+- To clear all caches created from prefix caching, use the
+ [`generativeMode.clearImplicitCaches()`](https://developer.android.com/android/reference/kotlin/com/google/mlkit/genai/prompt/GenerativeModel#clearCaches%28%29) method.
+
+ > [!NOTE]
+ > **Note:** The `clearImplicitCaches()` method is experimental and may change in the future.
+
+## Use explicit cache management
+
+The Prompt API includes explicit cache management methods to give developers
+more precise control over how caches are created, searched, used, and removed.
+These manual operations run independently of the system's automated cache
+handling.
+
+This example illustrates how to initialize explicit cache management and
+perform inference:
+
+### Kotlin
+
+ val cacheName = "my_cache"
+ val promptPrefix = "Reverse the given sentence: "
+ val dynamicSuffix = "Hello World"
+
+ // Create a cache
+ val cacheRequest = createCachedContextRequest(cacheName, PromptPrefix(promptPrefix))
+ val cache = generativeModel.caches.create(cacheRequest)
+
+ // Run inference with the cache
+ val response = generativeModel.generateContent(
+ generateContentRequest(TextPart(dynamicSuffix)) {
+ cachedContextName = cache.name
+ }
+ )
+
+### Java
+
+ String cacheName = "my_cache";
+ String promptPrefix = "Reverse the given sentence: ";
+ String dynamicSuffix = "Hello World";
+
+ // Create a cache
+ CachedContext cache = cachesFutures.create(
+ new CreateCachedContextRequest.Builder(cacheName, new PromptPrefix(promptPrefix))
+ .build())
+ .get();
+
+ // Run inference with the cache
+ GenerateContentResponse response = generativeModelFutures.generateContent(
+ new GenerateContentRequest.Builder(new TextPart(dynamicSuffix))
+ .setCachedContextName(cache.getName())
+ .build())
+ .get();
+
+This example demonstrates how to query, retrieve, and delete explicitly managed
+caches using `generativeModel.caches`:
+
+### Kotlin
+
+ val cacheName = "my_cache"
+
+ // Query pre-created caches
+ for (cache in generativeModel.caches.list()) {
+ // Do something with cache
+ }
+
+ // Get specific cache
+ val cache = generativeModel.caches.get(cacheName)
+
+ // Delete a pre-created cache
+ generativeModel.caches.delete(cacheName)
+
+### Java
+
+ String cacheName = "my_cache";
+
+ // Query pre-created caches
+ for (PrefixCache cache : cachesFutures.list().get()) {
+ // Do something with cache
+ }
+
+ // Get specific cache
+ PrefixCache cache = cachesFutures.get(cacheName).get();
+
+ // Delete a pre-created cache
+ cachesFutures.delete(cacheName);
\ No newline at end of file
diff --git a/device-ai/ml-kit-genai-prompt-api/references/prompt-design.md b/device-ai/ml-kit-genai-prompt-api/references/prompt-design.md
new file mode 100644
index 0000000..bba16ab
--- /dev/null
+++ b/device-ai/ml-kit-genai-prompt-api/references/prompt-design.md
@@ -0,0 +1,55 @@
+When using Prompt API, there are specific strategies you can use to tailor your
+prompts and receive optimal results. This page describes best practices for
+formatting prompts for Gemini Nano.
+
+For more general prompt engineering guidance, see [Prompt Engineering
+whitepaper](https://www.kaggle.com/whitepaper-prompt-engineering), [Prompt Engineering for Generative
+AI](https://developer.android.com/machine-learning/resources/prompt-eng), and [Prompt design strategies](https://ai.google.dev/gemini-api/docs/prompting-strategies).
+
+Alternatively, to automatically refine and improve prompts, you can use the
+[zero-shot optimizer](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/learn/prompts/zero-shot-optimizer#optimizing_for_smaller_models), which can target on-device models such as
+`gemma-3n-e4b-it`.
+
+## Prompt design best practices
+
+When designing prompts for Prompt API, use the following techniques:
+
+- **Provide examples for in-context learning**. Add well-distributed examples to
+ your prompt to show Gemini Nano the kind of result you expect.
+
+ Consider using the [prefix caching](https://developer.android.com/agents/skills/device-ai/prompt-api/references/prefix-caching) feature when you use in-context
+ learning, as providing examples makes the prompt longer and increases
+ inference time.
+- **Be concise** . Verbose preambles with repeated instructions can produce
+ suboptimal results. Keep your prompt focused and to-the-point. If you need to
+ repeat a short directive that guides the model's behavior, consider using
+ [system instructions](https://developer.android.com/agents/skills/device-ai/prompt-api/references/system-instructions).
+
+- **Structure prompts** to generate more effective responses, such as this
+ [sample prompt template](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/prompts/prompt-design-strategies#sample-prompt-template) that clearly defines instructions,
+ constraints, and examples.
+
+- **Keep output short** . LLM inference speeds are heavily dependent on the
+ output length. Carefully consider how you can generate the shortest possible
+ output for your use case and do manual post-processing to structure the output
+ in the chosen format. To help ensure that the response output is in your
+ preferred format, use the [Structured Output API](https://developer.android.com/agents/skills/device-ai/prompt-api/references/structured-output).
+
+- **Add delimiters** . Use delimiters like ``,
+ ``, and `##` to create separation between different parts of your
+ prompt. Using `##` between components is particularly critical for Gemini
+ Nano, as it significantly reduces the chances of the model failing to
+ correctly interpret each component.
+
+- **Prefer simple logic and a more focused task** . If you find it challenging
+ to achieve good results with a prompt requiring multi-step reasoning (for
+ example, *do X first, if the result of X is A, do M; otherwise do N;
+ then do Y...* ), consider breaking the task up and let each Gemini Nano call
+ handle a more focused task, while using code to chain multiple calls together.
+ If you do need to tackle a complex, reasoning-intensive task all at once,
+ consider using [thinking mode](https://developer.android.com/ml-kit/genai/prompt/android/thinking-mode).
+
+- **Use lower temperature values for deterministic tasks** . For tasks such as
+ entity extraction or translation that don't rely on creativity, consider
+ starting with a `temperature` value of `0.2`, and tune this value based on
+ your testing.
\ No newline at end of file
diff --git a/device-ai/ml-kit-genai-prompt-api/references/prompt-optimization.md b/device-ai/ml-kit-genai-prompt-api/references/prompt-optimization.md
new file mode 100644
index 0000000..05afba0
--- /dev/null
+++ b/device-ai/ml-kit-genai-prompt-api/references/prompt-optimization.md
@@ -0,0 +1,74 @@
+This skill provides guidance on how to optimize prompts for use with the ML Kit
+Prompt API.
+
+## Guidelines \& Rules
+
+The best practices for prompt design for Prompt API are at
+[Prompt Design](https://developer.android.com/agents/skills/device-ai/prompt-api/references/prompt-design). Apply these core rules:
+
+- Include examples for in-context learning with examples of the output. This is an example prompt that includes in-context learning because it has examples of the desired model output: Analyze the message and return whether the customer sentiment is positive, negative, or neutral. Example 1: Input - "This product doesn't work"; Output - "Negative" Example 2: Input - "I liked this product"; Output - "Positive"
+- Make prompts concise. Remove duplicate or repeated instructions. Do not include conversational filler such as: hello, please, do this, help me with, etc. For example, a verbose prompt such as: "Hello! I want you to act as an expert translator for me today. I am going to give you a short paragraph of text in English, and I would really like it if you could translate it into formal, high-level Business French. Please make sure that you do not include any intro like 'Here is your translation' or any closing remarks at all. Just output the translation itself." can be improved with this concise prompt: "Translate to formal Business French. Return ONLY the translation"
+- Use paired XML/HTML delimiters to denote dynamic inputs (for example `[content]`) to clearly isolate user data from prompt instructions. Mention that the delimiters represent placeholder text that should be replaced by code in the actual implementation.
+- Use the [Structured Output API](https://developer.android.com/agents/skills/device-ai/prompt-api/references/structured-output) if the output requires parsing responses into certain formats. Don't include the Structured Output API implementation in the prompt itself, but mention it after presenting the optimized prompt to the user so the user knows how to parse the model output. Prompts that use Structured Output API must return the `@Generable` typed object from the function signature instead of a string or JSON string.
+- Keep output short by adding output constraints such as word count, character count, or number of bullets or sentences. For example, add constraints such as "The summarization must be 10 words or fewer."
+- Use [system instructions](https://developer.android.com/agents/skills/device-ai/prompt-api/references/system-instructions) for short instructions that define how a model should behave, and use [prefix caching](https://developer.android.com/agents/skills/device-ai/prompt-api/references/prefix-caching) for prompts that are over 200 words.
+
+## Examples
+
+Here are examples of prompts that the user might ask you to optimize, and the
+improved, optimized versions.
+
+### Example 1
+
+Unoptimized prompt:
+
+ Process this customer email and return the order ID, what was bought, the phone
+ number, and what they want. Format it as JSON so my app can read it.
+
+Optimized prompt:
+
+ ## Task
+
+ Extract key details from the customer email below.
+
+ ## Customer Email
+
+
+ [email_content]
+
+
+Explanation: Because the information that the user wants to extract using the
+prompt is well suited for the Structured Output API, the optimized prompt
+implicitly uses the Structured Output API and doesn't need to explicitly define
+the schema for the extracted data. In your response to the user that explains
+the optimizations made to the prompt, mention that the optimized prompt is
+designed for use with the Structured Output API. Also, mention that the
+"" delimiters signify placeholder text that should be replaced with code in the user's actual implementation.
+
+### Example 2
+
+Unoptimized prompt:
+
+ Given this itinerary for a trip: [Flight to Paris (CDG), Check-in: Hotel Le
+ Meurice, Visit to the Louvre, Dinner at Le Jules Verne, Eiffel Tower Visit,
+ Versailles Palace Tour, Montmartre Walk, Seine River Cruise, Pastry and Macaron
+ Tasting, Flight Out], generate the following: overall vibe, tips on how to
+ prepare for this trip, and common short phrases to learn for the trip.
+
+Optimized prompt:
+
+ ## Task
+
+ Analyze the trip itinerary below and extract details to build a concise travel
+ guide.
+
+ ## Rules
+ - Overall vibe: Limit to 1 short sentence (under 15 words).
+ - Preparation tips: Provide exactly 3 short tips (maximum 10 words per tip).
+ - Useful phrases: Provide exactly 3 phrases (under 15 words each).
+
+ ## Itinerary
+
+
+ [itinerary_content]
+
\ No newline at end of file
diff --git a/device-ai/ml-kit-genai-prompt-api/references/structured-output.md b/device-ai/ml-kit-genai-prompt-api/references/structured-output.md
new file mode 100644
index 0000000..2740974
--- /dev/null
+++ b/device-ai/ml-kit-genai-prompt-api/references/structured-output.md
@@ -0,0 +1,219 @@
+If you need to parse the responses from the Prompt API into certain formats,
+such as JSON, for further processing, use the Structured Output API.
+
+With the Structured Output API, you define the target output structure using
+Kotlin classes and annotations. The Prompt API then returns a response in the
+form of your Kotlin object.
+
+Generating structured output is particularly useful for tasks like the
+following:
+
+- **Entity extraction**: Extracting structured fields (for example, event name, date, location) from unstructured text.
+- **Classification**: Categorizing input text into predefined categories.
+- **Data serialization**: Converting unstructured user input into a format suitable for database storage or API calls.
+
+## Prerequisites
+
+To verify that the Structured Output API is available on the device, use the
+`isStructuredOutputFeatureAvailable()` API. The API returns `true` if the
+Structured Output API is available on the device, and `false` otherwise.
+
+ suspend fun isStructuredOutputFeatureAvailable(): Boolean
+
+The Structured Output API also has the following requirements:
+
+- Android API level 26 or higher (`minSdk` 26)
+- KSP plugin version 2.3.6 or higher
+
+## Limitations
+
+The Structured Output API has the following limitations:
+
+- Works in Kotlin only.
+- ProGuard might interfere with the parsing of your annotated class. Add your annotated class to your [keep rules](https://developer.android.com/topic/performance/app-optimization/keep-rules-overview) to exclude them from ProGuard if you get errors parsing, for example:
+
+ # Keep classes used by structured output for deserialization for release builds.
+ -keep class com.google.mlkit.genai.demo.kotlin.Plant { *; }
+
+## Configure project
+
+To get started with the Structured Output API, follow these steps:
+
+1. [Add the ML Kit Prompt API as a dependency](https://developer.android.com/agents/skills/device-ai/prompt-api/references/get-started#configure-project) in your
+ app-level `build.gradle.kts` (or `build.gradle`) file, if you haven't
+ already.
+
+2. Add the KSP plugin to your project-level `build.gradle.kts` file. Use a
+ KSP plugin version that is compatible with your Kotlin version; we
+ recommend KSP version 2.3.6 or higher.
+
+ dependencies {
+ ...
+ classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:2.3.6"
+ }
+
+3. Add the structured compiler dependencies to your app-level
+ `build.gradle.kts` file:
+
+ dependencies {
+ ...
+ ksp("com.google.mlkit:genai-schema-compiler:1.0.0-alpha1")
+ }
+
+## Define the output structure
+
+Define the structure of the data you want the model to return using Kotlin
+data classes. There are two main annotations for defining the output
+structure:
+
+- Use the `@Generable` annotation to define the class as a target for structured output.
+- Use the `@Guide` annotations on the class properties to provide descriptions and constraints that guide the model's output.
+
+The following example defines a structure for extracting plant information:
+
+ import com.google.mlkit.genai.schema.annotations.Generable
+ import com.google.mlkit.genai.schema.annotations.Guide
+
+ @Generable
+ data class PlantList(
+ @Guide(description = "The list of plants found", minItems = 1, maxItems = 5)
+ val plants: List
+ )
+
+ @Generable("Information about a plant species")
+ data class Plant(
+ @Guide(description = "The common name of the plant")
+ val commonName: String,
+
+ @Guide(description = "The full latin scientific name of the plant")
+ val scientificName: String,
+
+ @Guide(
+ description = "The maximum height of the plant in centimeters.",
+ minimum = 1.0,
+ maximum = 10000.0
+ )
+ val maxHeightCm: Int,
+
+ @Guide(description = "Whether the plant is poisonous or not")
+ val isPoisonous: Boolean?,
+
+ @Guide(
+ description = "The primary continent where this plant is native to",
+ enumValues = ["Africa", "Antarctica", "Asia", "Australia", "Europe", "North America", "South America"]
+ )
+ val nativeContinent: String
+ )
+
+### Supported types and constraints
+
+The following types are supported within a `@Generable` annotated class,
+along with their respective `@Guide` constraints:
+
+| Type | Description | Supported `@Guide` constraints |
+|---|---|---|
+| `String` | For text. | `description`, `enumValues` |
+| `Double` / `Float` | For floating-point numbers. | `description`, `minimum`, `maximum` |
+| `Int` / `Long` | For whole numbers. | `description`, `minimum`, `maximum` |
+| `Boolean` | For true/false values. | `description` |
+| `List` | For lists of supported types or nested `@Generable` classes. | `description`, `minItems`, `maxItems` |
+| `List` | For lists of `String` values. | `description`, `enumValues`, `minItems`, `maxItems` > [!NOTE] > **Note:** Setting the `enumValues` parameter defines the values allowed for the individual list items. |
+| `@Generable` class | For nested structured objects. | `description` |
+
+> [!NOTE]
+> **Note:** Circular dependencies between nested `@Generable` classes are not supported (for example, a class referencing itself, or Class A referencing Class B which in turn references Class A).
+
+## Generate structured content
+
+To request structured output, use the `generateTypedContentRequest` helper
+function to wrap your standard prompt and specify the target output class.
+
+ // 1. Initialize your GenerativeModel as usual
+ val generativeModel = Generation.getClient()
+
+ // 2. Prepare the prompt text
+ val promptText = "List some common plants found in California."
+ val baseRequest = GenerateContentRequest.Builder(TextPart(promptText)).build()
+
+ // 3. Create the typed request, specifying the target class (e.g., PlantList)
+ val typedRequest = generateTypedContentRequest(
+ generateContentRequest = baseRequest,
+ outputClass = PlantList::class
+ )
+
+ // 4. Run the inference
+ try {
+ val typedResponse = generativeModel.generateContent(typedRequest)
+
+ // 5. Access the parsed object
+ // The response candidates contain the parsed object of type T (PlantList in this case)
+ val plantList: PlantList? = typedResponse.candidates.firstOrNull()?.response
+
+ if (plantList != null) {
+ // Process the structured data
+ for (plant in plantList.plants) {
+ Log.d("StructuredOutput", "Found plant: ${plant.commonName} (${plant.scientificName})")
+ }
+ } else {
+ Log.e("StructuredOutput", "Failed to parse response into the desired structure.")
+
+ // Inspect finish reason for details
+ val finishReason = typedResponse.candidates.firstOrNull()?.finishReason
+ Log.d("StructuredOutput", "Finish reason: $finishReason")
+ }
+ } catch (e: GenAiException) {
+ // Handle API errors
+ when (e.errorCode) {
+ GenAiException.STRUCTURED_OUTPUT_INVALID_CLASS -> {
+ Log.e("StructuredOutput", "The class structure is not supported.")
+ }
+ GenAiException.STRUCTURED_OUTPUT_INVALID_VALUE -> {
+ Log.e("StructuredOutput", "The model generated values that violate the schema constraints.")
+ }
+ else -> {
+ Log.e("StructuredOutput", "API error: ${e.message}")
+ }
+ }
+ }
+
+## Handle finish reasons and errors
+
+When using the Structured Output API, you should handle potential exceptions
+thrown by the API and inspect the `finishReason` property in the response
+candidates if the parsed response is null.
+
+### finishReason values
+
+The `finishReason` property can take one of the following values:
+
+- `TypedFinishReason.STOP`: The model finished generating successfully and the output matches the schema.
+- `TypedFinishReason.MAX_TOKENS`: The model stopped because it reached the token limit. The output might be incomplete.
+- `TypedFinishReason.PARSE_CLASS_ERROR`: The model completed generation, but the resulting JSON couldn't be parsed into the target Kotlin class.
+- `TypedFinishReason.STRUCTURE_NOT_ANNOTATED`: The target class or its nested classes are missing the required `@Generable` annotation.
+- `TypedFinishReason.STRUCTURE_VALUES_INVALID`: The generated values violated the constraints defined in the `@Guide` annotations (for example value out of range, list size out of bounds).
+- `TypedFinishReason.OTHER`: Generation stopped due to other reasons.
+
+### Exceptions
+
+The Structured Output API might throw `GenAiException` with the following
+error codes:
+
+- `GenAiException.STRUCTURED_OUTPUT_INVALID_CLASS` (-104): The structure of the annotated class is invalid or contains unsupported types. This is typically a development-time configuration error. Review your `@Generable` data class definition to check that all property types are supported and that there aren't any circular dependencies.
+- `GenAiException.STRUCTURED_OUTPUT_INVALID_VALUE` (-105): The values generated by the model are invalid or fail constraints verification. This is a runtime error. If you encounter this error frequently, consider the following solutions:
+ - Refining your prompt instructions to guide the model more strictly.
+ - Relaxing the constraints (like minimum, maximum, or list size limits) in your `@Guide` annotations if they are too restrictive for the model's capabilities.
+ - Implementing a fallback strategy in your app, such as retrying the request or displaying a default state.
+
+## Count tokens
+
+To check if your structured prompt is within the input token limit, calculate
+the token count using the [`countTokens()`](https://developer.android.com/android/reference/com/google/mlkit/genai/prompt/GenerativeModel#countTokens(com.google.mlkit.genai.prompt.GenerateContentRequest)) method.
+
+Because structured output requests need to instruct the model on the schema
+structure, counting tokens on just the raw prompt text (using a
+`GenerateContentRequest` instance) isn't accurate. To get an accurate token
+count, you must pass the complete `GenerateTypedContentRequest` instance, which
+includes your target class and schema configurations, to the `countTokens()`
+method:
+
+ suspend fun countTokens(request: GenerateTypedContentRequest): CountTokensResponse
\ No newline at end of file
diff --git a/device-ai/ml-kit-genai-prompt-api/references/system-instructions.md b/device-ai/ml-kit-genai-prompt-api/references/system-instructions.md
new file mode 100644
index 0000000..844131c
--- /dev/null
+++ b/device-ai/ml-kit-genai-prompt-api/references/system-instructions.md
@@ -0,0 +1,71 @@
+System instructions let you give the model a persona, set the tone of its
+responses, or provide specific rules it must follow. These instructions are
+separate from the user's prompt and are treated with higher priority by the
+model to ensure it behaves as expected.
+
+Common use cases include:
+
+- **Setting a persona:** For example, "You are a helpful math tutor."
+- **Enforcing output format:** For example, "Always respond in bullet points."
+- **Setting constraints:** For example, "Do not answer questions about politics."
+
+## Prerequisites
+
+System instructions work on devices running Gemini Nano V3 and higher. For a
+list of supported devices, see [Prompt API device support](https://developer.android.com/ml-kit/genai#prompt-device).
+
+## Limitations
+
+We don't recommend using system instructions with
+[prefix caching](https://developer.android.com/agents/skills/device-ai/prompt-api/references/prefix-caching). In general, use system instructions for
+short instructions that define how the model should behave; use prefix
+caching if you need to repeat a large part of your prompt across queries and
+need to optimize performance.
+
+## How to use system instructions
+
+To provide system instructions, create a `SystemInstruction` object and pass it
+to the `GenerateContentRequest` builder:
+
+ import com.google.mlkit.genai.prompt.SystemInstruction
+ import com.google.mlkit.genai.prompt.TextPart
+ import com.google.mlkit.genai.prompt.generateContentRequest
+
+ // 1. Define the system instruction
+ val systemInstruction =
+ SystemInstruction("You are a concise assistant. Answer in 2 sentences or less.")
+
+ // 2. Create the request
+ val request = generateContentRequest(TextPart("How does photosynthesis work?")) {
+ this.systemInstruction = systemInstruction
+ }
+
+ // 3. Run inference
+ try {
+ val response = generativeModel.generateContent(request)
+ println(response.candidates.firstOrNull()?.text)
+ } catch (e: GenAiException) {
+ // Handle SDK-specific exceptions
+ }
+
+You can further simplify the code by passing the system instructions directly
+into the `generateContentRequest` request builder:
+
+ val request = generateContentRequest(
+ SystemInstruction("You are a pirate. Speak like one."),
+ TextPart("What is the weather like today?")
+ ) {
+ // Optional configurations like temperature
+ temperature = 0.7f
+ }
+
+## Best practices
+
+Here are some best practices when using system instructions:
+
+- **Be clear and direct:** The model follows clear, direct instructions better than ambiguous ones. Here are some examples:
+ - Vague (avoid): "Don't write too much. Try to be helpful and friendly, and format the output nicely."
+ - Clear (preferred): "You are a friendly customer support assistant. Limit your responses to a maximum of 3 sentences. Format any lists using bullet points."
+- **Be concise:** While system instructions are powerful, very long instructions can consume the model's limited context window.
+- **Factor in token counts:** Make sure that your token counting logic includes the system instructions to avoid underestimating request size. We recommend keeping your system instructions to under 150 words (100-200 tokens).
+- **Test and iterate:** Model behavior can vary based on phrasing. Test with various user inputs to ensure the model maintains its persona consistently.
\ No newline at end of file
diff --git a/devtools/android-cli/SKILL.md b/devtools/android-cli/SKILL.md
new file mode 100644
index 0000000..c9ebe6d
--- /dev/null
+++ b/devtools/android-cli/SKILL.md
@@ -0,0 +1,471 @@
+---
+name: android-cli
+description: Provides instructions for installing and using the `android` CLI. The
+ `android` command-line tool is a critical tool for Android development and helps
+ you create new Android projects, run Android apps on devices, manage and interact
+ with Android virtual devices (including screenshots and UI inspection), manage Android
+ SDK components, look up official Android documentation, and discover and install
+ official Android skills.
+license: Complete terms in LICENSE.txt
+metadata:
+ author: Google LLC
+ last-updated: '2026-09-12'
+ keywords:
+ - android-cli
+ - sdk
+ - emulator
+ - skills
+ - docs
+ - knowledge base
+ - project creation
+ - screenshots
+---
+
+This skill provides instructions for using the `android` CLI tool. The tool
+includes various commands for creating projects, running applications,
+interacting with devices, and managing the CLI environment.
+
+## Installation
+
+If the android tool is not in the path, install it. To install run the following
+command:
+
+- Linux: curl -fsSL https://dl.google.com/android/cli/latest/linux_x86_64/install.sh \| bash
+- Mac Arm: curl -fsSL https://dl.google.com/android/cli/latest/darwin_arm64/install.sh \| bash
+- Mac Intel: curl -fsSL https://dl.google.com/android/cli/latest/darwin_x86_64/install.sh \| bash
+- Windows: curl -fsSL https://dl.google.com/android/cli/latest/windows_x86_64/install.cmd -o "%TEMP%\\i.cmd" \&\& "%TEMP%\\i.cmd"
+
+## SDK management
+
+To manage the installation of Android SDKs and tools, use the `sdk` command.
+For example:
+
+- `android sdk install [@]...`: Install specific packages. Multiple packages can be specified, separated by spaces. `` defaults to latest. For example: `android sdk install platforms/android-30@2
+ platforms/android-34`
+- `android sdk update []`: Update a specific package or all packages to the latest version.
+- `android sdk remove `: Remove a package from the local SDK.
+- `android sdk list --all`: List installed and available SDK packages.
+
+## Project creation
+
+Create projects from templates using the `create` command.
+
+For example: `android create empty-activity --name="My App" --output=./my-app`
+
+## Interacting with Android devices
+
+Use the `android layout` command to inspect the UI layout of an Android
+application in JSON format. Use the `android screen` command to visually inspect
+the UI and obtain bounding coordinates for visual regions.
+
+**IMPORTANT:** Before using `android layout` or `android screen`, you MUST read
+this reference: [interact.md](references/interact.md) The reference file
+[interact.md](references/interact.md) contains instructions for properly
+interacting with Android devices. Follow its instructions exactly.
+
+### Running journey tests
+
+Journey tests involve device interaction; use the `android layout` and `android
+screen` commands to evaluate Journeys. The instructions for device interaction
+apply here as well.
+
+**IMPORTANT:** Before evaluating a journey, you MUST read this reference:
+[journeys.md](references/journeys.md) The reference file
+[journeys.md](references/journeys.md) contains instructions for correctly
+evaluating an Android journey. Follow its instructions exactly.
+
+## Doc searching
+
+The `docs` command searches authoritative, high-quality Android developer
+documentation in the Android Knowledge Base. By providing a few keywords, this
+tool will return high quality articles that contain examples or guidance on how
+to use Android APIs or libraries. Use this tool to obtain additional information
+on how to achieve Android-specific tasks or to know more about Android APIs,
+surfaces, libraries, or devices.
+
+Always use this tool to get the most up-to-date information about Android
+concepts. Typical good use cases are:
+- Finding migration guides for APIs.
+- Finding examples for APIs.
+- Finding up-to-date information about Android APIs.
+- Finding best practices for Android concepts.
+
+## Running APKs
+
+Use the `run` command to run Android apps.
+
+## Managing emulators
+
+Manage Android Virtual Devices (AVDs) using the `android emulator` command
+
+## Capturing screenshots
+
+Capture an image of the current screen of a connected Android device and output
+it to a file using the `android screen` command.
+
+## Managing skills
+
+Manage agent skills for Android using the `android skills` command.
+
+## Updating the CLI
+
+Update the Android CLI using the `android update` command.
+
+## `android help` output
+
+ Usage: android [-Vhv] [--sdk=PARAM] [COMMAND]
+ -h, --help Show the help message for this command
+ --sdk=PARAM Path to the Android SDK
+ -v, --verbose Enable verbose output for troubleshooting
+ -V, --version Print version information and exit
+ Commands:
+ completion Installs shell autocomplete configuration for the Android CLI in
+ the current user profile
+ create Create a new Android project from available templates. Allows
+ specifying project name, output directory, minSdk, and dry-run
+ execution
+ describe Analyzes an Android project to generate descriptive metadata. This
+ command identifies and outputs the paths to JSON files that detail
+ the project's structure, including build targets and their
+ corresponding output artifact locations (e.g., APKs). This
+ information enables other tools and commands to locate build
+ artifacts efficiently
+ docs Android documentation commands for searching and fetching
+ developer documentation from the official knowledge base
+ emulator Manage Android Virtual Devices (AVDs). Includes commands to start,
+ stop, list, and view details about emulators
+ help Shows the help of all commands
+ info Print environment information including SDK location, connected
+ devices, and configuration variables. Use specific fields to
+ narrow the output
+ init Initializes the environment for the Android CLI. Sets up required
+ configurations, directories, and default skills
+ install Install an Android Application (one or more APKs) to a connected
+ device or emulator without activating any components using
+ incremental optimizations for faster deployment than adb
+ layout Returns the layout tree of an application
+ run Build, deploy, and launch an Android application on a connected
+ device or emulator
+ screen Commands to view the device
+ sdk Manage the Android SDK installation. Includes commands to install,
+ update, remove, and list available and installed SDK packages
+ skills Manage Android CLI skills. Includes commands to install, remove,
+ list, and search for skills by keyword
+ studio Android Studio commands
+ update Update the Android CLI to the latest version
+
+ completion
+ Usage: android completion [-h] []
+ Installs shell autocomplete configuration for the Android CLI in the
+ current user profile
+
+ Options:
+ -h, --help Show the help message for this command
+
+ android options:
+ --sdk=PARAM Path to the Android SDK
+ -v, --verbose Enable verbose output for troubleshooting
+ -V, --version Print version information and exit
+
+ Positional Parameters:
+ If provided, print the given shell completion without
+ installing. Supported: bash, zsh
+
+ create
+ Usage: android create [-h] [--applicationId=PARAM] [--list] [--minSdk=PARAM]
+ [--name=PARAM] [--namespace=PARAM] [--output=PARAM]
+ []
+ Create a new Android project from available templates. Allows
+ specifying project name, output directory, minSdk, and dry-run
+ execution
+
+ Options:
+ --applicationId=PARAM The application ID for the application
+ (e.g. 'com.example.myapp')
+ -h, --help Show the help message for this command
+ --list List all available templates
+ --minSdk=PARAM The 'minSdk' supported by the application
+ (default is defined in the template)
+ --name=PARAM The name of the application (e.g. 'My
+ Application')
+ --namespace=PARAM The namespace for resources and package
+ name for Kotlin source files
+ -o, --output=PARAM The destination project directory path
+ (default is '.')
+
+ android options:
+ --sdk=PARAM Path to the Android SDK
+ -v, --verbose Enable verbose output for troubleshooting
+ -V, --version Print version information and exit
+
+ Positional Parameters:
+ The template name
+
+ describe
+ Usage: android describe [-h] [--project_dir=PARAM]
+ Analyzes an Android project to generate descriptive metadata. This
+ command identifies and outputs the paths to JSON files that detail the
+ project's structure, including build targets and their corresponding
+ output artifact locations (e.g., APKs). This information enables other
+ tools and commands to locate build artifacts efficiently
+
+ Options:
+ -h, --help Show the help message for this command
+ --project_dir=PARAM The project directory to describe
+
+ android options:
+ --sdk=PARAM Path to the Android SDK
+ -v, --verbose Enable verbose output for troubleshooting
+ -V, --version Print version information and exit
+
+ docs
+ Usage: android docs [-h] [COMMAND]
+ Android documentation commands for searching and fetching developer
+ documentation from the official knowledge base
+
+ Options:
+ -h, --help Show the help message for this command
+
+ android options:
+ --sdk=PARAM Path to the Android SDK
+ -v, --verbose Enable verbose output for troubleshooting
+ -V, --version Print version information and exit
+ Commands:
+ search Search Android documentation. Enclose keywords in quotes
+ fetch Fetch an Android documentation article from a URL (kb://...)
+
+ emulator
+ Usage: android emulator [-h] [COMMAND]
+ Manage Android Virtual Devices (AVDs). Includes commands to start,
+ stop, list, and view details about emulators
+
+ Options:
+ -h, --help Show the help message for this command
+
+ android options:
+ --sdk=PARAM Path to the Android SDK
+ -v, --verbose Enable verbose output for troubleshooting
+ -V, --version Print version information and exit
+ Commands:
+ create Creates a virtual device
+ start Launches the specified virtual device. This command will
+ return when the emulator is fully started and ready to use
+ stop Stops the specified virtual device
+ list Lists available virtual devices
+ remove Delete a virtual device
+
+ help
+ Usage: android help [-h] [COMMAND]
+ Shows the help of all commands
+
+ Options:
+ -h, --help Show the help message for this command
+
+ android options:
+ --sdk=PARAM Path to the Android SDK
+ -v, --verbose Enable verbose output for troubleshooting
+ -V, --version Print version information and exit
+
+ Positional Parameters:
+ COMMAND The command to show help for
+
+ info
+ Usage: android info [-h] []
+ Print environment information including SDK location, connected
+ devices, and configuration variables. Use specific fields to narrow
+ the output
+
+ Options:
+ -h, --help Show the help message for this command
+
+ android options:
+ --sdk=PARAM Path to the Android SDK
+ -v, --verbose Enable verbose output for troubleshooting
+ -V, --version Print version information and exit
+
+ Positional Parameters:
+ The specific field to print the value of. If omitted print
+ all
+
+ init
+ Usage: android init [-h]
+ Initializes the environment for the Android CLI. Sets up required
+ configurations, directories, and default skills
+
+ Options:
+ -h, --help Show the help message for this command
+
+ android options:
+ --sdk=PARAM Path to the Android SDK
+ -v, --verbose Enable verbose output for troubleshooting
+ -V, --version Print version information and exit
+
+ install
+ Usage: android install [-h] [--apks=PARAM] [--device=PARAM] [--install-options=PARAM]
+ [--use-delta-install]
+ Install an Android Application (one or more APKs) to a connected
+ device or emulator without activating any components using incremental
+ optimizations for faster deployment than adb
+
+ Options:
+ --apks=PARAM The paths to the APKs, comma separated
+ --device=PARAM The device serial number
+ -h, --help Show the help message for this command
+ --install-options=PARAM Additional options/flags to pass to
+ package manager install (e.g. -g,-d)
+ --use-delta-install Use fast delta install (speeds up
+ incremental updates by transferring
+ only modified code and resources;
+ default: true)
+
+ android options:
+ --sdk=PARAM Path to the Android SDK
+ -v, --verbose Enable verbose output for
+ troubleshooting
+ -V, --version Print version information and exit
+
+ layout
+ Usage: android layout [-dhp] [--device=PARAM] [--flat] [--full] [--no-idle]
+ [--output=PARAM]
+ Returns the layout tree of an application
+
+ Options:
+ --device=PARAM The device serial number
+ -d, --diff Deprecated; no-op flag. Will be removed in a
+ future release
+ --flat Returns a flat list instead of a tree
+ --full Return the full tree, including non-interactive
+ and hidden elements
+ -h, --help Show the help message for this command
+ --no-idle Don't wait for layout idle state when fetching
+ layout
+ -o, --output=PARAM Writes the layout to the specified file or
+ directory. If omitted, prints to standard output
+ -p, --pretty Pretty-prints the returned JSON
+
+ android options:
+ --sdk=PARAM Path to the Android SDK
+ -v, --verbose Enable verbose output for troubleshooting
+ -V, --version Print version information and exit
+
+ run
+ Usage: android run [-h] [--activity=PARAM] [--apks=PARAM] [--debug] [--device=PARAM]
+ [--install-options=PARAM] [--type=PARAM] [--use-delta-install]
+ Build, deploy, and launch an Android application on a connected device
+ or emulator
+
+ Options:
+ --activity=PARAM The activity name
+ --apks=PARAM The paths to the APKs, comma separated
+ --debug Run in debug mode
+ --device=PARAM The device serial number
+ -h, --help Show the help message for this command
+ --install-options=PARAM Additional options/flags to pass to
+ package manager install (e.g. -g,-d)
+ --type=PARAM The component type (ACTIVITY,
+ WATCH_FACE, TILE, COMPLICATION,
+ DECLARATIVE_WATCH_FACE, WEAR_WIDGET)
+ --use-delta-install Use fast delta install (speeds up
+ incremental updates by transferring
+ only modified code and resources;
+ default: true)
+
+ android options:
+ --sdk=PARAM Path to the Android SDK
+ -v, --verbose Enable verbose output for
+ troubleshooting
+ -V, --version Print version information and exit
+
+ screen
+ Usage: android screen [-h] [COMMAND]
+ Commands to view the device
+
+ Options:
+ -h, --help Show the help message for this command
+
+ android options:
+ --sdk=PARAM Path to the Android SDK
+ -v, --verbose Enable verbose output for troubleshooting
+ -V, --version Print version information and exit
+ Commands:
+ capture Outputs the device screen to a PNG
+ resolve Target UI elements visually. Substitutes bounding box
+ coordinates from a annotated screenshot into a string.
+ Replaces all instances of '#N' with the center coordinates
+ of the bounding box labeled 'N'
+
+ sdk
+ Usage: android sdk [-h] [--platform=PARAM] [COMMAND]
+ Manage the Android SDK installation. Includes commands to install,
+ update, remove, and list available and installed SDK packages
+
+ Options:
+ -h, --help Show the help message for this command
+ --platform=PARAM Target platform _ (e.g.
+ linux_x86_64, mac_arm64, windows_x86),
+ defaults to current host
+
+ android options:
+ --sdk=PARAM Path to the Android SDK
+ -v, --verbose Enable verbose output for troubleshooting
+ -V, --version Print version information and exit
+ Commands:
+ install Install SDK packages
+ update Update one or all packages to the latest version
+ remove Remove packages from the SDK
+ list List installed and available SDK packages
+
+ skills
+ Usage: android skills [-h] [COMMAND]
+ Manage Android CLI skills. Includes commands to install, remove, list,
+ and search for skills by keyword
+
+ Options:
+ -h, --help Show the help message for this command
+
+ android options:
+ --sdk=PARAM Path to the Android SDK
+ -v, --verbose Enable verbose output for troubleshooting
+ -V, --version Print version information and exit
+ Commands:
+ add Install a specific skill by its ID to your environment
+ remove Remove an installed skill by its ID
+ list List installed and available skills
+ find Search for available skills in the repository matching a
+ keyword
+ update Update installed skills
+
+ studio
+ Usage: android studio [-h] [COMMAND]
+ Android Studio commands
+
+ Options:
+ -h, --help Show the help message for this command
+
+ android options:
+ --sdk=PARAM Path to the Android SDK
+ -v, --verbose Enable verbose output for troubleshooting
+ -V, --version Print version information and exit
+ Commands:
+ find-declaration Find declaration of a symbol
+ find-usages Find usages of a symbol
+ open-file Open a file in Android Studio
+ check Check the status of running Studio instances
+ analyze-file Analyze a file in Android Studio
+ render-compose-preview Render a Compose preview in Android Studio
+ version-lookup Looks up the latest available versions on
+ the internet of maven artifacts, Android
+ versions, and more
+
+ update
+ Usage: android update [-h] [--url=PARAM]
+ Update the Android CLI to the latest version
+
+ Options:
+ -h, --help Show the help message for this command
+ --url=PARAM The URL to download the update from
+
+ android options:
+ --sdk=PARAM Path to the Android SDK
+ -v, --verbose Enable verbose output for troubleshooting
+ -V, --version Print version information and exit
diff --git a/devtools/android-cli/references/interact.md b/devtools/android-cli/references/interact.md
new file mode 100644
index 0000000..2f2a4c4
--- /dev/null
+++ b/devtools/android-cli/references/interact.md
@@ -0,0 +1,113 @@
+Run `android layout --help` and `android screen --help`.
+
+## UI Dump
+
+`android layout` returns a flat JSON list of the UI elements on screen. `android
+layout --diff` returns a flat JSON list of the UI elements that have changed
+since the last call to `layout` or `layout --diff`
+
+Each JSON object represents a UI element in the Android app. The following
+properties may be present:
+
+- `text` - any literal text the element contains
+- `resourceId` - the Android resource id used to refer to the element
+- `contentDesc` - a description of a UI element for use by accessibility tools
+- `interactions` - the set of user interactions the element supports. May contain one or more of: `checkable`, `clickable`, `focusable`, `scrollable`, `long-clickable`, `password`
+- `state` - the set of states the element is in. May contain one or more of `checked`, `focused`, `selected`
+- `bounds` - the screen coordinates of the bounding rectangle of the element, in the format `[min X,min Y][max X, max Y]`
+- `center` - the screen coordinates of the center of the element, in the format `[x,y]`
+- `off-screen` - if true, the element is in the UI hierarchy but not visible; it may require scrolling to view.
+
+Use `layout` as a primary means of examining an Android app. Use `layout --diff`
+to focus on changes and to keep your context small. Example: When entering
+digits into a calculator, use `layout --diff` to output only the digit readout
+element.
+
+`layout` may fail due to the app displaying a WebView or animation; in these
+cases, use `android screen capture --annotate` to inspect the app. This failure
+will likely resolve after navigating away from the current screen.
+
+## Screenshot
+
+`android screen capture -o ` saves a PNG of the current device screen
+to ``
+
+Use `screen capture` as a secondary means of examining an Android app
+Examples:
+
+- Understanding the content of an on-screen image
+- Looking at a `WebView` (web content does not always appear in the ui dump)
+- Trying to find a UI element by its visual appearance
+
+**IMPORTANT** : Always *VISUALLY* examine the PNG image returned from `android
+screen` BEFORE doing anything else.
+
+## Annotated Screenshot
+
+`android screen capture --annotate -o `
+`android screen resolve --screen --string `
+
+The `--annotate` command adds numerical labels and bounding boxes around UI
+elements. Use this command to locate UI elements that cannot be located in the
+`layout` output.
+
+**IMPORTANT** : When using `android screen --annotate`, always *VISUALLY* examine
+the resulting PNG file.
+
+To refer to these labels in input commands, use `screen resolve` to convert
+labels into coordinates:
+
+`android screen resolve --screen --string "#3"` returns ``
+
+To save turns, you can combine shell commands:
+
+`adb shell input $(android screen resolve --screen screen.png --string "tap #34")`
+
+This command taps on region #34 from `screen.png`
+
+## Input
+
+Use `adb shell input` for interacting with Android devices. Refer to the
+`"interactions"` property of an element for what interactions can be performed
+on a particular element.
+
+Interact with UI elements with their `center` coordinate or their `bounds`
+coordinates:
+`json
+{
+"key": -248568265,
+"class": "android.widget.Button",
+"bounds": "[138,9][167,38]",
+"center": "[152,23]"
+}`
+
+To tap on this button, you would execute `adb shell input tap 152 23`. This taps
+the center.
+
+ {
+ "key": 12487234,
+ "class": "com.example.ui.ScrollableList",
+ "bounds": "[100,200][400,600]",
+ "center": "[250,400]"
+ }
+
+To scroll down on this list, you would execute `adb shell input swipe 250 400
+250 200 500`. This swipes from the center to the top over 500ms.
+
+### Text Input
+
+To enter text, ensure the field is focused and execute `adb shell input text
+""`.
+
+- **Spaces (`%s`):** Replace spaces with `%s` (Android ignores text after raw spaces): `adb shell input text "Hello%sworld"` (enters `"Hello world"`).
+- **Special characters:** Escape shell metacharacters (`&`, `$`, `(`, `)`, `!`): `adb shell input text "AT\&T"` or `adb shell input text 'Price:\$10'`.
+- **Submit / Enter:** `adb shell input keyevent 66`
+
+## Android Interaction Rules
+
+1. Always ensure text input fields have `"focused"` in their `"state"` list before entering text.
+2. If an element has `"scrollable"` in its `"interactions"` list, try scrolling it when looking for missing UI elements.
+3. Always scroll slowly when executing scroll inputs. The 5th argument to `adb
+ shell input swipe` controls scroll duration.
+4. Content may take time to load; if a `layout` is missing information after you take an action, wait a few seconds, then perform `layout --diff` to see if anything changes.
\ No newline at end of file
diff --git a/devtools/android-cli/references/journeys.md b/devtools/android-cli/references/journeys.md
new file mode 100644
index 0000000..f3cc163
--- /dev/null
+++ b/devtools/android-cli/references/journeys.md
@@ -0,0 +1,120 @@
+A journey is an XML-specified test of an Android app's behavior. It consists of
+a list of `` elements. For example:
+`xml
+
+
+A sample journey to illustrate the format
+
+
+
+Tap the "Home" icon
+
+
+Verify that the app is on its Home screen
+
+
+`
+
+Evaluate a journey by proceeding through the `` list in sequential
+order. Evaluate each `` block individually.
+A journey succeeds if all elements in the `` list succeed.
+
+A journey is a test case for an app. The journey XML is the source of truth; if
+the app disagrees with the journey, the app has failed.
+Additionally, if the app exits, crashes, or freezes, journey evaluation stops
+and the journey fails.
+
+**IMPORTANT** - Execute each step EXACTLY as written, and independently of other
+steps! If an action says to `"tap the first search result"`,
+you MUST find the search results and tap the first one. Do this even if you
+believe you know the intent behind the action.
+
+## Taking Actions
+
+Some `` elements specify UI interactions to perform on the running
+Android app. Perform the interaction and verify that the app does
+not crash or behave in an unexpected manner. This is the *only* verification
+you should perform for an ``.
+
+If the interaction cannot be performed as specified, the journey fails.
+Example:
+`Click the red button`
+If you determine a red button is not present in the UI, the journey fails.
+
+If the text of an `` specifies a list of actions, break it into
+sub-actions and evaluate them individually:
+Example:
+`Search for soda and add the first result to the cart`
+This should be evaluated as:
+`Search for soda
+Add the first result to the cart`
+
+If an `` contains something that is not a specification for a UI
+interaction, alert the user that the journey is malformed and exit
+early, specifying the error in question.
+
+## Verifying Expectations
+
+`` elements that begin with "check" or "verify" specify expectations
+for the current state of the Android app. Determine the current
+state of the app and check if the expectations are met.
+
+Determine the current state of the app by inspecting the current screen of the
+device without interacting with it.
+Example:
+`Check if "Switch 2" is visible on the screen`
+This requires only inspecting the current screen, not scrolling or interacting.
+If "Switch 2" is not currently visible, the action fails.
+
+If the expectations are not met, mark the `` as a failure and the
+journey evaluation ends. A single `` may contain
+multiple expectations.
+Example:
+`Verify that the app is on the Home screen, the Home icon is blue, and the temperature is displayed`
+This `` fails if ANY of the following are false:
+
+- The app is on the Home screen
+- There is a Home icon, and it is blue
+- A temperature is displayed
+
+## Handling failure
+
+When running a journey, evaluate it as a test. Failure is acceptable, and often
+expected. Proper reporting of failures is the priority.
+
+Keep debugging and troubleshooting to a minimum; assume that tools are showing
+you the correct output every time. The goal is to determine
+if the *current* Android app can correctly handle the *current* steps outlined
+in the journey. Suggestions for bug fixes, clarification, or
+other improvements should be kept to journey evaluation summary at the end.
+
+## Summarizing
+
+After evaluating the whole journey, output a markdown (.md) file describing the
+results.
+
+The file should have the following format:
+
+ # Journey:
+
+ ## Results
+
+
+ ### Action: Click the blue button ✅
+ - **Commands**:
+
+ - `adb shell input swipe 490 200 500 500 500`
+ - `adb shell input tap 45 920`
+
+
+ - **Comment**: The journey step doesn't specify that the button requires scrolling to see
+
+ ### Action: Check if "Switch 2" is visible on the screen ✅
+ - **Screenshot**: [switch_2_screenshot.png](path/to/screenshot)
+ - **Comment**: Confirmed both in layout and visually
+
+ ### Action: The home screen is shown ❌
+ - **Comment**: The settings page was shown
+
+
+ ### Action: Swipe down to view active notifications
\ No newline at end of file
diff --git a/identity/restore-credentials/SKILL.md b/identity/restore-credentials/SKILL.md
new file mode 100644
index 0000000..cc9078c
--- /dev/null
+++ b/identity/restore-credentials/SKILL.md
@@ -0,0 +1,356 @@
+---
+name: restore-credentials
+description: Provides knowledge and workflows to implement Android's Restore Credentials
+ feature using the androidx.credentials library. Use this skill to create, sign in
+ with, and delete restore keys, enabling silent user sign-in on new devices after
+ a restore. It covers version compatibility, dependencies, server-side prerequisites,
+ and the complete client-side implementation for creating, retrieving, and clearing
+ restore keys.
+license: Complete terms in LICENSE.txt
+metadata:
+ author: Google LLC
+ last-updated: '2026-09-14'
+ keywords:
+ - Credential Manager
+ - Restore Credentials
+ - backup & restore
+ - backup
+ - restore
+ - implementation
+---
+
+## Fundamentals
+
+The objective is to implement the **Restore Credentials** feature through the
+Android Credential Manager API (`androidx.credentials`). This allows apps that
+use or are integrating Credential Manager to silently log users back in when
+they restore their app on a new device. Restore Credentials operates
+independently of the app's primary authentication method (passwords, passkeys,
+federated sign-in) and requires no UI changes to existing sign-in flows.
+
+### Scope
+
+**Crucial:** This skill focuses exclusively on the Android client-side
+integration. It does **not** implement the server-side cryptographic
+validation logic. The developer must be reminded of this and the
+[Points to inform the developer about](#backend-guidelines) after implementation is done.
+
+## Implementation Guidelines
+
+When instructed to implement Restore Credentials on a developer's application,
+remember the following:
+
+1. Before the implementation, you **MUST** read and understand the [Two-Tier
+ Restoration Architecture](#two-tier-restoration-architecture) and review the [DOs and DON'Ts](#dos-and-donts).
+2. After the implementation, you **MUST** present the developer with the [Backend Guidelines](#backend-guidelines) as a reminder for their backend setup. It is important that you remind the developer that they still have to implement the backend.
+
+## Two-Tier Restoration Architecture
+
+To enable a resilient sign-in experience, retrieve credentials through a
+**two-tier architecture**:
+
+1. **Tier 1 (Primary - Background):** Executes automatically during device setup using the app's `BackupAgent.onRestoreFinished()` callback. This provides an invisible restoration before the user opens the app for the first time, allowing background sync and notification delivery.
+2. **Tier 2 (Secondary - Foreground):** Runs in the Launcher `Activity.onCreate()` to catch failovers if background restoration didn't complete (example: dropped network, delayed restoration) or if `allowBackup` is disabled.
+
+If `allowBackup` in the manifest is set to true, implement both. Otherwise, only
+implement tier 2 (Foreground Restoration). Do **NOT** change the value of
+`allowBackup` in the manifest.
+
+If you added a `BackupAgent` to the app, you **MUST** also set
+`android:fullBackupOnly="true"` in the manifest. Do **NOT** do this if there
+already existed a `BackupAgent` in the app before your implementation.
+
+## DOs and DON'Ts
+
+**DO:**
+
+- Do check `AndroidManifest.xml` for the value of allowBackup to determine what you have to implement.
+- Do implement a fallback for createCredential: always try calling it first with `isCloudBackupEnabled` set to true. If an `E2eeUnavailableException` is thrown, catch it and retry the call with `isCloudBackupEnabled` set to `false`.
+- Do implement a `BackupAgent` (subclass of `android.app.backup.BackupAgent`) if `allowBackup` is true in the manifest.
+- Do set `android:fullBackupOnly="true"` in the manifest if you added a `BackupAgent` to the app.
+- Call `clearCredentialState()` when the user signs out. This is a mandatory security measure to log the user out fully.
+- Do attempt to get the restore key on the first launch of the app on a new device and also within the `BackupAgent.onRestoreFinished()` callback if your app uses it.
+- Do ensure that a restore credential is created even if the user is already logged in.
+- Do ensure that the credential retrieval and login in `onRestoreFinished()` is performed synchronously (for example using `runBlocking`).
+- Do restore notifications in the `BackupAgent` if your app uses them. (For example capture and send FCM token to backend)
+- Do ensure that if you implement mock network requests or stubs, you replace all placeholders with valid, properly formatted JSON payloads for the credential requests.
+- Do encapsulate credential creation and retrieval into their own dedicated functions. Because credential creation must be called in multiple places (sign-up, sign-in) and retrieval across multiple tiers (`BackupAgent` and Launcher `Activity`), this prevents code duplication.
+- Do remind the developer of the [critical guidelines](#implementation-guide) for implementing the backend once you're done with the implementation.
+- Do generate a separate restore key for each application if the organization has multiple apps with different package names, as a restore key is tied to a unique application package name.
+
+**DON'T:**
+
+- DON'T change the value of `allowBackup` in `AndroidManifest.xml`. Restore Credentials functionality is not affected by the allowBackup setting, meaning the user will still be automatically logged in when a Restore Credential exists, even if `allowBackup` is false.
+- DON'T implement a `BackupAgent` if `allowBackup` is `false` in `AndroidManifest.xml`.
+- DON'T assume the credential stored in the `GetCredentialResponse` to be of type `PublicKeyCredential`. It has type `RestoreCredential`.
+- DON'T chain `GetRestoreCredentialOption` with any other `CredentialOption` in the construction of a `GetCredentialRequest`.
+- DON'T assume `CredentialManager` or the Android system will automatically delete a restore key when a user signs out of the app. You must explicitly call `clearCredentialState` with a `ClearCredentialStateRequest` of `TYPE_CLEAR_RESTORE_CREDENTIAL`.
+- DON'T remove any existing calls to `clearCredentialState()`. A `ClearCredentialStateRequest` without a type specified only clears all NON-restore credentials.
+
+## Implementation Guide
+
+Implement the Android client-side code by using the following guide. Follow it
+**step-by-step** and don't implement any backend functionality, only remind
+the user of the [Backend Guidelines](#backend-guidelines) once you're done.
+
+## Version compatibility
+
+Credential Manager's Restore Credentials works on devices running Android 9 (API
+level 28) and higher, Google Play services (GMS) core version 24220000 or
+higher, and version 1.5.0 or higher of the `androidx.credentials` library.
+
+## Prerequisites
+
+Set up a [relying party server](#backend-guidelines) similar to the server for [passkeys](#dos-and-donts). If
+you already have a [server](#two-tier-restoration-architecture) set up to handle authentication with passkeys,
+use the same server-side implementation for restore keys.
+
+> [!NOTE]
+> **Note:** While the server-side implementation is the same for passkeys and restore keys, your client-side app can support restore keys without supporting passkeys. Because restore keys work independently of the authentication method in your app (for example, passwords or Sign in with Google), you don't need to make any additional changes to the existing authentication methods in your app's code.
+
+## Dependencies
+
+Add the following dependencies to your app module's `build.gradle` file:
+
+### Kotlin
+
+```kotlin
+dependencies {
+ implementation("androidx.credentials:credentials:1.7.0-alpha03")
+ implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha03")
+}
+```
+
+### Groovy
+
+```groovy
+dependencies {
+ implementation "androidx.credentials:credentials:1.7.0-alpha03"
+ implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha03"
+}
+```
+
+Restore Credentials is available from version 1.5.0 and higher of the
+androidx.credentials library. However, it's recommended to use the latest stable
+versions of the dependencies where possible.
+
+> [!NOTE]
+> **Note:** The Restore Credentials feature works regardless of whether [`allowBackup`](references/android/guide/topics/manifest/application-element.md) is set in the manifest.
+
+## Overview
+
+1. [**Create a restore key**](#create-restore-key): To create a restore key, complete the following steps:
+ 1. [**Instantiate Credential Manager**](#implementation-guide): Create a `CredentialManager` object.
+ 2. [**Get credential creation options from the app server**](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API): Send the client app the details required to create the restore key from your app server.
+ 3. [**Create the restore key**](https://w3c.github.io/webauthn/#dictdef-publickeycredentialcreationoptionsjson): Create a restore key for the user's account if the user is signed in to your app.
+ 4. [**Handle the credential creation response**](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson): Send the credentials from your client app to your app server for processing, and handle any exceptions.
+2. [**Sign in with a restore key**](#sign-restore): To sign in with a restore key, complete the following steps:
+ 1. [**Get credential retrieval options from the app server**](#get-credential-retrieval): Send the client app the details required to retrieve the restore key from your app server.
+ 2. [**Get the restore key**](#get-restore): Request the restore key from Credential Manager when the user sets up a new device. This lets the user sign in without additional input.
+ 3. [**Handle the credential retrieval response**](#handle-sign-in): Send the restore key from the client app to the app server to sign in the user.
+3. [**Delete a restore key**](#delete-restore).
+
+## Create a restore key
+
+Your app should cover all cases of a user signing in to ensure active users have
+a restore key created. Create the restore key in the following scenarios:
+
+- If the user is signed in and a restore key isn't already created (such as in the `onCreate` method for the main `Activity`).
+- When the user is signing in or completing a new account registration flow.
+
+To optimize performance and avoid the overhead of creating or checking for a
+restore credential on every single login, set a `boolean` flag or a credential
+creation timestamp in local storage, such as `has_synced_restore_credential`, to
+track whether the key has already been created.
+
+> [!NOTE]
+> **Note:** A restore key is tied to an application's unique package name. If your organization's main app and sub-apps have different package names, create a separate restore key for each app.
+
+### Instantiate Credential Manager
+
+Use your app's activity context to instantiate a `CredentialManager` object.
+
+ // Use your app or activity context to instantiate a client instance of
+ // CredentialManager.
+ private val credentialManager = CredentialManager.create(context)
+
+### Get credential creation options from your app server
+
+Use a FIDO-compliant library in your app server to send your client app the
+information required to create the restore credential, such as information about
+the user, the app, and additional configuration properties. For more information
+about the server-side implementation, see [Server-side
+guidance](https://developers.google.com/identity/passkeys/developer-guides/server-registration).
+
+### Create the restore key
+
+After parsing the public key creation options sent by the server, create a
+restore key by wrapping these options in a
+[`CreateRestoreCredentialRequest`](https://developer.android.com/reference/androidx/credentials/CreateRestoreCredentialRequest) object and calling the
+[`createCredential()`](https://developer.android.com/reference/androidx/credentials/CredentialManager#createCredential(android.content.Context,androidx.credentials.CreateCredentialRequest)) method with the `CredentialManager` object.
+
+ // createRestoreRequest contains the details sent by the server
+ val response = credentialManager.createCredential(context, createRestoreRequest)
+
+#### Key points about the code
+
+- The `CreateRestoreCredentialRequest` object contains the following fields:
+
+ - `requestJson`: The credential creation options sent by the app server in the [Web Authentication API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API) format for [`PublicKeyCredentialCreationOptionsJSON`](https://w3c.github.io/webauthn/#dictdef-publickeycredentialcreationoptionsjson).
+ - `isCloudBackupEnabled`: `Boolean` field to determine if the restore key
+ should be backed up to the cloud. By default, this flag is `true`. This
+ field has these values:
+
+ - `true`: (**Recommended**) This value enables the backup of restore keys to the cloud if the user has Google Backup and end-to-end encryption, such as a screen lock, enabled.
+ - `false`: This value saves the key locally and not in the cloud. The key is not available on the new device if the user chooses to restore from the cloud.
+
+ > [!CAUTION]
+ > **Caution:** It's recommended to set `isCloudBackupEnabled` to `true`. If cloud backup is disabled and the user restores from a cloud backup, the call to retrieve the restore key fails. Users who restore your app with a cloud backup don't receive the restore key and aren't automatically signed in.
+
+### Handle the credential creation response
+
+The Credential Manager API returns a response of type
+[`CreateRestoreCredentialResponse`](https://developer.android.com/reference/androidx/credentials/CreateRestoreCredentialResponse). This response holds the public key
+credential registration response in [JSON format](https://w3c.github.io/webauthn/#authenticatorattestationresponse).
+
+Send the public key from your app to the relying party server. This public key
+is similar to the public key generated when you create a passkey. The same code
+that handles passkey creation on the server can also handle restore key
+creation. For more information about the server-side implementation, see [the
+guidance for passkeys](references/android/identity/passkeys/create-passkeys.md).
+
+During the restore key creation process, handle these exceptions:
+
+- [`CreateRestoreCredentialDomException`](https://developer.android.com/reference/androidx/credentials/exceptions/restorecredential/CreateRestoreCredentialDomException): This exception occurs if `requestJson` is invalid and does not follow the WebAuthn format for [`PublicKeyCredentialCreationOptionsJSON`](https://w3c.github.io/webauthn/#dictdef-publickeycredentialcreationoptionsjson).
+- [`E2eeUnavailableException`](https://developer.android.com/reference/androidx/credentials/exceptions/restorecredential/E2eeUnavailableException): This exception occurs if `isCloudBackupEnabled` is `true`, but the user's device doesn't have data backup or end-to-end encryption, such as a screen lock.
+ To ensure that Restore Credentials are created in all cases, you must handle the `E2eeUnavailableException` explicitly by calling `createCredential` with `isCloudBackupEnabled` set to `true`. If `E2eeUnavailableException` is thrown, catch and call `createCredential` again with `isCloudBackupEnabled` set to `false`.
+- `IllegalArgumentException`: This exception occurs if `createRestoreRequest` is empty or not valid JSON, or if it doesn't have a valid `user.id` that conforms to the WebAuthn [specifications](https://w3c.github.io/webauthn/#dictdef-publickeycredentialcreationoptionsjson).
+
+## Sign in with a restore key
+
+Use Restore Credentials to silently sign in the user during the device setup
+process.
+
+### Get credential retrieval options from the app server
+
+Send the client app the options required to get the restore key from the server.
+For similar passkey guidance for this step, see [Sign in with a passkey](references/android/identity/passkeys/sign-in-with-passkeys.md).
+For more information about the server-side implementation, see the [server-side
+authentication guide](https://developers.google.com/identity/passkeys/developer-guides/server-authentication#create_credential_request_options).
+
+### Get the restore key
+
+To get the restore key on the new device, call the `getCredential()` method on
+the `CredentialManager` object.
+
+It's recommended to fetch the restore key in both of the following scenarios:
+
+- On the first launch of the app on the device. Credential restoration in this scenario is independent of restoration of the app data.
+- If app data backup and restore is enabled, get the restore key immediately after the app data is restored. Use [`BackupAgent`](https://developer.android.com/reference/android/app/backup/BackupAgent) to configure your app's backup and ensure you complete the `getCredential` functionality within the [`onRestoreFinished`](https://developer.android.com/reference/android/app/backup/BackupAgent#onRestoreFinished()) callback. Don't use the `onRestore` method, as it is only called for key-value backups, whereas `onRestoreFinished` is reliably called for any kind of backup restore. This avoids potential delays when users open their new device for the first time and lets users interact with the app without waiting for them to open your app. For example, this lets your app send the user notifications before they open the app for the first time on the new device, which is particularly relevant for messaging or communications apps.
+
+If you create a new `BackupAgent` and previously had backup enabled with
+`allowBackup="true"`, set the boolean value `android:fullBackupOnly="true"` in
+your app's manifest. This ensures that your app's backup and restore behavior is
+maintained.
+
+> [!IMPORTANT]
+> **Important:** Notifications aren't automatically restored after the restore credentials are retrieved. If you use Firebase to handle notifications, you must fetch and send the Firebase Cloud Messaging (FCM) token to the backend to successfully resume background messaging and notifications.
+
+ // Fetch the options required to get the restore key
+ val authenticationJson = fetchAuthenticationJson()
+
+ // Create the GetRestoreCredentialRequest object
+ val options = GetRestoreCredentialOption(authenticationJson)
+ val getRequest = GetCredentialRequest(listOf(options))
+
+ val response = credentialManager.getCredential(context, getRequest)
+
+ // Type-check and extract the restore credential
+ val credential = response.credential as RestoreCredential
+
+The credential manager APIs return a response of type
+[`GetCredentialResponse`](https://developer.android.com/reference/android/credentials/GetCredentialResponse). The credential contained in this response is
+explicitly of type `RestoreCredential`, which holds the public key.
+
+### Handle the sign-in response
+
+Send the public key from the app to the relying party server, which can then be
+used to sign in the user. On the server side, this action is similar to signing
+in using a passkey. The same code that handles sign-in with passkeys on the
+server can also handle sign-ins with restore keys. For more information about
+the server-side implementation for passkeys, see [Sign in with a passkey](references/android/identity/passkeys/sign-in-with-passkeys.md).
+
+> [!NOTE]
+> **Note:** Even though restore keys and passkeys use the same underlying server implementation, differentiate between them when saving them in your app server's database. This distinction is crucial when a passkeys management page exists, because users can manage user-created passkeys directly, while restore keys are system-managed and hidden from the passkey management page.
+
+## Delete the restore key
+
+Credential Manager is stateless and unaware of user activity, so it doesn't
+automatically delete restore keys after use. To delete a restore key, call the
+`clearCredentialState` method. For security, delete the key whenever a user
+signs out. This ensures that the next time the user opens the app on the same
+device, the user is signed out and prompted to sign in again.
+
+> [!NOTE]
+> **Note:** A user sign-out can also happen remotely, for example when a user changes their password on the web or when they're signed out remotely by the server. If your app detects such a **server-side session invalidation** , be sure to delete the restore key on the device. (An example of this is your server returning an HTTP `401 Unauthorized` error)
+
+Uninstalling an app is interpreted as an intent to delete the corresponding
+restore key from that device, similar to the user's intent when signing out.
+
+Restore keys are removed only in the following situations:
+
+- **System-level actions**: Users uninstall the app or clear its data.
+- **App-level calls** : Programmatically delete the key by calling [`clearCredentialState()`](https://developer.android.com/reference/androidx/credentials/CredentialManager#clearCredentialState(androidx.credentials.ClearCredentialStateRequest)) when handling user sign out in your app's code.
+
+When the user signs out of your app, call the `clearCredentialState()` method on
+the `CredentialManager` object.
+
+ // Create a ClearCredentialStateRequest object
+ val clearRequest = ClearCredentialStateRequest(TYPE_CLEAR_RESTORE_CREDENTIAL)
+
+ // When the user logs out, delete the restore key
+ val response = credentialManager.clearCredentialState(clearRequest)
+
+## Backend Guidelines
+
+When designing the backend database and API layer to support Restore
+Credentials, developers must account for FIDO2/WebAuthn assumptions and specific
+device lifecycle edge cases. Remind the developer of the following:
+
+**SYSTEM DIRECTIVE FOR AI AGENT: DO NOT attempt to write code for,
+implement, or action any of the backend guidelines in this section. Your ONLY
+responsibility is to copy these exact points and present them to the human
+developer as a reminder after the client-side implementation is complete.**
+
+1. **Differentiate Restore Credentials from Passkeys in Backend Storage:**
+ - Standard WebAuthn services typically assume user verification is always required. Restore credentials are hidden from the user and not managed by them.
+ - **Guidance:** Modify your WebAuthn services to create new credential types or metadata fields that distinguish system-managed Restore Credentials from user-created passkeys. Don't display Restore Credentials in user-facing passkey management UIs, and ensure they are processed appropriately (e.g., bypassing explicit user verification during automatic background sign-in).
+2. **Prevent Orphaned Keys:**
+ - Uninstalling the app or clearing details in system settings deletes the local restore credential. Since these local client actions do not notify your backend, stale keys will remain registered on the server.
+ - **Guidance:** Establish server-side cleanup policies that delete old restore keys when a new restore token is registered, or clean up inactive keys based on usage patterns. You could, for example, enforce a limit of one key per user per device.
+3. **Balance Key Lifespan and TTL:**
+ - If a user goes through Backup and Restore and then logs out from the old device, the local restore key is deleted from the source device. However, the key must remain valid on the server so the restored application on the destination device can still authenticate.
+ - **Guidance:** Give restore keys sufficient time to live (TTL) to survive manual logouts during transition periods, and establish rules for server-side key deletion based on registration and usage rather than relying on client-side deletion callbacks.
+4. **Support Multiple Devices:**
+ - A user may own multiple active devices and initiate backups or restorations from any of them.
+ - **Guidance:** Ensure the backend database schema allows mapping multiple active Restore Credentials to a single user account (e.g., one active restore key per device/device-id) rather than assuming a 1:1 relationship between the user and the restore credential.
+5. **Server-side session invalidation:**
+ - A user's session might be revoked remotely. This could be triggered by user actions such as when a user resets their password on the web or server actions that can cause the API to return HTTP `401 Unauthorized` indicating session expiry.
+ - **Guidance:** If this applies, make sure to clear the user's restore credentials on the phone when it happens.
+
+## References
+
+- **WebAuthentication API (WebAuthn) Documentation \& Specification**
+ *When to use:* Use these resources ONLY if you need to inspect or debug the
+ strict JSON schema requirements for FIDO2/WebAuthn, specifically when
+ generating mock data or formatting the `requestJson`
+ (`PublicKeyCredentialCreationOptionsJSON`) and `authenticationJson` payloads.
+
+- [MDN Web Authentication API Documentation](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API): Mozilla Developer Network
+ guide and reference for WebAuthn APIs
+
+- [W3C `PublicKeyCredentialCreationOptionsJSON`](https://w3c.github.io/webauthn/#dictdef-publickeycredentialcreationoptionsjson): Data structure definition
+ for WebAuthn credential creation requests in JSON format.
+
+- [W3C `PublicKeyCredentialRequestOptionsJSON`](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson): Data structure definition
+ for WebAuthn authentication or assertion requests in JSON format.
diff --git a/identity/restore-credentials/references/android/guide/topics/manifest/application-element.md b/identity/restore-credentials/references/android/guide/topics/manifest/application-element.md
new file mode 100644
index 0000000..638053a
--- /dev/null
+++ b/identity/restore-credentials/references/android/guide/topics/manifest/application-element.md
@@ -0,0 +1,832 @@
+syntax:
+:
+
+ ```xml
+
+ . . .
+
+ ```
+
+contained in:
+: `https://developer.android.com/guide/topics/manifest/manifest-element`
+
+can contain:
+: `https://developer.android.com/guide/topics/manifest/activity-element`
+
+ `https://developer.android.com/guide/topics/manifest/activity-alias-element`
+
+ `https://developer.android.com/guide/topics/manifest/meta-data-element`
+
+ `https://developer.android.com/guide/topics/manifest/service-element`
+
+ `https://developer.android.com/guide/topics/manifest/receiver-element`
+
+ `https://developer.android.com/guide/topics/manifest/profileable-element`
+
+ `https://developer.android.com/guide/topics/manifest/provider-element`
+
+ `https://developer.android.com/guide/topics/manifest/uses-library-element`
+
+ `https://developer.android.com/guide/topics/manifest/uses-native-library-element`
+
+ `https://developer.android.com/guide/topics/manifest/property-element`
+
+description:
+
+: The declaration of the application. This element contains subelements
+ that declare each of the application's components and has attributes
+ that can affect all the components.
+
+ Many of these attributes, such as
+ `icon`, `label`, `permission`, `process`,
+ `taskAffinity`, and `allowTaskReparenting`, set default values
+ for corresponding attributes of the component elements. Others, such as
+ `debuggable`, `enabled`, `description`, and
+ `allowClearUserData`, set values for the application as a whole and
+ aren't overridden by the components.
+
+attributes
+:
+
+ `android:allowTaskReparenting`
+ : Whether activities that the application defines can move from
+ the task that started them to the task they have an affinity for when that task
+ is next brought to the front. It's `"true"` if they can move, and
+ `"false"` if they must remain with the task where they started.
+ The default value is `"false"`.
+
+
+ The
+ `https://developer.android.com/guide/topics/manifest/activity-element`
+ element has its own
+ `https://developer.android.com/guide/topics/manifest/activity-element#reparent`
+ attribute that can override the value set here.
+
+ `android:allowBackup`
+
+ : Whether to let the application participate in the backup
+ and restore infrastructure. If this attribute is set to `"false"`, no
+ backup or restore of the application is ever performed, disabling all cloud backups
+ and device-to-device (D2D) transfers, even by a full-system backup that otherwise
+ causes all application data to save using `adb`. The default value of this
+ attribute is `"true"`. If your app processes sensitive data, you can keep
+ this attribute set to `"true"` and configure
+ [`android:dataExtractionRules`](https://developer.android.com/guide/topics/manifest/application-element#dataExtractionRules) to safely
+ exclude sensitive keys or temporary caches while preserving user preferences and
+ continuity.
+
+ **Note:**For apps targeting Android 12 (API level 31) or higher, this behavior
+ varies. On devices from some device manufacturers, you can't disable device-to-device migration
+ of your app's files.
+
+ However, you can disable cloud-based backup and restore of your app's files by setting
+ this attribute to `"false"`, even if your app targets Android 12 (API level 31) or
+ higher.
+
+
+ For more information, see the
+ [backup and restore](https://developer.android.com/about/versions/12/behavior-changes-12#backup-restore) section
+ of the page that describes behavior changes for apps targeting Android 12 (API level 31) or
+ higher.
+
+ `android:allowClearUserData`
+
+ : Whether to let the application reset user data. This data includes
+ flags, such as whether the user has seen introductory tooltips, as
+ well as user-customizable settings and preferences. The default value of this
+ attribute is `"true"`.
+
+ **Note:** Only apps that are part of the system image can
+ declare this attribute explicitly. Third-party apps can't include this
+ attribute in their manifest files.
+
+ For more information, see [Data backup overview](https://developer.android.com/guide/topics/data/backup).
+
+ `android:allowCrossUidActivitySwitchFromBelow`
+
+ : Specifies whether activities from other applications in the same task
+ can be launched on top of this application. If set to `"false"`,
+ activity launches that would replace this application in the user's view
+ are blocked.
+
+ The default value is `"true"`.
+
+ For more information, see the guide to
+ [Secure Background Activity Launches](https://developer.android.com/guide/components/activities/secure-bal).
+
+ `android:allowNativeHeapPointerTagging`
+
+ : Whether the app enables the Heap pointer tagging feature. The default value of
+ this attribute is `"true"`.
+
+ **Note:** Disabling this feature **doesn't** address the
+ underlying code health issue. Future hardware devices might not support this manifest tag.
+
+ For more information, see [Tagged Pointers](https://source.android.com/devices/tech/debug/tagged-pointers).
+
+ `android:appCategory`
+
+ : Declares the category of this app. Categories are used to cluster multiple apps
+ together into meaningful groups, such as when summarizing battery, network, or
+ disk usage. Only define this value for apps that fit well into one of
+ the specific categories.
+
+ Must be one of the following constant values.
+
+ | Value | Description |
+ |---|---|
+ | `accessibility` | Apps that are primarily accessibility apps, such as screen-readers. |
+ | `audio` | Apps that primarily work with audio or music, such as music players. |
+ | `game` | Apps that are primarily games. |
+ | `image` | Apps that primarily work with images or photos, such as camera or gallery apps. |
+ | `maps` | Apps that are primarily map apps, such as navigation apps. |
+ | `news` | Apps that are primarily news apps, such as newspapers, magazines, or sports apps. |
+ | `productivity` | Apps that are primarily productivity apps, such as cloud storage or workplace apps. |
+ | `social` | Apps that are primarily social apps, such as messaging, communication, email, or social network apps. |
+ | `video` | Apps that primarily work with video or movies, such as streaming video apps. |
+
+
+ `android:backupAgent`
+ : The name of the class that implements the application's backup agent,
+ a subclass of `https://developer.android.com/reference/android/app/backup/BackupAgent`. The attribute value is
+ a fully qualified class name, such as `"com.example.project.MyBackupAgent"`.
+ However, as a shorthand, if the first character of the name is a period,
+ for example, `".MyBackupAgent"`, it is appended to the
+ package name specified in the
+ `https://developer.android.com/guide/topics/manifest/manifest-element`
+ element.
+
+
+ There is no default. The name must be specific.
+
+ `android:backupInForeground`
+ : Indicates that
+ [Auto Backup](https://developer.android.com/guide/topics/data/autobackup) operations
+ can be performed on this app even if the app is in a foreground-equivalent
+ state. The system shuts down an app during auto backup operation,
+ so use this attribute with caution. Setting this flag to `"true"` can impact app
+ behavior while the app is active.
+
+ The default value is `"false"`, which means that the OS avoids
+ backing up the app while it's running in the foreground, such as a music app
+ that is actively playing music using a service in the
+ `https://developer.android.com/reference/android/app/Service#startForeground(int, android.app.Notification)` state.
+
+ `android:banner`
+ : A [drawable resource](https://developer.android.com/guide/topics/resources/drawable-resource)
+ providing an extended graphical banner for its associated item. Use with the
+ `` tag to supply a default banner for all application activities or with the
+ [``](https://developer.android.com/guide/topics/manifest/activity-element)
+ tag to supply a banner for a specific activity.
+
+ The system uses the banner to represent an app in
+ the Android TV home screen. Therefore, only specify this for applications with an activity that handles the
+ `https://developer.android.com/reference/android/content/Intent#CATEGORY_LEANBACK_LAUNCHER` intent.
+
+
+ This attribute is set as a reference to a drawable resource containing
+ the image, for example `"@drawable/banner"`. There is no default banner.
+
+
+ For more information, see [Provide a home screen banner](https://developer.android.com/training/tv/start/start#banner).
+
+ `android:dataExtractionRules`
+
+ :
+ Applications can set this attribute to an XML resource where they specify the
+ rules determining which files and directories can be copied from the device as part of backup or
+ transfer operations.
+
+
+ For information about the format of the XML file, see [Backup and restore](https://developer.android.com/about/versions/12/behavior-changes-12#backup-restore).
+
+ `android:debuggable`
+ : Whether the application can be debugged, even when running
+ on a device in user mode. It's `"true"` if it can be and `"false"`
+ if not. The default value is `"false"`.
+
+ `android:description`
+ : User-readable text about the application, which is longer and more descriptive than the application label.
+ The value is set as a reference to a string resource. Unlike the label, it can't be a raw string.
+ There is no default value.
+
+ `android:enabled`
+ : Whether the Android system can instantiate components of
+ the application. It's `"true"` if it can and `"false"`
+ if not. If the value is `"true"`, each component's
+ `enabled` attribute determines whether that component is enabled.
+ If the value is `"false"`, it overrides the
+ component-specific values, and all components are disabled.
+
+
+ The default value is `"true"`.
+
+ `android:enableOnBackInvokedCallback`
+
+ : This flag lets you opt out of predictive system animations at the app level.
+
+
+ Set `android:enableOnBackInvokedCallback=false` to turn off predictive back animations at the app level
+ and instruct the system to ignore calls to the `OnBackInvokedCallback` platform API.
+
+ `android:extractNativeLibs`
+ : This attribute indicates whether the package installer extracts native libraries from the
+ APK to the file system. If set to `"false"`, your native libraries are stored
+ uncompressed in the APK. Although your APK might be larger, your application loads faster
+ because the libraries load directly from the APK at runtime.
+
+
+ The default value of `extractNativeLibs` depends on `minSdkVersion` and the
+ version of AGP you're using. In most cases, the default behavior is probably what you want, and
+ you don't have to set this attribute explicitly.
+
+ `android:forceQueryable`
+
+ : Specifies whether this application is visible to all other applications
+ on the device, regardless of what those other applications declare in the
+ `` tags in their manifests.
+
+ The default value is `"false"`.
+
+ For more information, see the guide on
+ [automatic package visibility filtering](https://developer.android.com/training/package-visibility/automatic).
+
+ `android:fullBackupContent`
+ : This attribute points to an XML file that contains full backup rules for
+ [Auto Backup](https://developer.android.com/guide/topics/data/autobackup).
+ These rules determine what files get backed up. For more information, see the
+ [XML config syntax](https://developer.android.com/guide/topics/data/autobackup#XMLSyntax)
+ for Auto Backup.
+
+
+ This attribute is optional. If it is not specified, by default, Auto Backup
+ includes most of your app's files. For more information, see
+ [Files that are backed up](https://developer.android.com/guide/topics/data/autobackup#Files).
+
+ `android:fullBackupOnly`
+ : This attribute indicates whether to use
+ [Auto Backup](https://developer.android.com/guide/topics/data/autobackup) on devices
+ where it is available. If set to `"true"`, then your app performs
+ Auto Backup when installed on a device running Android 6.0 (API level 23) or
+ higher. On older devices, your app ignores this attribute and performs
+ [key/value backups](https://developer.android.com/guide/topics/data/keyvaluebackup).
+
+
+ The default value is `"false"`.
+
+ `android:gwpAsanMode`
+ : This attribute indicates whether to use
+ [GWP-ASan](https://developer.android.com/ndk/guides/gwp-asan), a native memory allocator feature that helps
+ find use-after-free and heap-buffer-overflow bugs.
+
+
+ The default value is `"never"`.
+
+ `android:hasCode`
+ : Whether the application contains any DEX code---that is, code using the
+ Kotlin or Java programming language.
+ It's `"true"` if it does and `"false"` if not. When the
+ value is `"false"`, the system doesn't try to load any application
+ code when launching components. The default value is `"true"`.
+
+
+ If the application includes native (C/C++) code, but no DEX code, this should
+ be set to `"false"`. If set to `"true"` when the APK
+ contains no DEX code, the app may fail to load.
+
+
+ **This property must account for code included in the application by
+ dependencies.** If the application depends on an AAR that uses
+ Java/Kotlin code, or directly on a JAR, `app:hasCode` must be
+ `"true"`, or omitted as that is the default.
+
+
+ For example, your app might support
+ [Play feature delivery](https://developer.android.com/platform/technology/app-bundle) and include feature
+ modules that don't generate any DEX files, which is bytecode optimized for the Android
+ platform. If so, you need to set this property to `"false"` in the module's manifest
+ file to avoid runtime errors.
+
+ `android:hasFragileUserData`
+ : Whether to show the user a prompt to
+ keep the app's data when the user uninstalls the app. The default value is `"false"`.
+
+ `android:hardwareAccelerated`
+ : Whether hardware-accelerated rendering is enabled for all
+ activities and views in this application. It's `"true"` if it is
+ enabled and `"false"` if not. The default value is `"true"` if you set
+ either [`minSdkVersion`](https://developer.android.com/guide/topics/manifest/uses-sdk-element#min)
+ or [`targetSdkVersion`](https://developer.android.com/guide/topics/manifest/uses-sdk-element#target)
+ to `"14"` or higher. Otherwise, it's `"false"`.
+
+ Starting from Android 3.0 (API level 11), a hardware-accelerated OpenGL renderer is
+ available to applications to improve performance for many common 2D graphics
+ operations. When the hardware-accelerated renderer is enabled, most operations
+ in Canvas, Paint, Xfermode, ColorFilter, Shader, and Camera are accelerated.
+
+
+ This results in smoother animations, smoother scrolling, and improved
+ responsiveness overall, even for applications that don't explicitly make use
+ the framework's OpenGL libraries.
+
+
+ Not all of the OpenGL 2D operations are accelerated. If you enable
+ the hardware-accelerated renderer, test your application so that it can
+ make use of the renderer without errors.
+
+
+ For more information, read the
+ [Hardware acceleration](https://developer.android.com/guide/topics/graphics/hardware-accel)
+ guide.
+
+ `android:icon`
+ : An icon for the application as whole and the default icon for
+ each of the application's components. See the individual
+ `icon` attributes for the
+ `https://developer.android.com/guide/topics/manifest/activity-element`,
+ `https://developer.android.com/guide/topics/manifest/activity-alias-element`,
+ `https://developer.android.com/guide/topics/manifest/service-element`,
+ `https://developer.android.com/guide/topics/manifest/receiver-element`, and
+ `https://developer.android.com/guide/topics/manifest/provider-element` elements.
+
+
+ This attribute is set as a reference to a drawable resource containing
+ the image, such as `"@drawable/icon"`. There is no default icon.
+
+ `android:intentMatchingFlags`
+
+ :
+ Use this attribute to fine-tune how the system matches incoming intents to app
+ components. By default, no special matching rules are applied.
+
+
+ This attribute can be specified on the `` tag
+ as well as on component tags, including ``,
+ ``, ``,
+ ``, and ``. The value
+ set on a component overrides the value set on the
+ `` tag.
+
+
+ The value must be one or more of the following flags, separated by '`|`':
+
+ | Flag | Description |
+ |---|---|
+ | `none` | Disables all special matching rules for incoming intents. When specifying multiple flags, conflicting values are resolved by giving precedence to the `none` flag. |
+ | `enforceIntentFilter` | Enforces stricter matching for incoming intents: - Explicit intents must match the target component's intent filter. - Intents without an action don't match any intent filter. |
+ | `allowNullAction` | Relaxes the matching rules to allow intents without an action to match. This flag is used in conjunction with `enforceIntentFilter` to achieve the following behavior: - Explicit intents must match the target component's intent filter. - Intents without an action are allowed to match any intent filter. |
+
+ For more information, see the
+ [Safer Intents](https://developer.android.com/about/versions/16/behavior-changes-16#safer-intents)
+ section in the Android 16 (API level 36) behavior changes.
+
+ `android:isGame`
+ : Whether the application is a game. The system might group together applications classified
+ as games or display them separately from other applications. The default is `"false"`.
+
+ `android:isMonitoringTool`
+
+ : Indicates that this application is designed to monitor other individuals.
+
+ **Note:** If an app declares this attribute in its manifest, the developer must
+ follow the
+ [Stalkerware](https://support.google.com/googleplay/android-developer/answer/9888380#commercial-spyware)
+ policy to publish the app to Google Play.
+
+ There is no default value. The developer must specify one of the following values:
+
+ | Value | Description |
+ |---|---|
+ | `"parental_control"` | App caters to parental control and is specifically targeted at parents who want to keep their kids safe. |
+ | `"enterprise_management"` | App caters to enterprises that want to manage and track devices given to employees. |
+ | `"other"` | App caters to a use case not otherwise specified in this table. |
+
+ `android:killAfterRestore`
+
+ : Whether the application terminates after its
+ settings have been restored during a full-system restore operation.
+ Single-package restore operations never cause the application to
+ shut down. Full-system restore operations typically only occur once,
+ when the phone is first set up. Third-party applications don't normally
+ need to use this attribute.
+
+ The default is `"true"`, which means that after the application
+ finishes processing its data during a full-system restore, it terminates.
+
+ `android:largeHeap`
+
+ : Whether the application's processes are created with a large Dalvik heap. This applies to
+ all processes created for the application. It only applies to the first application loaded into a
+ process. If you're using a shared user ID to let multiple applications use a process, they all
+ must use this option consistently to avoid unpredictable results.
+
+ Most apps don't need this and instead focus on reducing their overall memory usage for
+ improved performance. Enabling this also doesn't guarantee a fixed increase in available memory,
+ because some devices are constrained by their total available memory.
+
+ To query the available memory size at runtime, use the methods `https://developer.android.com/reference/android/app/ActivityManager#getMemoryClass()` or `https://developer.android.com/reference/android/app/ActivityManager#getLargeMemoryClass()`.
+
+ `android:label`
+ : A user-readable label for the application as a whole and a default
+ label for each of the application's components. See the individual
+ `label` attributes for the
+ `https://developer.android.com/guide/topics/manifest/activity-element`,
+ `https://developer.android.com/guide/topics/manifest/activity-alias-element`,
+ `https://developer.android.com/guide/topics/manifest/service-element`,
+ `https://developer.android.com/guide/topics/manifest/receiver-element`, and
+ `https://developer.android.com/guide/topics/manifest/provider-element` elements.
+
+
+ The label is set as a reference to a string resource, so that
+ it can be localized like other strings in the user interface.
+ However, as a convenience while you're developing the application,
+ it can also be set as a raw string.
+
+ `android:localeConfig`
+
+ : A reference to an XML resource that specifies the list of locales
+ supported by the application. This is used by the system to support per-app
+ language preferences.
+
+ For more information, see the guide on
+ [per-app language preferences](https://developer.android.com/guide/topics/resources/app-languages).
+
+ `android:logo`
+ : A logo for the application as whole and the default logo for activities.
+ This attribute is set as a reference to a drawable resource containing
+ the image, such as `"@drawable/logo"`. There is no default logo.
+
+ `android:manageSpaceActivity`
+ : The fully qualified name of an `Activity` subclass that the system
+ launches to let users manage the memory occupied by the application
+ on the device. The activity is also declared with an
+ `https://developer.android.com/guide/topics/manifest/activity-element` element.
+
+ `android:name`
+ : The fully qualified name of an `https://developer.android.com/reference/android/app/Application`
+ subclass implemented for the application. When the application process
+ is started, this class is instantiated before any of the application's
+ components.
+
+
+ The subclass is optional. Most applications don't need one.
+ In the absence of a subclass, Android uses an instance of the base
+ `Application` class.
+
+ `android:networkSecurityConfig`
+
+ : Specifies the name of the XML file that contains your application's
+ [Network security
+ configuration](https://developer.android.com/training/articles/security-config). The value is a reference to the XML resource file
+ containing the configuration.
+
+ This attribute was added in API level 24.
+
+ `android:pageSizeCompat`
+
+ : Overrides the user or platform compatibility settings for 16 KB page
+ sizes, which lets you force page-agnostic compatibility mode on or off for
+ this application.
+
+ For more information, see the guide on
+ [supporting 16 KB page sizes](https://developer.android.com/guide/practices/page-sizes).
+
+ `android:permission`
+ : The name of a permission that clients need in order to interact
+ with the application. This attribute is a convenient way to set a
+ permission that applies to all of the application's components. It is
+ overwritten by setting the `permission` attributes of individual
+ components.
+
+
+ For more information about permissions, see the
+ [Permissions](https://developer.android.com/guide/topics/manifest/manifest-intro#perms)
+ section in the app manifest overview and
+ [Security tips](https://developer.android.com/guide/topics/security/security).
+
+ `android:persistent`
+ : Whether the application remains running at all times. It's
+ `"true"` if it does and `"false"` if not. The default value
+ is `"false"`. Applications don't normally set this flag.
+ Persistence mode is intended only for certain system applications.
+
+ `android:process`
+ : The name of a process where all components of the application run.
+ Each component can override this default by setting its own `process`
+ attribute.
+
+
+ By default, Android creates a process for an application when the first
+ of its components needs to run. All components then run in that process.
+ The name of the default process matches the package name set by the
+ `https://developer.android.com/guide/topics/manifest/manifest-element` element.
+
+
+ By setting this attribute to a process name that's shared with another
+ application, you can arrange for components of both applications to run in
+ the same process, but only if the two applications also share a
+ user ID and are signed with the same certificate.
+
+
+ If the name assigned to this attribute begins with a colon (`:`), a new
+ process, private to the application, is created when it's needed.
+ If the process name begins with a lowercase character, a global process
+ of that name is created. A global process can be shared with other
+ applications, reducing resource usage.
+
+ `android:restoreAnyVersion`
+ : Indicates that the application is prepared to attempt a restore of any
+ backed-up data set, even if the backup was stored by a newer version
+ of the application than is currently installed on the device. Setting
+ this attribute to `"true"` lets the Backup Manager
+ - `$1`
+ attempt a restore even when a version mismatch suggests that the data is incompatible. *Use with caution!*
+ - The default value of this attribute is `"false"`.
+
+ `android:requestLegacyExternalStorage`
+
+ : Whether the application wants to opt out of
+ [scoped storage](https://developer.android.com/training/data-storage/files/external-scoped).
+
+ **Note:** Depending on changes related to policy or app
+ compatibility, the system might not honor this opt-out request.
+
+ `android:requiredAccountType`
+ : Specifies the account type required by the application to function.
+ If your app requires an `https://developer.android.com/reference/android/accounts/Account`, the value for this attribute must
+ correspond to the account authenticator
+ type used by your app, as defined by `https://developer.android.com/reference/android/accounts/AuthenticatorDescription`,
+ such as `"com.google"`.
+
+ The default value is null and indicates that the application
+ can work *without* any accounts.
+
+
+ Because restricted profiles
+ can't add accounts, specifying this attribute makes your app
+ unavailable from a restricted profile unless you also declare
+ [`android:restrictedAccountType`](https://developer.android.com/guide/topics/manifest/application-element#restrictedAccountType) with
+ the same value.
+
+
+ **Caution:**
+ If the account data might reveal personally identifiable information, it's important
+ that you declare this attribute and leave [`android:restrictedAccountType`](https://developer.android.com/guide/topics/manifest/application-element#restrictedAccountType) null, so that restricted profiles cannot use
+ your app to access personal information that belongs to the owner user.
+
+
+ This attribute was added in API level 18.
+
+ `android:resizeableActivity`
+
+ :
+ Specifies whether the app supports [multi-window mode](https://developer.android.com/guide/topics/ui/multi-window).
+
+ > [!WARNING]
+ > **Warning:** To improve the layout of apps on form factors with smallest width \>= 600dp, the system ignores this attribute for apps that target Android 16 (API level 36). Your app can opt out of the Android 16 behavior, but the opt out will be eliminated in a future release. See [Device compatibility mode](https://developer.android.com/guide/practices/device-compatibility-mode#android_16).
+
+
+ You can set this attribute in either the [``](https://developer.android.com/guide/topics/manifest/activity-element)
+ or `` element.
+
+
+ If you set this attribute to `"true"`, the user can launch the activity in
+ split-screen and free-form modes. If you set the attribute to `"false"`, the app
+ can't be tested or optimized for a multi-window environment. The system can
+ still put the activity in multi-window mode with compatibility mode applied.
+
+
+ Setting this attribute to `"true"` doesn't guarantee that there are no
+ other apps in multi-window mode visible on screen, such as picture-in-picture, or
+ on other displays. Therefore, setting this flag doesn't mean that your app
+ has exclusive resource access.
+
+
+ For apps targeting API level 24 or higher, the default is `"true"`.
+
+
+ If your app targets API level 31 or higher, this attribute works differently on small and large
+ screens:
+
+ - **Large screens (sw \>= 600dp):** all apps support multi-window mode. The attribute indicates whether an app can be resized, not whether the app supports multi-window mode. If `resizeableActivity="false"`, the app is put into compatibility mode when necessary to conform to display dimensions.
+ - **Small screens (sw \< 600dp):** if `resizeableActivity="true"` and the minimum width and minimum height of the main activity are within the multi-window requirements, the app supports multi-window mode. If `resizeableActivity="false"`, the app doesn't support multi-window mode regardless of the activity minimum width and height.
+
+ > [!NOTE]
+ > **Note:**
+ >
+ > - Device manufacturers can override the API level 31 behavior to improve the layout of apps.
+ > - On devices with Android 16 (API level 36) or higher installed, virtual device owners (select trusted and privileged apps) can configure devices they manage to override (ignore) this attribute to improve app layout. See also [Companion app
+ > streaming](https://source.android.com/docs/core/permissions/app-streaming).
+ >
+ > See [Device compatibility mode](https://developer.android.com/guide/practices/device-compatibility-mode).
+
+
+ A task's root activity value is applied to all additional activities launched in the task. That is, if the root
+ activity of a task is resizable, then the system treats all other activities in the task as resizable. If the root
+ activity isn't resizable, the other activities in the task aren't resizable.
+
+
+ This attribute was added in API level 24.
+
+ `android:restrictedAccountType`
+ : Specifies the account type required by this application and indicates that restricted profiles
+ can access such accounts that belong to the owner user. If your app requires an
+ `https://developer.android.com/reference/android/accounts/Account` and restricted profiles *can*
+ access the primary user's accounts, the value for this attribute must
+ correspond to the account authenticator type used by your app, as
+ defined by `https://developer.android.com/reference/android/accounts/AuthenticatorDescription`, such as `"com.google"`.
+
+ The default value is null and indicates that the application can work *without* any
+ accounts.
+
+
+ **Caution:**
+ Specifying this attribute lets restricted profiles use your
+ app with accounts that belong to the owner user, which might reveal personally identifiable
+ information. If the account might reveal personal details, *don't*
+ use this attribute. Instead, declare the [`android:requiredAccountType`](https://developer.android.com/guide/topics/manifest/application-element#requiredAccountType) attribute
+ to make your app unavailable to restricted profiles.
+
+
+ This attribute was added in API level 18.
+
+ `android:supportsRtl`
+
+ : Declares whether your application is willing to support right-to-left (RTL) layouts.
+
+ If this is set to `"true"` and
+ [`targetSdkVersion`](https://developer.android.com/guide/topics/manifest/uses-sdk-element#target)
+ is set to 17 or higher, various RTL APIs are
+ activated and used by the system so your app can display RTL layouts.
+ If this is set to `"false"` or if `targetSdkVersion` is set to 16 or lower, the RTL APIs are ignored
+ or have no effect, and your app behaves the same regardless of the layout
+ direction associated to the user's locale choice. That is, your layouts are always left-to-right.
+
+ The default value of this attribute is `"false"`.
+
+ This attribute was added in API level 17.
+
+ `android:taskAffinity`
+ : An affinity name that applies to all activities within the application,
+ except for those that set a different affinity with their own
+ `https://developer.android.com/guide/topics/manifest/activity-element#aff`
+ attributes. See that attribute for more information.
+
+
+ By default, all activities within an application share the same
+ affinity. The name of that affinity is the same as the package name
+ set by the
+ `https://developer.android.com/guide/topics/manifest/manifest-element` element.
+
+ `android:testOnly`
+ : Indicates whether this application is only for testing purposes. For example,
+ it might expose functionality or data outside of itself that can cause a security
+ hole, but be useful for testing. This kind of APK only installs
+ through `https://developer.android.com/studio/command-line/adb`. You can't publish it to Google Play.
+
+ Android Studio automatically adds this attribute when you click **Run**
+ .
+
+ `android:theme`
+ : A reference to a style resource defining a default theme for all
+ activities in the application. Individual activities can override
+ the default by setting their own `https://developer.android.com/guide/topics/manifest/activity-element#theme`
+ attributes. For more information, see [Styles and themes](https://developer.android.com/guide/topics/ui/themes).
+
+ `android:uiOptions`
+ : Extra options for an activity's UI. Must be one of the following values:
+
+ | Value | Description |
+ |---|---|
+ | `"none"` | No extra UI options. This is the default. |
+ | `"splitActionBarWhenNarrow"` | Adds a bar at the bottom of the screen to display action items in the *app bar* , also known as the *action bar*, when constrained for horizontal space, such as when in portrait mode on a handset. Instead of a small number of action items appearing in the app bar at the top of the screen, the app bar splits into the top navigation section and the bottom bar for action items. This means a reasonable amount of space is available for the action items and for the navigation and title elements at the top. Menu items aren't split across the two bars. They always appear together. |
+
+
+ For more information about the app bar, see [Add the app bar](https://developer.android.com/training/appbar).
+
+ This attribute was added in API level 14.
+
+ `android:usesCleartextTraffic`
+ : Indicates whether the app intends to use cleartext network traffic, such as cleartext HTTP.
+ The default value for apps that target API level 27 or lower is `"true"`. Apps that
+ target API level 28 or higher default to `"false"`.
+
+ **Note:**
+ This attribute is getting deprecated and will be ignored for apps targeting API levels 38 and above.
+ Specify a [Network Security Configuration](https://developer.android.com/training/articles/security-config#CleartextTraffic) to control cleartext traffic for API levels 24 and above.
+ If your app targets API levels 23 and below, you must specify `android:usesCleartextTraffic` in addition to a Network Security Config.
+
+
+ When the attribute is set to `"false"`, platform components, for example, HTTP and FTP
+ stacks, `https://developer.android.com/reference/android/app/DownloadManager`, and
+ `https://developer.android.com/reference/android/media/MediaPlayer`, refuse the
+ app's requests to use cleartext traffic.
+
+ Third-party libraries are strongly encouraged to honor this
+ setting as well. The key reason for avoiding cleartext traffic is the lack of confidentiality,
+ authenticity, and protections against tampering. A network attacker can eavesdrop on transmitted
+ data and also modify it without being detected.
+
+
+ This flag is honored on a best-effort basis because it's impossible to prevent all cleartext
+ traffic from Android applications given the level of access provided to them. For example, there's
+ no expectation that the `https://developer.android.com/reference/java/net/Socket` API honors
+ this flag, because it can't determine whether its traffic is in cleartext.
+
+ However, most
+ network traffic from applications is handled by higher-level network stacks and components, which can
+ honor this flag by either reading it from
+ `https://developer.android.com/reference/android/content/pm/ApplicationInfo#flags`
+ or
+ `https://developer.android.com/reference/android/security/NetworkSecurityPolicy#isCleartextTrafficPermitted()`.
+
+
+ **Note:**
+ `https://developer.android.com/reference/android/webkit/WebView` honors this attribute for
+ applications targeting API level 26 and higher.
+
+
+ During app development, StrictMode can be used to identify any cleartext traffic from the app.
+ For more information, see
+ `https://developer.android.com/reference/android/os/StrictMode.VmPolicy.Builder#detectCleartextNetwork()`.
+
+
+ This attribute was added in API level 23.
+
+
+ This flag is ignored on Android 7.0 (API level 24) and above if an Android Network Security
+ Config is present.
+
+ `android:vmSafeMode`
+ : Indicates whether the app wants the virtual machine (VM) to operate
+ in safe mode. The default value is `"false"`.
+
+ This attribute was added in API level 8, where a value of `"true"`
+ disabled the Dalvik just-in-time (JIT) compiler.
+
+
+ This attribute was adapted in API level 22, where a value of `"true"`
+ disabled the ART ahead-of-time (AOT) compiler.
+
+introduced in:
+: API level 1
+
+see also:
+: `https://developer.android.com/guide/topics/manifest/activity-element`
+
+ `https://developer.android.com/guide/topics/manifest/service-element`
+
+ `https://developer.android.com/guide/topics/manifest/receiver-element`
+
+ `https://developer.android.com/guide/topics/manifest/provider-element`
\ No newline at end of file
diff --git a/identity/restore-credentials/references/android/identity/passkeys/create-passkeys.md b/identity/restore-credentials/references/android/identity/passkeys/create-passkeys.md
new file mode 100644
index 0000000..089cc21
--- /dev/null
+++ b/identity/restore-credentials/references/android/identity/passkeys/create-passkeys.md
@@ -0,0 +1,327 @@
+Before your users can authenticate with passkeys, your app must first register
+or create the passkey for their account.
+
+To create the passkey, obtain details required to create the passkey from your
+app server, and then call the Credential Manager API, which returns a public and
+private key pair. The returned private key is stored in a credential provider,
+such as Google Password Manager, as a passkey. The public key is stored on your
+app server.
+
+> [!TIP]
+> **Tip:** While designing authentication flows with passkeys, make sure that you follow the [UX guidelines for passkeys](https://developer.android.com/design/ui/mobile/guides/patterns/passkeys).
+
+ **Figure 1:**Creation of passkeys
+
+## Prerequisites
+
+Make sure that you have set up [Digital Asset Links](https://developer.android.com/identity/credential-manager/prerequisites) and that you target
+devices running Android 9 (API level 28) or higher.
+
+## Overview
+
+This guide focuses on the changes required in your [relying party client app](https://developer.android.com/identity/credential-manager#authentication-terminology)
+to create a passkey, and gives a brief overview of the [relying party app
+server](https://developer.android.com/identity/credential-manager#authentication-terminology) implementation. To learn more about the server-side integration, see
+[Server-side passkey registration](https://developers.google.com/identity/passkeys/developer-guides/server-registration).
+
+1. [**Add dependencies to your app**](https://developer.android.com/identity/passkeys/create-passkeys#add-dependencies): Add the required Credential Manager libraries.
+2. [**Instantiate Credential Manager**](https://developer.android.com/identity/passkeys/create-passkeys#instantiate): Create a Credential Manager instance.
+3. [**Get credential creation options from the app server**](https://developer.android.com/identity/passkeys/create-passkeys#get-options): From your app server, send the client app the details required to create the passkey, such as information about the app, the user, as well as a `challenge` and other fields.
+4. [**Request a passkey**](https://developer.android.com/identity/passkeys/create-passkeys#request-passkey): In your app, use the details received from the app server to create a [`GetPublicKeyCredentialOption`](https://developer.android.com/reference/androidx/credentials/GetPublicKeyCredentialOption) object and use this object to invoke the `credentialManager.getCredential()` method to create a passkey.
+5. [**Handle the passkey creation response**](https://developer.android.com/identity/passkeys/create-passkeys#handle-response): When you receive the credentials on your client app, you must encode, serialize and then send the public key to the app server. You must also handle each of the exceptions that can occur in case of passkey creation.
+6. [**Verify and save the public key on the server**](https://developer.android.com/identity/passkeys/create-passkeys#verify): Complete the server-side steps to verify the origin of the credential and then save the public key.
+7. [**Notify the user**](https://developer.android.com/identity/passkeys/create-passkeys#notify): Notify the user that their passkey has been created.
+
+> [!TIP]
+> **Tip:** To enhance the user experience during sign up, add functionality to [restore credentials](https://developer.android.com/identity/sign-in/restore-credentials) on a new device to let users seamlessly set up their existing accounts on new Android devices, and [suppressing autofill dialogs on
+> credential fields](https://developer.android.com/identity/passkeys/create-passkeys#suppress-autofill).
+
+## Add dependencies to your app
+
+Add the following dependencies to your app module's `build.gradle` file:
+
+### Kotlin
+
+```kotlin
+dependencies {
+ implementation("androidx.credentials:credentials:1.7.0-alpha03")
+ implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha03")
+}
+```
+
+### Groovy
+
+```groovy
+dependencies {
+ implementation "androidx.credentials:credentials:1.7.0-alpha03"
+ implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha03"
+}
+```
+
+> [!NOTE]
+> **Note:** Use the latest available versions of the dependencies. Versions of [`androidx.credentials`](https://developer.android.com/identity/passkeys/create-passkeys#automatic-upgrade) earlier than version 1.2 throw an `UnsupportedOperationException("Post-U not supported yet")` exception on Android 14.
+
+## Instantiate Credential Manager
+
+Use your app or activity context to create a `CredentialManager` object.
+
+ // Use your app or activity context to instantiate a client instance of
+ // CredentialManager.
+ private val credentialManager = CredentialManager.create(context)
+
+## Get credential creation options from your app server
+
+When the user clicks a "Create Passkey" button or when a new user signs up, make
+a request from your app to your app server to obtain the information required to
+start the passkey registration process.
+
+Use a FIDO-compliant library in your app server to send your client app the
+information required to create a passkey, such as information about the user,
+the app, and additional configuration properties. To learn more, see [Server
+side passkey registration](https://developers.google.com/identity/passkeys/developer-guides/server-registration).
+
+In the client app, decode the public key creation options sent by the app
+server. These are usually represented in JSON format. To learn more about how
+this decoding is done for web clients, see [Encoding and
+Decoding](https://developers.google.com/identity/passkeys/developer-guides/server-registration#encoding_and_decoding). For Android client apps, you must handle the decoding
+separately.
+
+> [!NOTE]
+> **Note:** On your app server, securely store the `challenge` so that you can later verify the origin of the credential.
+
+The following snippet shows the structure the public key creation options sent
+by the app server:
+
+ {
+ "challenge": "",
+ "rp": {
+ "name": "",
+ "id": ""
+ },
+ "user": {
+ "id": "",
+ "name": "",
+ "displayName": ""
+ },
+ "pubKeyCredParams": [
+ {
+ "type": "public-key",
+ "alg": -7
+ }
+ ],
+ "attestation": "none",
+ "excludeCredentials": [
+ {
+ "id": "",
+ "type": "public-key"
+ }
+ ],
+ "authenticatorSelection": {
+ "requireResidentKey": true,
+ "residentKey": "required",
+ "userVerification": "required"
+ }
+ }
+
+Key fields in the public key creation options include:
+
+- `challenge`: A server-generated random string that is used to prevent replay attacks.
+- `rp`: Details about the app.
+ - `rp.name`: The app's name.
+ - `rp.id`: The app's domain or subdomain.
+- `user`: Details about the user.
+ - `id`: The user's unique ID. This value must not include personally identifying information, for example, email addresses or usernames. You can use a random, 16-byte value.
+ - `name`: A unique identifier for the account that the user will recognise, such as their email address or username. This will be displayed in the account selector. If using a username, use the same value as in password authentication.
+ - `displayName`: An optional, user-friendly name for the account intended for display in the account selector.
+- `authenticatorSelection`: Details about the device that will be used for authentication.
+ - `authenticatorAttachment`: Indicates the preferred [authenticator](https://www.w3.org/TR/webauthn/#authenticator). The possible values are as follows:
+ - `platform`: This value is used for an authenticator built into the user's device, such as a fingerprint sensor.
+ - `cross-platform`: This value is used for roaming devices such as security keys. It is not typically used in the passkey context.
+ - Unspecified (recommended): Leaving this value unspecified provides users with the flexibility to create passkeys on their preferred devices. In most cases, leaving the parameter unspecified is the best option.
+ - `requireResidentKey`: To create a passkey, set the value of this `Boolean` field to `true`.
+ - `residentKey`: To create a passkey, set the value to `required`.
+ - `userVerification`: Used to specify the requirements for user verification during a passkey registration. The possible values are as follows:
+ - `preferred`: Use this value if you prioritize user experience over protection, such as in environments where user verification causes more friction than protection.
+ - `required`: Use this value if invoking a user verification method available on the device is required.
+ - `discouraged`: Use this value if using a user verification method is discouraged.
+ To learn more about `userVerification`, see [userVerification deep dive](https://web.dev/articles/webauthn-user-verification).
+- `excludeCredentials`: List credential IDs in an [array](https://w3c.github.io/webauthn/#dom-publickeycredentialcreationoptions-excludecredentials) to prevent the creation of a duplicate passkey if one already exists with the same credential provider.
+
+## Create a passkey
+
+After you have parsed the server-side public key creation options, create a
+passkey by wrapping these options in a `CreatePublicKeyCredentialRequest` object
+and calling `createCredential()`.
+
+The `createPublicKeyCredentialRequest` includes the following:
+
+- `requestJson`: The credential creation options sent by the app server.
+- `preferImmediatelyAvailableCredentials`: This is an optional Boolean field that defines whether to only use locally-available or credential provider-synced credentials to fulfill the request, instead of credentials from security keys or [hybrid](https://w3c.github.io/webauthn/#dom-authenticatortransport-hybrid) key flows. The possible usages are as follows:
+ - `false` (default): Use this value if the call to Credential Manager was triggered by an explicit user action.
+ - `true`: Use this value if Credential Manager is opportunistically called, such as when first opening the app.
+ If you set the value to `true` and there are no immediately available credentials, Credential Manager won't show any UI and the request will fail immediately, returning NoCredentialException for get requests and [`CreateCredentialNoCreateOptionException`](https://developer.android.com/reference/kotlin/androidx/credentials/exceptions/CreateCredentialNoCreateOptionException) for create requests.
+- `origin`: This field is automatically set for Android apps. For browsers and similarly privileged apps that need to set `origin`, see [Make Credential
+ Manager calls on behalf of other parties for privileged apps](https://developer.android.com/training/sign-in/privileged-apps).
+- `isConditional`: This is an optional field that defaults to `false`. For more information, see [Automatically create a passkey](https://developer.android.com/identity/passkeys/create-passkeys#automatic-upgrade).
+
+Calling the `createCredential()` function launches Credential Manager's built-in
+bottom sheet UI that prompts the user to use a passkey and to select a
+credential provider and account for storage. However, if `isConditional` is set
+to `true`, the bottom sheet UI does not display, and the passkey is
+automatically created.
+
+### Automatically create a passkey
+
+You can automatically create a passkey for a user after a successful password
+login by setting the `isConditional` parameter to `true` in your
+`CreatePublicKeyCredentialRequest` while creating a passkey. If the user doesn't
+already have a passkey, your app will automatically attempt to create one in the
+background and store it in the user's credential provider, such as Google
+Password Manager. For an example of how this is implemented, see the [public
+sample](https://github.com/android/identity-samples/blob/main/Shrine/app/src/main/java/com/authentication/shrine/ui/AuthenticationScreen.kt#L98).
+ **Figure 2:**Google Password Manager notification
+
+> [!NOTE]
+> **Note:** If a passkey is created automatically, credential providers are responsible for notifying users about a newly created passkey. Google Password Manager notifies users when a passkey is automatically created. However, other credential providers might have their own conditions and notifications for this feature.
+
+## Handle the response
+
+After the user is verified using the device's screen lock, a passkey is created
+and stored in the user's selected credential provider.
+
+The response after you successfully call `createCredential()` is a
+[PublicKeyCredential](https://developer.android.com/jetpack/androidx/releases/credentials) object.
+
+The `PublicKeyCredential` looks as follows:
+
+ {
+ "id": "",
+ "type": "public-key",
+ "rawId": "",
+ "response": {
+ "clientDataJSON": "",
+ "attestationObject": ""
+ },
+ "authenticatorAttachment": "platform"
+ }
+
+In the client app, serialize the object and send it to the app server.
+
+Add code to handle failures as shown in the following snippet:
+
+ fun handleFailure(e: CreateCredentialException) {
+ when (e) {
+ is CreatePublicKeyCredentialDomException -> {
+ // Handle the passkey DOM errors thrown according to the
+ // WebAuthn spec.
+ }
+ is CreateCredentialCancellationException -> {
+ // The user intentionally canceled the operation and chose not
+ // to register the credential.
+ }
+ is CreateCredentialInterruptedException -> {
+ // Retry-able error. Consider retrying the call.
+ }
+ is CreateCredentialProviderConfigurationException -> {
+ // Your app is missing the provider configuration dependency.
+ // Most likely, you're missing the
+ // "credentials-play-services-auth" module.
+ }
+ is CreateCredentialCustomException -> {
+ // You have encountered an error from a 3rd-party SDK. If you
+ // make the API call with a request object that's a subclass of
+ // CreateCustomCredentialRequest using a 3rd-party SDK, then you
+ // should check for any custom exception type constants within
+ // that SDK to match with e.type. Otherwise, drop or log the
+ // exception.
+ }
+ else -> Log.w(TAG, "Unexpected exception type ${e::class.java.name}")
+ }
+ }
+
+## Verify and save the public key on the app server
+
+On the app server, you must verify the public key credential and then [save the
+public key](https://web.dev/articles/passkey-registration#save-credential).
+
+To verify the public key credential's origin, compare it against an allow list
+of approved apps. If a key has an unrecognized origin, reject it.
+
+> [!NOTE]
+> **Note:** An app's origin is based on its unique identity, which is the SHA-256 fingerprint of its signing certificate.
+
+To obtain the app's SHA 256 fingerprint:
+
+1. Print your release app's signing certificate by running the following
+ command in a terminal:
+
+ keytool -list -keystore
+
+ In the response, identify the signing certificate's SHA 256 fingerprint,
+ mentioned as `Certificate fingerprints block` : `SHA256`.
+2. Encode the SHA256 fingerprint with base64url encoding. This Python example
+ demonstrates how to properly encode the fingerprint:
+
+ import binascii
+ import base64
+ fingerprint = '' # your app's SHA256 fingerprint
+ print(base64.urlsafe_b64encode(binascii.a2b_hex(fingerprint.replace(':', ''))).decode('utf8').replace('=', ''))
+
+3. Append `android:apk-key-hash`: to the start of the output from the previous
+ step so that you get something that is similar to the following:
+
+ android:apk-key-hash:
+
+ The result should match with an allowed origin on your app server. If you
+ have multiple signing certificates, such as certificates for debugging and
+ release, or multiple apps, then repeat the process and accept all the
+ origins as valid on the app server.
+
+> [!NOTE]
+> **Note:** When you save the passkey on the app server, make sure that you save the Authenticator Attestation Globally Unique Identifier ([AAGUID](https://web.dev/articles/webauthn-aaguid)) from the client data. The AAGUID is a unique number that identifies the model of the authenticator. For more information, see [Manage passkeys](https://developer.android.com/identity/passkeys/manage-passkeys).
+
+## Notify the user
+
+After the passkey is successfully created, notify your users about the passkey
+and inform them that they can manage their passkeys from their credential
+provider app or from [within the app settings](https://developer.android.com/identity/passkeys/manage-passkeys). Notify users by using a
+custom dialog, notification, or snackbar. Since an unexpected passkey creation
+by a malicious entity requires an immediate security alert, consider
+supplementing these in-app methods with external communication, such as an
+email.
+
+## Enhance the user experience
+
+To enhance the user experience while implementing sign up with Credential
+Manager, consider adding functionality for restore credentials and suppress
+autofill dialogs.
+
+### Add functionality to restore credentials on a new device
+
+To allow users to seamlessly log into their accounts on a new device, implement
+the [Restore Credentials](https://developer.android.com/identity/sign-in/restore-credentials) functionality. Adding restore credentials with
+`BackupAgent` logs users in when they open your restored app on a new device,
+letting them use your app right away.
+
+### Suppress autofill on credential fields (optional)
+
+For app screens where users are expected to use Credential Manager's bottom
+sheet UI for authentication, add the `isCredential` attribute to the username
+and password fields. This suppresses autofill dialogs (`FillDialog` and
+`SaveDialog`) from overlapping with Credential Manager's bottom sheet UI.
+
+The `isCredential` attribute is supported on Android 14 and higher.
+
+The following example demonstrates how you can add the `isCredential` attribute
+to the relevant username and password fields in the relevant views for your app:
+
+
+
+## Next steps
+
+- [Sign in with passkeys](https://developer.android.com/identity/passkeys/sign-in-with-passkeys)
+- [Manage passkeys](https://developer.android.com/identity/passkeys/manage-passkeys)
+- [Understand passkey user experience flows](https://developer.android.com/design/ui/mobile/guides/patterns/passkeys)
\ No newline at end of file
diff --git a/identity/restore-credentials/references/android/identity/passkeys/sign-in-with-passkeys.md b/identity/restore-credentials/references/android/identity/passkeys/sign-in-with-passkeys.md
new file mode 100644
index 0000000..a6786be
--- /dev/null
+++ b/identity/restore-credentials/references/android/identity/passkeys/sign-in-with-passkeys.md
@@ -0,0 +1,219 @@
+This guide continues on the implementation of using passkeys for authentication.
+Before your users can sign in with passkeys, you must also complete the
+instructions in [Create passkeys](https://developer.android.com/identity/passkeys/create-passkeys).
+
+To authenticate with a passkey, you must first retrieve the options required to
+retrieve the public key from your [app server](https://developer.android.com/identity/credential-manager#authentication-terminology), and then call the Credential
+Manager API to retrieve the public key. Then, handle the sign-in response
+appropriately.
+
+> [!TIP]
+> **Tip:** While designing authentication flows with passkeys, make sure you follow the recommendations in the [UX guidelines for passkeys](https://developer.android.com/design/ui/mobile/guides/patterns/passkeys).
+
+## Overview
+
+This guide focuses on the changes required in your client app to sign in your
+user with a passkey, and gives a brief overview of the app server-side
+implementation. To learn more about server-side integration, see [Server-side
+passkey authentication](https://developers.google.com/identity/passkeys/developer-guides/server-authentication).
+
+To retrieve all the passkey and password options that are associated with the
+user's account, complete these steps:
+
+1. [**Get credential request options from the server**](https://developer.android.com/identity/passkeys/sign-in-with-passkeys#get-options): Make a request from your app to your authentication server to start the passkey sign-in process. From the server, send the options required to get the public key credential, as well as a unique challenge.
+2. [**Create the object required to get the public key credential**](https://developer.android.com/identity/passkeys/sign-in-with-passkeys#create-object): Wrap the options sent by the server in a `GetPublicKeyCredentialOption` object
+3. ([**optional) Prepare getCredential**](https://developer.android.com/identity/passkeys/sign-in-with-passkeys#reduce-latency): In Android 14 and higher, you can reduce latency by showing the account selector by using the `prepareGetCredential()` method before calling `getCredential()`.
+4. [**Launch the sign in flow**](https://developer.android.com/identity/passkeys/sign-in-with-passkeys#launch-sign-in): Call `getCredential()` method to sign in the user
+5. [**Handle the response**](https://developer.android.com/identity/passkeys/sign-in-with-passkeys#handle-response): Handle each of the possible credential responses.
+6. [**Handle exceptions**](https://developer.android.com/identity/passkeys/sign-in-with-passkeys#handle-exceptions): Make sure that you handle exceptions appropriately.
+
+> [!TIP]
+> **Tip:** Enhance the user experience during sign in by also adding functionality to [restore credentials](https://developer.android.com/identity/sign-in/restore-credentials) on a new device to let users seamlessly set up their existing accounts on new Android devices.
+
+## Get credential request options from the server
+
+Request the server for the options required to get the public key credentials,
+as well as the `challenge`, which is unique for each sign-in attempt. To learn
+more about the server-side implementation, see [Create the
+challenge](https://developers.google.com/identity/passkeys/developer-guides/server-authentication#create_the_challenge) and [Create credential request
+options](https://developers.google.com/identity/passkeys/developer-guides/server-authentication#create_credential_request_options).
+
+The options look similar to the following:
+
+ {
+ "challenge": "",
+ "allowCredentials": [],
+ "rpId": ""
+ }
+
+To learn more about the fields, see the blogpost about [signing in with a
+passkey](https://web.dev/articles/passkey-form-autofill#fetch_information_from_the_backend).
+
+> [!TIP]
+> **Tip:** To reduce wait times in the UI because of as network calls, fetch the details required from the app server at the beginning of the user's app session.
+
+## Create the object required to get the public key credential
+
+In your app, use the options to create a `GetPublicKeyCredentialOption` object.
+In the following example, `requestJson` represents the options sent by the
+server.
+
+ // Get password logins from the credential provider on the user's device.
+ val getPasswordOption = GetPasswordOption()
+
+ // Get passkeys from the credential provider on the user's device.
+ val getPublicKeyCredentialOption = GetPublicKeyCredentialOption(
+ requestJson = requestJson
+ )
+
+Then, wrap `GetPublicKeyCredentialOption` in a `GetCredentialRequest` object.
+
+ val credentialRequest = GetCredentialRequest(
+ // Include all the sign-in options that your app supports.
+ listOf(getPasswordOption, getPublicKeyCredentialOption),
+ // Defines whether you prefer to use only immediately available
+ // credentials or hybrid credentials.
+ preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials
+ )
+
+> [!NOTE]
+> **Note:** [`GetCredentialRequest`](https://developer.android.com/reference/androidx/credentials/GetCredentialRequest#GetCredentialRequest) can include an `origin` field. However, `origin` is automatically set for Android apps, and hence should be left as null. For browsers and similarly privileged apps that need to set `origin`, see [Make Credential Manager calls on behalf of other parties for privileged
+> apps](https://developer.android.com/training/sign-in/privileged-apps).
+
+## Optional: Reduce sign-in latency
+
+On Android 14 or higher, you can reduce latency when showing the account
+selector by using the [`prepareGetCredential()`](https://developer.android.com/reference/androidx/credentials/CredentialManager#prepareGetCredential) method before calling
+`getCredential()`.
+
+The `prepareGetCredential()` method returns a
+[`PrepareGetCredentialResponse`](https://developer.android.com/reference/androidx/credentials/PrepareGetCredentialResponse) object which is cached. This lets the
+`getCredential()` method in the following step bring up the account selector
+with the cached data.
+
+ coroutineScope {
+ val response = credentialManager.prepareGetCredential(
+ GetCredentialRequest(
+ listOf(
+ // Include all the sign-in options that your app supports
+ getPublicKeyCredentialOption,
+ getPasswordOption
+ )
+ )
+ )
+ }
+
+## Launch the sign-in flow
+
+Call the `getCredential()` method to show the user the account selector. Use the
+following code snippet as a reference for how to launch the sign-in flow:
+
+> [!NOTE]
+> **Note:** To avoid a potential memory leak, pass in a `MutableContextWrapper` of your foreground `Activity` to the `getCredential()` call. Credential Manager handles updating the context during `Activity` reconstruction.
+
+ // Use an activity-based context to avoid undefined system UI
+ // launching behavior.
+ val context = MutableContextWrapper(activityContext)
+ coroutineScope {
+ try {
+ result = credentialManager.getCredential(
+ // Use MutableContextWrapper to avoid memory leak during configuration changes
+ context = context,
+ request = credentialRequest
+ )
+ handleSignIn(result)
+ } catch (e: GetCredentialException) {
+ // Handle failure
+ }
+ }
+
+> [!NOTE]
+> **Note:** If you call `getCredential()` but the user does not have any credentials available, or if they don't grant consent to using their credentials, a `NoCredentialException` is returned. To learn more about this exception, see the [troubleshooting guide](https://developer.android.com/identity/sign-in/credential-manager-troubleshooting-guide).
+
+## Handle the response
+
+Handle the response, which can contain one of various types of credential
+objects.
+
+ fun handleSignIn(result: GetCredentialResponse) {
+ // Handle the successfully returned credential.
+ val credential = result.credential
+
+ when (credential) {
+ is PublicKeyCredential -> {
+ val responseJson = credential.authenticationResponseJson
+ // Share responseJson i.e. a GetCredentialResponse on your server to
+ // validate and authenticate
+ }
+
+ is PasswordCredential -> {
+ val username = credential.id
+ val password = credential.password
+ // Use id and password to send to your server to validate
+ // and authenticate
+ }
+
+ is CustomCredential -> {
+ // If you are also using any external sign-in libraries, parse them
+ // here with the utility functions provided.
+ if (credential.type == ExampleCustomCredential.TYPE) {
+ try {
+ val ExampleCustomCredential =
+ ExampleCustomCredential.createFrom(credential.data)
+ // Extract the required credentials and complete the authentication as per
+ // the federated sign in or any external sign in library flow
+ } catch (e: ExampleCustomCredential.ExampleCustomCredentialParsingException) {
+ // Unlikely to happen. If it does, you likely need to update the dependency
+ // version of your external sign-in library.
+ Log.e(TAG, "Failed to parse an ExampleCustomCredential", e)
+ }
+ } else {
+ // Catch any unrecognized custom credential type here.
+ Log.e(TAG, "Unexpected type of credential")
+ }
+ }
+ else -> {
+ // Catch any unrecognized credential type here.
+ Log.e(TAG, "Unexpected type of credential")
+ }
+ }
+ }
+
+The `PublicKeyCredential` returned from authentication is essentially a signed
+assertion, structured as follows:
+
+ {
+ "id": "",
+ "type": "public-key",
+ "rawId": "",
+ "response": {
+ "clientDataJSON": "",
+ "authenticatorData": "",
+ "signature": "",
+ "userHandle": ""
+ }
+ }
+
+On the server, you must verify the credential. To learn more, see [Verify and
+sign in the user](https://developers.google.com/identity/passkeys/developer-guides/server-authentication#verify_and_sign_in_the_user).
+
+## Handle exceptions
+
+You should handle all the subclass exceptions of [`GetCredentialException`](https://developer.android.com/reference/androidx/credentials/exceptions/GetCredentialException).
+To learn how to handle each exception, see the [troubleshooting guide](https://developer.android.com/identity/sign-in/credential-manager-troubleshooting-guide).
+
+ coroutineScope {
+ try {
+ result = credentialManager.getCredential(
+ context = activityContext,
+ request = credentialRequest
+ )
+ } catch (e: GetCredentialException) {
+ Log.e("CredentialManager", "No credential available", e)
+ }
+ }
+
+## Next steps
+
+- [Manage passkeys](https://developer.android.com/identity/passkeys/manage-passkeys)
+- [Understand passkey user experience flows](https://developer.android.com/design/ui/mobile/guides/patterns/passkeys)
\ No newline at end of file
diff --git a/identity/verified-email/SKILL.md b/identity/verified-email/SKILL.md
new file mode 100644
index 0000000..31a75d0
--- /dev/null
+++ b/identity/verified-email/SKILL.md
@@ -0,0 +1,431 @@
+---
+name: verified-email
+description: Provides a complete workflow for implementing verified email retrieval
+ on Android Credential Manager API. Use this skill to integrate a secure, OTP-less
+ email verification flow into an Android app. This skill solves the problem of high-friction
+ sign-up processes by leveraging cryptographically verified credentials from trusted
+ providers like Google.
+license: Complete terms in LICENSE.txt
+metadata:
+ author: Google LLC
+ last-updated: '2026-09-16'
+ keywords:
+ - implementation
+ - Android
+ - Credential Manager
+ - Digital Credentials
+ - Verified Email
+ - OpenID4VP
+ - SD-JWT
+ - OTP-less
+ - authentication
+ - passkeys
+ - CredMan
+ - identity.
+---
+
+## Fundamentals
+
+- *[Overview of Digital Credentials](references/android/identity/digital-credentials/email-verification.md)*: Learn about cryptographically verifiable documents and the role of Credential Manager.
+- *[Glossary](references/android/identity/digital-credentials/email-verification-implementation.md)* : Definitions for `dcql_query`, `UserInfoCredential`, and `GetDigitalCredentialOption`.
+
+### Standards \& Examples
+
+- *[OpenID4VP Standard](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#name-introduction)*: The specification used to create digital credentials requests.
+- *[Digital Credentials Demo](https://digital-credentials.dev/)*: Example requests and cross-platform testing tool.
+- *[W3C Verifiable Credentials](https://www.w3.org/TR/vc-data-model-2.0/)*: The data model for cryptographically secured claims.
+- *[SD-JWT](https://datatracker.ietf.org/doc/draft-ietf-oauth-selective-disclosure-jwt/)*: Selective Disclosure JSON Web Token format used for responses.
+- *[mdoc](https://www.iso.org/standard/69084.html)*: ISO/IEC 18013-5 standard for mobile documents.
+
+### Requirements
+
+- **SDK Version**: Minimum SDK 28 (Android 9) is required.
+- **GMS Version**: Google Play services version 25.49.x or higher.
+
+### Use Cases
+
+Email verification is applicable for the following use cases:
+
+- **Account Creation/Sign-up**: Remove friction by skipping manual email verification.
+- **Account Recovery**: Securely verify email ownership during recovery flows.
+- **Re-authentication**: Versatile verification for high-risk actions, independent of the initial sign-in method.
+
+### Limitations \& Nuances
+
+- **Workspace Accounts**: Google does not issue verifiable credentials for Google Workspace Accounts.
+- **Freshness**: For non-@gmail.com addresses, Google verifies the email at account creation but there is no freshness claim; implement an additional challenge like an OTP.
+
+### Scope \& Pre-requisites
+
+**Crucial** : This skill focuses exclusively on the **Android client-side
+integration** . It does **not** implement the app's server-side cryptographic
+validation logic. Server-side validation of the returned credential is required
+for security and must be implemented in your backend.
+
+## Codebase exploration for Use Cases
+
+Get started with the following queries in project source code to find relevant
+screens with different use cases to implement verified email:
+
+- `SignUpScreen`
+- `"Email address"`
+- `"Recover Account"`
+- `"Account Recovery"`
+- `"Forgot password?"`
+- `"Delete Account"`
+
+## Identifying Integration Points
+
+To implement this feature effectively, you must first locate the relevant
+flows in your codebase. To initiate, start with the following strategies to
+cater to different use cases using verified email:
+
+### 1. Search for Navigation Routes
+
+If your app uses Navigation, search for routes or destinations related
+to authentication:
+
+Look for:
+
+- **Keywords** : `signup`, `registration`, `create_account`, `forgot_password`, `recovery`, `verify_email`.
+- **Code Pattern** : Search for `NavHost` or `composable` destinations using these strings.
+
+### 2. Locate Authentication ViewModels
+
+Find the business logic handling user attributes and account creation, account
+recovery:
+
+- **Keywords** : `SignUpViewModel`, `AuthViewModel`, `RegistrationRepository`.
+- **Code Pattern** : Look for methods like `onCreateAccount`, `onRecoverAccount`, or `validateEmail`.
+
+### 3. Find instances of reauthentication for sensitive actions
+
+For reauthentication use cases, find areas where users perform sensitive
+actions:
+
+- **Keywords** : `ChangePassword`, `UpdatePayment`, `DeleteAccount`, `UpdateDetails`, `EditUserDetails`
+
+## Important pointers for Implementation
+
+- Construct a Digital Credential Request and present it to the user.
+- Make sure to follow the request JSON structure as mentioned in [documentation](references/android/identity/digital-credentials/email-verification-implementation.md).
+- While presenting the request to the user, check if result credential is DigitalCredential and credential.credentialJson as responseJsonString
+- Parse the response from the client.
+- Offer a passkey creation option if one is not already present.
+- Assume a local `SdJwtParser` to parse raw SD-JWT and return a `JSONObject`.
+- Use a `VerifiedUserInfo` data class to store the parsed name and email.
+- Leave a TODO for developers to handle the app's server-side validation and parsing.
+- Direct users to the home screen after API call success and show a snackbar with user details for reference purpose only.
+
+## Overview
+
+This guide describes how to implement verified email retrieval using the
+[Digital Credentials Verifier API](references/android/identity/digital-credentials/credential-verifier.md) through an [OpenID for Verifiable
+Presentations (OpenID4VP)](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html) request.
+
+## Add dependencies
+
+In your app's `build.gradle` file, add the following dependencies for Credential
+Manager:
+
+### Kotlin
+
+```kotlin
+dependencies {
+ implementation("androidx.credentials:credentials:1.7.0-alpha03")
+ implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha03")
+}
+```
+
+### Groovy
+
+```groovy
+dependencies {
+ implementation "androidx.credentials:credentials:1.7.0-alpha03"
+ implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha03"
+}
+```
+
+## Initialize Credential Manager
+
+Use your app or activity context to create a `CredentialManager` object.
+
+ // Use your app or activity context to instantiate a client instance of
+ // CredentialManager.
+ private val credentialManager = CredentialManager.create(context)
+
+## Construct the Digital Credential request
+
+To request a verified email, construct a [`GetCredentialRequest`](https://developer.android.com/reference/android/credentials/GetCredentialRequest)
+containing a [`GetDigitalCredentialOption`](https://developer.android.com/reference/androidx/credentials/GetDigitalCredentialOption). This option requires a
+`requestJson` string formatted as an OpenID for Verifiable Presentations
+(OpenID4VP) request.
+
+The OpenID4VP request JSON must follow a specific structure. The current
+providers support a JSON structure with an outer `"digital": {"requests":
+[...]}` wrapper.
+
+ val nonce = generateSecureRandomNonce()
+
+ // This request follows the OpenID4VP spec
+ val openId4vpRequest = """
+ {
+ "requests": [
+ {
+ "protocol": "openid4vp-v1-unsigned",
+ "data": {
+ "response_type": "vp_token",
+ "response_mode": "dc_api",
+ "nonce": "$nonce",
+ "dcql_query": {
+ "credentials": [
+ {
+ "id": "user_info_query",
+ "format": "dc+sd-jwt",
+ "meta": {
+ "vct_values": ["UserInfoCredential"]
+ },
+ "claims": [
+ {"path": ["email"]},
+ {"path": ["name"]},
+ {"path": ["given_name"]},
+ {"path": ["family_name"]},
+ {"path": ["picture"]},
+ {"path": ["hd"]},
+ {"path": ["email_verified"]}
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+ """
+
+ val getDigitalCredentialOption = GetDigitalCredentialOption(requestJson = openId4vpRequest)
+ val request = GetCredentialRequest(listOf(getDigitalCredentialOption))
+
+The request contains the following key information:
+
+- **DCQL query** : The `dcql_query` specifies the credential type and the
+ claims being requested (`email_verified`). You can request other claims to
+ determine the level of verification. A few possible claims are as follows:
+
+ - `email_verified`: In the response, this is a Boolean that indicates whether the email is verified.
+ - `hd` (hosted domain): In the response, this is empty.
+
+ > [!NOTE]
+ > **Note:** If `email_verified` is `true` and `hd` is empty in the response, it implies that the account is an authorized Google Account. Google does not issue [verifiable credentials](references/android/identity/digital-credentials/index.md) for Google Workspace Accounts. However, the `hd` field is present in verifiable credentials issued for non-workspace accounts. You are encouraged to implement handling this field to future-proof your app. If the email is non-@gmail.com, Google verified this email when the Google Account was created, but there is no freshness claim. Therefore, for non-Google emails, you should consider an additional challenge, such as an OTP, to verify the user. To understand the schema of the credential and the specific rules for validating fields like `email_verified`, refer to the [Google
+ > Identity guides](https://developers.google.com/identity/gsi/web/guides/verify-google-id-token).
+
+- **nonce**: A unique, cryptographically secure random value is generated for
+ each request. This is critical for security, as it prevents replay attacks.
+
+- `UserInfoCredential`: This value implies a specific type of digital
+ credential that contains user attributes. Including this in the request is
+ pivotal to distinguish the email verification use case.
+
+Next, wrap the `openId4vpRequest` JSON in a `GetDigitalCredentialOption`, create
+a `GetCredentialRequest`, and call `getCredential()`.
+
+> [!NOTE]
+> **Note:** The `hd` and `email_verified` fields are hidden from users in Credential Manager's built-in UI. You cannot make a request with only these hidden fields- in case of such requests, the response is the [`GetCredentialCancellationException`](https://developer.android.com/reference/kotlin/androidx/credentials/exceptions/GetCredentialCancellationException).
+
+## Present the request to the user
+
+Present the user with the request, using the Credential Manager built-in UI.
+
+ coroutineScope {
+ try {
+ // Requesting Digital Credential from user...
+ val result = credentialManager.getCredential(activity, request)
+
+ when (val credential = result.credential) {
+ is DigitalCredential -> {
+ val responseJsonString = credential.credentialJson
+
+ // Successfully received digital credential response.
+
+ // Next, parse this response and send it to your server.
+ // ...
+ }
+
+ else -> {
+ // handle Unexpected State() - Up to the developer
+ }
+ }
+ } catch (e: Exception) {
+ // handle exceptions - Up to the developer
+ }
+ }
+
+> [!NOTE]
+> **Note:** There is no equivalent of Sign in with Google's `preferImmediatelyAvailableCredentials` for Digital Credentials. If no verifiable credential is found (for example, no eligible account on device), the user will be shown a "No options available" or similar system screen.
+
+## Parse the response on the client
+
+> [!WARNING]
+> **Warning:** From August 2026, the [response JSON](https://developer.android.com/reference/androidx/credentials/DigitalCredential#getCredentialJson()) format has been updated to strictly match the W3C standards. It contains `data` and `protocol` keys, with the OpenID4VP `vp_token` nested in `data`, while legacy formats hold the `vp_token` directly. Ensure your client-side parsing and server-side validation handle both formats during the transition period, while the older implementation is phased out. Apps that begin to integrate the email verification flow after August 2026 need to use the new format only.
+
+After receiving the response, you can perform a preliminary parse on the client.
+This is useful for immediately updating the UI, for example, by showing the
+user's name.
+
+> [!IMPORTANT]
+> **Important:** This step is not for validation. Full cryptographic verification must be performed on your server.
+
+The following code extracts the raw [Selective Disclosure JWT
+(SD-JWT)](https://datatracker.ietf.org/doc/rfc9901/) and uses a helper to decode its claims.
+
+ // 1. Parse the outer JSON wrapper to get the `vp_token`
+ val responseData = JSONObject(responseJsonString)
+ val dataObject = responseData.getJSONObject("data")
+ val vpToken = dataObject.getJSONObject("vp_token")
+
+ // 2. Extract the raw SD-JWT string
+ val credentialId = vpToken.keys().next()
+ val rawSdJwt = vpToken.getJSONArray(credentialId).getString(0)
+
+ // 3. Use your parser to get the verified claims
+ // Server-side validation/parsing is highly recommended.
+
+ // Assumes a local parser like the one in our SdJwtParser.kt sample
+ val claims = SdJwtParser.parse(rawSdJwt)
+ Log.d("TAG", "Parsed Claims: ${claims.toString(2)}")
+
+ // 4. Create your VerifiedUserInfo object with REAL data
+ val userInfo = VerifiedUserInfo(
+ email = claims.getString("email"),
+ displayName = claims.optString("name", claims.getString("email"))
+ )
+
+## Handle the response
+
+The Credential Manager API will return a [`DigitalCredential`](https://developer.android.com/reference/androidx/credentials/DigitalCredential)
+response.
+
+The following is an example of what the raw `responseJsonString` looks like, and
+what the claims look like after parsing the inner SD-JWT where you get
+additional metadata as well along with verified email:
+
+ /*
+ // Example of the raw JSON response from credential.credentialJson:
+ {
+ "protocol": "openid4vp-v1-unsigned",
+ "data": {
+ "vp_token": {
+ // This key matches the 'id' you set in your dcql_query
+ "user_info_query": [
+ // The SD-JWT string (Issuer JWT ~ Disclosures ~ Key Binding JWT)
+ "eyJhbGciOiJ...~WyI...IiwgImVtYWlsIiwgInVzZXJAZXhhbXBsZS5jb20iXQ~...~eyJhbGciOiJ..."
+ ]
+ }
+ }
+ }
+
+ // Example of the parsed and verified claims from the SD-JWT on your server:
+ {
+ "cnf": {
+ "jwk": {..}
+ },
+ "exp": 1775688222,
+ "iat": 1775083422,
+ "iss": "https://verifiablecredentials-pa.googleapis.com",
+ "vct": "UserInfoCredential",
+ "email": "jane.doe.246745@gmail.com",
+ "email_verified": true,
+ "given_name": "Jane",
+ "family_name": "Doe",
+ "name": "Jane Doe",
+ "picture": "http://example.com/janedoe/me.jpg",
+ "hd": ""
+ }
+ */
+
+> [!IMPORTANT]
+> **Important:** We highly recommend that after receiving the verified email, you trigger Credential Manager's [passkey creation](https://developer.android.com/identity/credential-manager/passkeys/create-passkeys).
+
+## Server-side validation for account creation
+
+Since the retrieved email is cryptographically verified, you can omit the email
+OTP verification step, significantly reducing sign-up friction and potentially
+increasing conversion. This process is best handled on your server. The client
+sends the raw response (containing the `vp_token`) and the original nonce to a
+new server endpoint.
+
+For verification, your application must send the full `responseJsonString` to
+your server for cryptographic validation before creating an account or logging
+the user in.
+
+The digital credential provides two critical levels of verification for your
+server:
+
+- **Authenticity of the data** : Verifying the issuer (`iss`) URL and the `SD-JWT` signature proves that a trusted authority issued this data.
+- **Identity of the presenter** : Verifying the `cnf` field and the Key Binding (`kb`) signature confirms that the credential is being shared by the same device it was originally issued to, preventing it from being intercepted or used on another device.
+
+The validation on the server must achieve the following:
+
+- **Verify issuer** : Ensure the `iss` (issuer) field matches `https://verifiablecredentials-pa.googleapis.com`.
+- **Verify signature**: Check the signature of the SD-JWT using the public keys (JWKs) available at https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks.
+
+> [!NOTE]
+> **Note:** Use a standard library (such as [@sd-jwt/sd-jwt-vc](https://datatracker.ietf.org/doc/rfc9901/) for Node.js) to perform the verification steps as outlined in the [OpenID for Verifiable
+> Presentations specification](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html).
+
+For full security, make sure that you also validate the `nonce` to prevent
+replay attacks.
+
+By combining these steps, your server can validate both the authenticity of the
+data and the identity of the presenter, ensuring the credential wasn't
+intercepted or spoofed before provisioning the new account.
+
+> [!WARNING]
+> **Warning:** As mentioned in [Parse the response on the client](#parse-response), from August 2026, the [response JSON](https://developer.android.com/reference/androidx/credentials/DigitalCredential#getCredentialJson()) format has been updated to match W3C standards. Ensure your client-side parsing and server-side validation handle both formats during the transition period, while the older implementation is phased out. Apps that begin to integrate the email verification flow after August 2026 need to use the new format only.
+
+ try {
+ // Send the raw credential response and the original nonce to your server.
+ // Your server must validate the response. createAccountWithVerifiedCredentials
+ // is a custom implementation per each RP for server side verification and account creation.
+ val serverResponse = createAccountWithVerifiedCredentials(responseJsonString, nonce)
+
+ // Server returns the new account info (e.g., email, name)
+ val claims = JSONObject(serverResponse.json)
+
+ val userInfo = VerifiedUserInfo(
+ email = claims.getString("email"),
+ displayName = claims.optString("name", claims.getString("email"))
+ )
+
+ // handle response - Up to the developer
+ } catch (e: Exception) {
+ // handle exceptions - Up to the developer
+ }
+
+## Passkey creation
+
+An optional but highly recommended next step after provisioning an account is to
+immediately [create a passkey](references/android/identity/passkeys/create-passkeys.md) for that account. This provides a secure,
+passwordless method for the user to sign in. This flow is identical to a
+standard passkey registration.
+
+## WebView support
+
+For the flow to work on a [`WebView`](https://developer.android.com/reference/android/webkit/WebView), developers should implement a
+[JavaScript bridge](references/android/identity/sign-in/credential-manager-webview.md) (JS Bridge) to facilitate the handoff. This bridge
+allows the `WebView` object to signal the native app, which can then perform the
+actual call to the Credential Manager API.
+
+## See also
+
+- [Overview of verified email retrieval](references/android/identity/digital-credentials/email-verification.md)
+- [Credential Manager](references/android/identity/credential-manager/index.md)
+
+## Critical Security Guidelines
+
+To maintain the integrity of the email verification flow, the following security
+requirements are mandatory:
+
+- **Server-side Validation** : Never trust claims parsed on the client for security-sensitive operations like account creation. Send the complete, raw `responseJsonString` and the original `nonce` to the app's server for full verification.
+- **Nonce Integrity** : Generate a unique, cryptographically secure nonce for every request and **never** reuse a nonce across multiple requests to prevent replay attacks.
+- **Cryptographic Checks** : The app's server must validate the issuer (`iss`) field, the SD-JWT signature, and the presenter identity using the `cnf` field.
diff --git a/identity/verified-email/references/android/identity/credential-manager/index.md b/identity/verified-email/references/android/identity/credential-manager/index.md
new file mode 100644
index 0000000..c646f19
--- /dev/null
+++ b/identity/verified-email/references/android/identity/credential-manager/index.md
@@ -0,0 +1,67 @@
+[Credential Manager](https://developer.android.com/reference/kotlin/androidx/credentials/package-summary) is the recommended Jetpack API for credential exchange
+in Android apps. The Credential Manager API streamlines credential exchange
+across form factors for use cases spanning authentication and authorization. You
+can also use Credential Manager to handle digital credentials and restore user
+credentials on new Android devices.
+
+## Credential Manager features
+
+Credential Manager streamlines the sign-in process and enhances security by
+offering a robust set of capabilities, including:
+
+- **Support for various authentication mechanisms** : Enables users to sign up or sign in to your app using:
+ - [Passkeys](https://developer.android.com/identity/passkeys)
+ - [Sign-in with Google](https://developer.android.com/identity/sign-in/credential-manager-siwg) and other federated sign-in mechanisms
+ - [Passwords](https://developer.android.com/identity/passwords)
+ - [Digital credentials](https://developer.android.com/identity/digital-credentials)
+- **Cross-device credential restore** : Provides integration with [Restore
+ Credentials](https://developer.android.com/identity/sign-in/restore-credentials), allowing users to seamlessly start using your app on a new device.
+- **Seamless integration with credential providers** : Offers support for [credential providers](https://developer.android.com/identity/sign-in/credential-provider), including password managers such as Google Password Manager.
+- **Credential management**: Enables updating the metadata for user credentials, helping to keep credentials consistent across your app and credential providers.
+- **WebView compatibility** : Works with apps that use [WebView](https://developer.android.com/identity/sign-in/credential-manager-webview).
+- **Autofill integration** : Integrates with [autofill](https://developer.android.com/identity/autofill/credential-manager-autofill) to display credentials within the autofill UI.
+
+## Benefits of using Credential Manager
+
+Adopting Credential Manager provides several key advantages for both your
+application and your users:
+
+- **Enhanced security**: Passkeys offer enhanced security and protect users from phishing attempts.
+- **Support for various form factors** : The APIs work across Android [form
+ factors](https://developer.android.com/identity/form-factors), including mobile, Android XR, and Wear OS devices.
+- **Simplified authentication across use cases** : [Digital credentials](https://developer.android.com/identity/digital-credentials), such as digital driver's licenses, corporate IDs, and national ID cards, enable authentication across various use cases, including those requiring [phone number verification](https://developer.android.com/identity/digital-credentials/phone-number-verification).
+- **Improved user experience**: Credential Manager's unified interface gives users a familiar and consistent experience and improves registration and sign-in speeds. The bottom sheet UI appears inline to your app's content, so your users remain within your app's context during sign-in. The following image shows Credential Manager's built-in UI:
+
+ **Figure 1.**Credential Manager's built-in unified interface for passkeys, passwords, and federated sign-ins
+
+## Authentication terminology
+
+An entity that requires authentication for its users is known as a **relying
+party**. An authentication workflow typically includes the following components:
+
+- **Relying party client app**: The client---in this case, your Android app---that handles the user interface to create and use passkeys.
+- **Relying party server**: An app server that helps with the creation, storage, and verification of passkeys.
+- **Credential provider** : A component that stores and provides user credentials, such as Google Password Manager. Note that the [FIDO documentation](https://fidoalliance.org/passkeys/) refers to credential providers as credential managers.
+
+## Credential storage
+
+Credential providers, such as Google Password Manager, provide a centralized and
+secure location for users to manage credentials across their devices, further
+simplifying the authentication process. To integrate with Credential Manager as
+a credential provider, see
+[Implement authentication as a credential provider](https://developer.android.com/identity/sign-in/credential-provider).
+
+For more information about how Google Password Manager keeps your credentials
+secure, see
+[Security of Passkeys in the Google Password Manager](https://security.googleblog.com/2022/10/SecurityofPasskeysintheGooglePasswordManager.html).
+
+## Migrate to Credential Manager
+
+Credential Manager is intended to replace legacy Android authentication APIs and
+local FIDO2 credentials. For more information about migrating to Credential
+Manager, see the following guides:
+
+- [Google Sign-In](https://developer.android.com/identity/sign-in/legacy-gsi-migration)
+- [One Tap](https://developer.android.com/identity/legacy/one-tap)
+- [Smart Lock for Passwords](https://developer.android.com/identity/sign-in/smart-lock-migration)
+- [FIDO2](https://developer.android.com/identity/sign-in/fido2-migration)
\ No newline at end of file
diff --git a/identity/verified-email/references/android/identity/digital-credentials/credential-verifier.md b/identity/verified-email/references/android/identity/digital-credentials/credential-verifier.md
new file mode 100644
index 0000000..c66bf50
--- /dev/null
+++ b/identity/verified-email/references/android/identity/digital-credentials/credential-verifier.md
@@ -0,0 +1,166 @@
+Digital credential verification within Android apps can be used to authenticate
+and authorize a user's identity (such as a government ID), properties about that
+user (such as a driver's license, academic degree, or attributes such as age or
+address), or other scenarios where a credential needs to be issued and verified
+to assert the authenticity of an entity.
+
+Digital Credentials is a public W3C standard that specifies how to access a
+user's verifiable digital credentials from a digital wallet, and is implemented
+for web use cases with the [W3C Credential Management API](https://www.w3.org/TR/credential-management-1/). On
+Android, Credential Manager's [`DigitalCredential`](https://developer.android.com/reference/kotlin/androidx/credentials/DigitalCredential) API is used for
+verifying digital credentials.
+
+### Android version compatibility
+
+The Verifier API is supported on Android 6 (API level 23) and higher.
+
+### Implementation
+
+To verify digital credentials in your Android project, do the following:
+
+1. Add dependencies to your app's build script and initialize a `CredentialManager` class.
+2. Construct a digital credential request and use it to initialize a `DigitalCredentialOption`, followed by building the `GetCredentialRequest`.
+3. Launch the `getCredential` flow with the constructed request to receive a successful `GetCredentialResponse` or handle any exceptions that may occur. Upon successful retrieval, validate the response.
+
+#### Add dependencies and initialize
+
+Add the following dependencies to your Gradle build script:
+
+ dependencies {
+ implementation("androidx.credentials:credentials:1.6.0-beta01")
+ implementation("androidx.credentials:credentials-play-services-auth:1.6.0-beta01")
+ }
+
+Next, Initialize an instance of the `CredentialManager` class.
+
+ val credentialManager = CredentialManager.create(context)
+
+#### Construct a digital credential request
+
+Construct a digital credential request and use it to initialize a
+`DigitalCredentialOption`.
+
+ // The request in the JSON format to conform with
+ // the JSON-ified Credential Manager - Verifier API request definition.
+ val requestJson = generateRequestFromServer()
+ val digitalCredentialOption =
+ GetDigitalCredentialOption(requestJson = requestJson)
+
+ // Use the option from the previous step to build the `GetCredentialRequest`.
+ val getCredRequest = GetCredentialRequest(
+ listOf(digitalCredentialOption)
+ )
+
+Here is an example of an OpenId4Vp request. A full reference can be found at
+this [website](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html).
+
+ {
+ "requests": [
+ {
+ "protocol": "openid4vp-v1-unsigned",
+ "data": {
+ "response_type": "vp_token",
+ "response_mode": "dc_api",
+ "nonce": "OD8eP8BYfr0zyhgq4QCVEGN3m7C1Ht_No9H5fG5KJFk",
+ "dcql_query": {
+ "credentials": [
+ {
+ "id": "cred1",
+ "format": "mso_mdoc",
+ "meta": {
+ "doctype_value": "org.iso.18013.5.1.mDL"
+ },
+ "claims": [
+ {
+ "path": [
+ "org.iso.18013.5.1",
+ "family_name"
+ ]
+ },
+ {
+ "path": [
+ "org.iso.18013.5.1",
+ "given_name"
+ ]
+ },
+ {
+ "path": [
+ "org.iso.18013.5.1",
+ "age_over_21"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+
+#### Get the credential
+
+Launch the `getCredential` flow with the constructed request. You will receive
+either a successful `GetCredentialResponse`, or a `GetCredentialException` if
+the request fails.
+
+The `getCredential` flow triggers Android system dialogs to present the user's
+available credential options and collect their selection. Next, the wallet app
+that contains the chosen credential option will display UIs to collect consent
+and perform actions needed to generate a digital credential response.
+
+ coroutineScope.launch {
+ try {
+ val result = credentialManager.getCredential(
+ context = activityContext,
+ request = getCredRequest
+ )
+ verifyResult(result)
+ } catch (e : GetCredentialException) {
+ handleFailure(e)
+ }
+ }
+
+ // Handle the successfully returned credential.
+ fun verifyResult(result: GetCredentialResponse) {
+ val credential = result.credential
+ when (credential) {
+ is DigitalCredential -> {
+ val responseJson = credential.credentialJson
+ validateResponseOnServer(responseJson)
+ }
+ else -> {
+ // Catch any unrecognized credential type here.
+ Log.e(TAG, "Unexpected type of credential ${credential.type}")
+ }
+ }
+ }
+
+ // Handle failure.
+ fun handleFailure(e: GetCredentialException) {
+ when (e) {
+ is GetCredentialCancellationException -> {
+ // The user intentionally canceled the operation and chose not
+ // to share the credential.
+ }
+ is GetCredentialInterruptedException -> {
+ // Retry-able error. Consider retrying the call.
+ }
+ is NoCredentialException -> {
+ // No credential was available.
+ }
+ is CreateCredentialUnknownException -> {
+ // An unknown, usually unexpected, error has occurred. Check the
+ // message error for any additional debugging information.
+ }
+ is CreateCredentialCustomException -> {
+ // You have encountered a custom error thrown by the wallet.
+ // If you made the API call with a request object that's a
+ // subclass of CreateCustomCredentialRequest using a 3rd-party SDK,
+ // then you should check for any custom exception type constants
+ // within that SDK to match with e.type. Otherwise, drop or log the
+ // exception.
+ }
+ else -> Log.w(TAG, "Unexpected exception type ${e::class.java}")
+ }
+ }
\ No newline at end of file
diff --git a/identity/verified-email/references/android/identity/digital-credentials/email-verification-implementation.md b/identity/verified-email/references/android/identity/digital-credentials/email-verification-implementation.md
new file mode 100644
index 0000000..4946eb4
--- /dev/null
+++ b/identity/verified-email/references/android/identity/digital-credentials/email-verification-implementation.md
@@ -0,0 +1,315 @@
+## Android skills
+
+[View on GitHub](https://github.com/android/skills/tree/main/identity/verified-email)
+
+### Retrieve verified email
+
+Use an [Android skill](https://developer.android.com/tools/agents/android-skills) to integrate a secure, OTP-less email verification flow into your app. To install the skill from the [Android CLI](https://developer.android.com/tools/agents/android-cli), run:
+
+ android skills add verified-email
+
+
+
+## Overview
+
+This guide describes how to implement verified email retrieval using the
+[Digital Credentials Verifier API](https://developer.android.com/identity/digital-credentials/credential-verifier) through an [OpenID for Verifiable
+Presentations (OpenID4VP)](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html) request.
+
+## Add dependencies
+
+In your app's `build.gradle` file, add the following dependencies for Credential
+Manager:
+
+### Kotlin
+
+```kotlin
+dependencies {
+ implementation("androidx.credentials:credentials:1.7.0-alpha03")
+ implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha03")
+}
+```
+
+### Groovy
+
+```groovy
+dependencies {
+ implementation "androidx.credentials:credentials:1.7.0-alpha03"
+ implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha03"
+}
+```
+
+## Initialize Credential Manager
+
+Use your app or activity context to create a `CredentialManager` object.
+
+ // Use your app or activity context to instantiate a client instance of
+ // CredentialManager.
+ private val credentialManager = CredentialManager.create(context)
+
+## Construct the Digital Credential request
+
+To request a verified email, construct a [`GetCredentialRequest`](https://developer.android.com/reference/android/credentials/GetCredentialRequest)
+containing a [`GetDigitalCredentialOption`](https://developer.android.com/reference/androidx/credentials/GetDigitalCredentialOption). This option requires a
+`requestJson` string formatted as an OpenID for Verifiable Presentations
+(OpenID4VP) request.
+
+The OpenID4VP request JSON must follow a specific structure. The current
+providers support a JSON structure with an outer `"digital": {"requests":
+[...]}` wrapper.
+
+ val nonce = generateSecureRandomNonce()
+
+ // This request follows the OpenID4VP spec
+ val openId4vpRequest = """
+ {
+ "requests": [
+ {
+ "protocol": "openid4vp-v1-unsigned",
+ "data": {
+ "response_type": "vp_token",
+ "response_mode": "dc_api",
+ "nonce": "$nonce",
+ "dcql_query": {
+ "credentials": [
+ {
+ "id": "user_info_query",
+ "format": "dc+sd-jwt",
+ "meta": {
+ "vct_values": ["UserInfoCredential"]
+ },
+ "claims": [
+ {"path": ["email"]},
+ {"path": ["name"]},
+ {"path": ["given_name"]},
+ {"path": ["family_name"]},
+ {"path": ["picture"]},
+ {"path": ["hd"]},
+ {"path": ["email_verified"]}
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+ """
+
+ val getDigitalCredentialOption = GetDigitalCredentialOption(requestJson = openId4vpRequest)
+ val request = GetCredentialRequest(listOf(getDigitalCredentialOption))
+
+The request contains the following key information:
+
+- **DCQL query** : The `dcql_query` specifies the credential type and the
+ claims being requested (`email_verified`). You can request other claims to
+ determine the level of verification. A few possible claims are as follows:
+
+ - `email_verified`: In the response, this is a Boolean that indicates whether the email is verified.
+ - `hd` (hosted domain): In the response, this is empty.
+
+ > [!NOTE]
+ > **Note:** If `email_verified` is `true` and `hd` is empty in the response, it implies that the account is an authorized Google Account. Google does not issue [verifiable credentials](https://developer.android.com/identity/digital-credentials#verifiable-credentials) for Google Workspace Accounts. However, the `hd` field is present in verifiable credentials issued for non-workspace accounts. You are encouraged to implement handling this field to future-proof your app. If the email is non-@gmail.com, Google verified this email when the Google Account was created, but there is no freshness claim. Therefore, for non-Google emails, you should consider an additional challenge, such as an OTP, to verify the user. To understand the schema of the credential and the specific rules for validating fields like `email_verified`, refer to the [Google
+ > Identity guides](https://developers.google.com/identity/gsi/web/guides/verify-google-id-token).
+
+- **nonce**: A unique, cryptographically secure random value is generated for
+ each request. This is critical for security, as it prevents replay attacks.
+
+- `UserInfoCredential`: This value implies a specific type of digital
+ credential that contains user attributes. Including this in the request is
+ pivotal to distinguish the email verification use case.
+
+Next, wrap the `openId4vpRequest` JSON in a `GetDigitalCredentialOption`, create
+a `GetCredentialRequest`, and call `getCredential()`.
+
+> [!NOTE]
+> **Note:** The `hd` and `email_verified` fields are hidden from users in Credential Manager's built-in UI. You cannot make a request with only these hidden fields- in case of such requests, the response is the [`GetCredentialCancellationException`](https://developer.android.com/reference/kotlin/androidx/credentials/exceptions/GetCredentialCancellationException).
+
+## Present the request to the user
+
+Present the user with the request, using the Credential Manager built-in UI.
+
+ coroutineScope {
+ try {
+ // Requesting Digital Credential from user...
+ val result = credentialManager.getCredential(activity, request)
+
+ when (val credential = result.credential) {
+ is DigitalCredential -> {
+ val responseJsonString = credential.credentialJson
+
+ // Successfully received digital credential response.
+
+ // Next, parse this response and send it to your server.
+ // ...
+ }
+
+ else -> {
+ // handle Unexpected State() - Up to the developer
+ }
+ }
+ } catch (e: Exception) {
+ // handle exceptions - Up to the developer
+ }
+ }
+
+> [!NOTE]
+> **Note:** There is no equivalent of Sign in with Google's `preferImmediatelyAvailableCredentials` for Digital Credentials. If no verifiable credential is found (for example, no eligible account on device), the user will be shown a "No options available" or similar system screen.
+
+## Parse the response on the client
+
+> [!WARNING]
+> **Warning:** From August 2026, the [response JSON](https://developer.android.com/reference/androidx/credentials/DigitalCredential#getCredentialJson()) format has been updated to strictly match the W3C standards. It contains `data` and `protocol` keys, with the OpenID4VP `vp_token` nested in `data`, while legacy formats hold the `vp_token` directly. Ensure your client-side parsing and server-side validation handle both formats during the transition period, while the older implementation is phased out. Apps that begin to integrate the email verification flow after August 2026 need to use the new format only.
+
+After receiving the response, you can perform a preliminary parse on the client.
+This is useful for immediately updating the UI, for example, by showing the
+user's name.
+
+> [!IMPORTANT]
+> **Important:** This step is not for validation. Full cryptographic verification must be performed on your server.
+
+The following code extracts the raw [Selective Disclosure JWT
+(SD-JWT)](https://datatracker.ietf.org/doc/rfc9901/) and uses a helper to decode its claims.
+
+ // 1. Parse the outer JSON wrapper to get the `vp_token`
+ val responseData = JSONObject(responseJsonString)
+ val dataObject = responseData.getJSONObject("data")
+ val vpToken = dataObject.getJSONObject("vp_token")
+
+ // 2. Extract the raw SD-JWT string
+ val credentialId = vpToken.keys().next()
+ val rawSdJwt = vpToken.getJSONArray(credentialId).getString(0)
+
+ // 3. Use your parser to get the verified claims
+ // Server-side validation/parsing is highly recommended.
+
+ // Assumes a local parser like the one in our SdJwtParser.kt sample
+ val claims = SdJwtParser.parse(rawSdJwt)
+ Log.d("TAG", "Parsed Claims: ${claims.toString(2)}")
+
+ // 4. Create your VerifiedUserInfo object with REAL data
+ val userInfo = VerifiedUserInfo(
+ email = claims.getString("email"),
+ displayName = claims.optString("name", claims.getString("email"))
+ )
+
+## Handle the response
+
+The Credential Manager API will return a [`DigitalCredential`](https://developer.android.com/reference/androidx/credentials/DigitalCredential)
+response.
+
+The following is an example of what the raw `responseJsonString` looks like, and
+what the claims look like after parsing the inner SD-JWT where you get
+additional metadata as well along with verified email:
+
+ /*
+ // Example of the raw JSON response from credential.credentialJson:
+ {
+ "protocol": "openid4vp-v1-unsigned",
+ "data": {
+ "vp_token": {
+ // This key matches the 'id' you set in your dcql_query
+ "user_info_query": [
+ // The SD-JWT string (Issuer JWT ~ Disclosures ~ Key Binding JWT)
+ "eyJhbGciOiJ...~WyI...IiwgImVtYWlsIiwgInVzZXJAZXhhbXBsZS5jb20iXQ~...~eyJhbGciOiJ..."
+ ]
+ }
+ }
+ }
+
+ // Example of the parsed and verified claims from the SD-JWT on your server:
+ {
+ "cnf": {
+ "jwk": {..}
+ },
+ "exp": 1775688222,
+ "iat": 1775083422,
+ "iss": "https://verifiablecredentials-pa.googleapis.com",
+ "vct": "UserInfoCredential",
+ "email": "jane.doe.246745@gmail.com",
+ "email_verified": true,
+ "given_name": "Jane",
+ "family_name": "Doe",
+ "name": "Jane Doe",
+ "picture": "http://example.com/janedoe/me.jpg",
+ "hd": ""
+ }
+ */
+
+> [!IMPORTANT]
+> **Important:** We highly recommend that after receiving the verified email, you trigger Credential Manager's [passkey creation](https://developer.android.com/identity/credential-manager/passkeys/create-passkeys).
+
+## Server-side validation for account creation
+
+Since the retrieved email is cryptographically verified, you can omit the email
+OTP verification step, significantly reducing sign-up friction and potentially
+increasing conversion. This process is best handled on your server. The client
+sends the raw response (containing the `vp_token`) and the original nonce to a
+new server endpoint.
+
+For verification, your application must send the full `responseJsonString` to
+your server for cryptographic validation before creating an account or logging
+the user in.
+
+The digital credential provides two critical levels of verification for your
+server:
+
+- **Authenticity of the data** : Verifying the issuer (`iss`) URL and the `SD-JWT` signature proves that a trusted authority issued this data.
+- **Identity of the presenter** : Verifying the `cnf` field and the Key Binding (`kb`) signature confirms that the credential is being shared by the same device it was originally issued to, preventing it from being intercepted or used on another device.
+
+The validation on the server must achieve the following:
+
+- **Verify issuer** : Ensure the `iss` (issuer) field matches `https://verifiablecredentials-pa.googleapis.com`.
+- **Verify signature**: Check the signature of the SD-JWT using the public keys (JWKs) available at https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks.
+
+> [!NOTE]
+> **Note:** Use a standard library (such as [@sd-jwt/sd-jwt-vc](https://datatracker.ietf.org/doc/rfc9901/) for Node.js) to perform the verification steps as outlined in the [OpenID for Verifiable
+> Presentations specification](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html).
+
+For full security, make sure that you also validate the `nonce` to prevent
+replay attacks.
+
+By combining these steps, your server can validate both the authenticity of the
+data and the identity of the presenter, ensuring the credential wasn't
+intercepted or spoofed before provisioning the new account.
+
+> [!WARNING]
+> **Warning:** As mentioned in [Parse the response on the client](https://developer.android.com/identity/digital-credentials/email-verification-implementation#parse-response), from August 2026, the [response JSON](https://developer.android.com/reference/androidx/credentials/DigitalCredential#getCredentialJson()) format has been updated to match W3C standards. Ensure your client-side parsing and server-side validation handle both formats during the transition period, while the older implementation is phased out. Apps that begin to integrate the email verification flow after August 2026 need to use the new format only.
+
+ try {
+ // Send the raw credential response and the original nonce to your server.
+ // Your server must validate the response. createAccountWithVerifiedCredentials
+ // is a custom implementation per each RP for server side verification and account creation.
+ val serverResponse = createAccountWithVerifiedCredentials(responseJsonString, nonce)
+
+ // Server returns the new account info (e.g., email, name)
+ val claims = JSONObject(serverResponse.json)
+
+ val userInfo = VerifiedUserInfo(
+ email = claims.getString("email"),
+ displayName = claims.optString("name", claims.getString("email"))
+ )
+
+ // handle response - Up to the developer
+ } catch (e: Exception) {
+ // handle exceptions - Up to the developer
+ }
+
+## Passkey creation
+
+An optional but highly recommended next step after provisioning an account is to
+immediately [create a passkey](https://developer.android.com/identity/passkeys/create-passkeys) for that account. This provides a secure,
+passwordless method for the user to sign in. This flow is identical to a
+standard passkey registration.
+
+## WebView support
+
+For the flow to work on a [`WebView`](https://developer.android.com/reference/android/webkit/WebView), developers should implement a
+[JavaScript bridge](https://developer.android.com/identity/sign-in/credential-manager-webview) (JS Bridge) to facilitate the handoff. This bridge
+allows the `WebView` object to signal the native app, which can then perform the
+actual call to the Credential Manager API.
+
+## See also
+
+- [Overview of verified email retrieval](https://developer.android.com/identity/digital-credentials/email-verification)
+- [Credential Manager](https://developer.android.com/identity/credential-manager)
\ No newline at end of file
diff --git a/identity/verified-email/references/android/identity/digital-credentials/email-verification.md b/identity/verified-email/references/android/identity/digital-credentials/email-verification.md
new file mode 100644
index 0000000..106c2ac
--- /dev/null
+++ b/identity/verified-email/references/android/identity/digital-credentials/email-verification.md
@@ -0,0 +1,158 @@
+This document describes using Credential Manager to get a cryptographically
+verified email address from a user's device. This process removes the need for
+your app users to verify their email with one-time passwords (OTPs) or magic
+links.
+
+This document explains the following areas:
+
+- Android compatibility
+- User experience
+- Accounts supported
+- Implication of email deliverability
+- Comparison with [Sign in with Google](https://developer.android.com/identity/sign-in/credential-manager-siwg)
+
+This guide assumes you are familiar with the following concepts:
+
+- [Credential Manager](https://developer.android.com/identity/credential-manager)
+- [Digital Credentials](https://developer.android.com/identity/digital-credentials)
+- [Verifiable Credentials](https://developer.android.com/identity/digital-credentials#verifiable-credentials)
+
+
+## Android skills
+
+[View on GitHub](https://github.com/android/skills/tree/main/identity/verified-email)
+
+### Retrieve verified email
+
+Use an [Android skill](https://developer.android.com/tools/agents/android-skills) to integrate a secure, OTP-less email verification flow into your app. To install the skill from the [Android CLI](https://developer.android.com/tools/agents/android-cli), run:
+
+ android skills add verified-email
+
+
+
+## Android compatibility
+
+This feature is supported on mobiles, tablets, and foldable devices running
+Android 9 (API level 28) and higher. The minimum version of Google Play services
+(GMS) required is 25.49.x.
+
+## User experience
+
+The following sections describe the user experience during the verification
+flow, the need to include fallback verification methods, as well as the
+recommended user experience for various use cases.
+
+### The verification flow
+
+The user experience for sharing a verified email is as follows:
+
+1. The user either focuses on an input field or taps a button that calls the
+ Credential Manager API. Depending on the design of the screen, you can also
+ call the API on your app's screen load.
+
+2. A bottom sheet appears, showing the information that will be shared with the
+ app. If no information is available on that device, the user sees a generic
+ error message.
+
+3. After the user taps **Agree and Continue**, display a success or failure
+ message.
+
+ > [!NOTE]
+ > **Note:** If the verified email you receive doesn't match what you expect, inform the user about the mismatch and either ask them to try again with a different credential or provide an alternate verification method, such as through OTPs.
+
+4. Optional: If the user is signing up for your service, prompt the user to
+ [create](https://developer.android.com/identity/passkeys/create-passkeys) a [passkey](https://developer.android.com/identity/passkeys) to make it easier for them to sign in
+ subsequently.
+
+ > [!NOTE]
+ > **Note:** The email verification process doesn't automatically trigger passkey creation. However, it's highly recommended to include the steps for passkey creation. Passkeys help users by making it easier and more secure for them to sign in, and remove the need for the conventional username and password interaction.
+
+### Include primary and fallback flows
+
+To ensure a streamlined user experience, include the following options on
+screens that require email verification:
+
+- **Primary verification option**: An email field or button to trigger the Credential Manager API flow for quick verification.
+- **Alternate verification options**: A link or button for users to "Verify another way" or with "Other options" for manual email entry in case of failures, such as no information available on the device, or a mismatch between the retrieved and expected email. This should allow users to try verification with a different credential or by providing a manual OTP.
+
+### Use cases
+
+The following sections describe the recommended use cases, as well as the
+suggested user experience, for email verification.
+
+#### Sign up
+
+Users can immediately create an account with a verified email without a separate
+verification step. Optionally, prompt the user to add a passkey. If they opt to
+add a passkey, trigger the [passkey creation](https://developer.android.com/identity/passkeys/create-passkeys) flow.
+ Email verification during sign up
+
+#### Account recovery
+
+To eliminate the frustration of users searching for recovery codes in their spam
+folders, allow them to recover their account using the verified email securely
+stored on their device. Additionally, suggest that they create a passkey for
+future use.
+ Email verification during account recovery
+
+#### Reauthentication for sensitive actions
+
+Protect sensitive user actions, such as changing settings or updating profile
+details, by requiring a quick reauthentication step.
+ Email verification during reauthentication
+
+## Accounts supported
+
+Email verification through Credential Manager only supports verification of
+consumer Google Accounts. [Workspace accounts](https://knowledge.workspace.google.com/admin/getting-started/set-up-google-workspace-for-your-organization) and [supervised
+accounts](https://support.google.com/families/answer/9499054) aren't supported.
+
+A consumer Google Account can be created with an email address from any
+provider, not necessarily @gmail.com. However, Google verifies these accounts
+differently:
+
+- For @gmail.com accounts: Google is the authoritative source, and the email is known to be verified.
+- For non-@gmail.com accounts: Google is not the authoritative source for these email addresses in the long term. While Google verifies the email when the account is created, the ownership of that email address might change over time. Therefore, for non-@gmail.com addresses, you should consider an additional verification step, such as sending an OTP, to ensure that the user still has access to the email account.
+
+For more information about what verification implies, see [Digital
+Credentials](https://developer.android.com/identity/digital-credentials#verified).
+
+> [!NOTE]
+> **Note:** Apart from a user's email information, you can request other unverified fields, such as the user's given name, family name, name, and the profile picture of their Google Account. However, only the email is verified by Google.
+> For phone number verification, see [Verify phone numbers with digital credentials](https://developer.android.com/identity/digital-credentials/phone-number-verification). For a Firebase solution, see [Firebase Phone Number Verification](https://firebase.google.com/docs/phone-number-verification). Note that you can't verify a phone number and email in the same invocation as these are separate capabilities.
+
+## Validity and freshness
+
+The system issues [verifiable credentials](https://developer.android.com/identity/digital-credentials#verified) (VCs) based on the user's current
+email from the active Google Accounts on the device. These credentials are
+issued to the device in advance, typically while the device is idle. While these
+credentials might remain valid for multiple days, the system performs a check at
+the moment of sharing the credentials to ensure that the account still exists,
+is on the device, and that the email address is valid---effectively prioritizing
+the account's immediate status over the credential's validity window.
+
+To help ensure authenticity, a Key Binding (kb) signature is generated at the
+time of sharing, incorporating the nonce.
+
+If a device is offline or the account is removed, the process fails rather than
+providing an expired VC or a VC for an inactive Google Account.
+
+### Email deliverability
+
+While the process confirms the account's legitimacy, it doesn't guarantee inbox
+delivery (for instance, the email might be diverted to spam). An OTP remains the
+definitive method for confirming email deliverability.
+
+## Comparison with Sign in with Google
+
+While both Digital Credentials and [Sign in with Google](https://developer.android.com/identity/sign-in/credential-manager-siwg) solutions provide a
+verified email, the user flows and use cases are different:
+
+- **Use cases**: The Credential Manager email verification flow is not exclusively used in sign up or sign in use cases, but rather can be used in any use case involving the retrieval of verified email. This could include account recovery as well.
+- **Registration**: The Credential Manager flow doesn't require Google registration, unlike Sign in with Google.
+- **Platform support**: The Credential Manager flow is an Android-only solution.
+- **Scopes** : Unlike Sign in with Google, which can use OAuth 2.0 to request access to user data (such as Calendar or Drive through scopes), the Digital Credentials API is strictly for retrieving verified identity attributes. It can't be used to request additional [authorization scopes](https://developers.google.com/identity/protocols/oauth2/scopes).
+
+## Next steps
+
+To implement this feature in your app, see the [Implementation guide](https://developer.android.com/identity/digital-credentials/email-verification-implementation).
\ No newline at end of file
diff --git a/identity/verified-email/references/android/identity/digital-credentials/index.md b/identity/verified-email/references/android/identity/digital-credentials/index.md
new file mode 100644
index 0000000..83b6156
--- /dev/null
+++ b/identity/verified-email/references/android/identity/digital-credentials/index.md
@@ -0,0 +1,112 @@
+Digital credentials are cryptographically verifiable digital documents that your
+users can use to provide information about themselves, and use to authenticate
+or authorize themselves. Digital credentials are based on the [open W3C Digital
+Credentials API industry standard](https://www.w3.org/TR/digital-credentials/). On Android, this is
+implemented through [Credential Manager's](https://developer.android.com/identity/credential-manager) [Digital Credentials API](https://developer.android.com/reference/kotlin/androidx/credentials/DigitalCredential).
+ **Figure 1.** Using a digital credential in a sample app.
+
+## Benefits of digital credentials
+
+Digital credentials offer several advantages over physical credentials and
+non-standardized digital documents:
+
+- **Improved security and trust**: Digital credentials are encrypted, ensuring data integrity and authenticity. This asserts the surety that the credential was issued by a verifiable source and was not tampered with.
+- **Enhanced privacy**: Many digital credential formats support selective disclosure, which lets users share only necessary information. For example, a user could share proof of driving qualification without revealing their birth date.
+- **Consolidated storage**: Users can store various credentials from different issuers in digital holders all on their device, reducing the need to carry physical cards.
+- **Interoperability**: By following open standards, digital credentials can work across different operating systems, devices, and platforms.
+
+## Use cases
+
+The Digital Credentials API can be used across a broad set of use cases such as
+the following:
+
+- **Accept government-issued IDs**: Apps can request and use official government document attributes, for flows including age verification, account recovery, Know-Your-Customer (KYC) process.
+- **Verify phone numbers** : Apps can use the API for phone number verification, by exchanging digital credentials derived from the phone's SIM card directly with the user's carrier. This removes the need for one-time passwords (OTPs), improves security, and reduces transmission costs. For more information, see the [phone number verification guide](https://developer.android.com/identity/digital-credentials/phone-number-verification).
+- **Verify email addresses** : The API lets your app retrieve verified emails directly from the user's device, removing the need for OTPs, for frictionless sign-up, sign-in, and account recovery. For more information, see the [email verification guide](https://developer.android.com/identity/digital-credentials/email-verification).
+- **Custom credentials**: The extensibility of the API lets any app begin issuing its own custom digital credentials, which corresponding verifiers can request.
+- **Confirm payment credentials and transactions**: The API lets you secure payment authorizations and Digital Payment Credentials (DPC) with native, cryptographically bound wallet confirmation.
+
+## How digital credentials work
+
+The digital credential ecosystem involves three primary categories of apps:
+
+- **Issuers** : Issuers are apps that securely create and [issue](https://developer.android.com/identity/digital-credentials/credential-issuer/issue-credentials) credentials.
+- **Holders (wallets)** : Holders are apps on a user's device that store credentials. They should be able to share these credentials with requesting apps through a [presentation](https://developer.android.com/identity/digital-credentials/credential-holder/credential-holder) process.
+- **Verifiers**: Verifiers are apps that verify and use digital credentials.
+
+Credential Manager's Digital Credentials API orchestrates the interaction
+between the issuers, holders, and verifier apps.
+
+When a verifier makes a request for a digital credential, it sends a request to
+the Android system through the API. Credential Manager then displays eligible
+digital credentials from various holders within a trusted system UI. Once the
+user agrees to proceed, Credential Manager invokes the corresponding holder to
+generate the response.
+
+> [!NOTE]
+> **Note:** Issuers and holders don't have to be separate---an app can be both an issuer and holder. For example, if you use the Digital Credentials API for email verification, Google is the issuer and holder for the Gmail email address.
+
+## User experience
+
+Similarly to how Credential Manager has built-in user interfaces for
+authentication flows, such as with passkeys, passwords, and Sign in with Google,
+there are also standardized interfaces for the various use cases of the Digital
+Credentials API.
+
+There are UI variants tailored to specific use cases. When using the API, the
+Android system automatically renders context-aware bottom sheets, such as for
+the following scenarios:
+
+- **Email verification**: The interface displays an account picker featuring the verified email address and identity provider.
+- **Phone number verification**: The interface includes a verification card with network carriers represented.
+- **Multi-credential**: This view stacks multiple cards, and allows bundling related items into a single tap.
+- **Digital payments credentials**: This checkout sheet displays payment card details, merchant info, and total transaction amount for immediate confirmation.
+
+ **Figure 2.** The UX variants for digital credentials for email verification, phone verification, and multi-credential scenarios.
+
+## Industry standards
+
+Digital credentials rely on industry standards to ensure cross-platform
+compatibility. The commonly used open standards are:
+
+- **Sharing** : Digital credentials are requested using the [OpenID4VP
+ standard](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#name-introduction).
+- **Issuance** : Digital credentials are issued using the [OpenID4VCI
+ standard](https://openid.github.io/OpenID4VCI/openid-4-verifiable-credential-issuance-1_1-wg-draft.html).
+- **Credential storage format** : Digital credentials are represented in standardized formats maintained by different standards bodies, primarily the following:
+ - IETF [selective disclosure of JSON web tokens (sd-jwt)](https://datatracker.ietf.org/doc/draft-ietf-oauth-selective-disclosure-jwt/) VC
+ - ISO [MDoc](https://www.iso.org/standard/69084.html)
+
+These standards are used widely on multiple platforms and operating systems,
+enabling seamless operation across web browsers, mobile devices, and other form
+factors. This allows developers to exchange digital credentials with other apps
+regardless of the platform they're on.
+
+## Try it out
+
+You can test the cross-platform digital credentials flow by installing the
+sample holder and verifier apps on an Android-powered device.
+
+To test the flow, complete the following steps:
+
+1. **Install the sample apps on the Android-powered device** : Use one of the following methods to install the apps on your first device:
+ - **Use the prebuilt APKs** :
+ - **Download the holder app** : Sign in to your GitHub account, navigate to the [holder app GitHub Actions page](https://github.com/digitalcredentialsdev/CMWallet/actions), select the latest successful build, and download the `app-debug.apk` file from the **Artifacts** section. This app is called **CMWallet**.
+ - **Download the verifier app** : For the sample verifier app, get the APK from the [Identity samples repository](https://github.com/android/identity-samples/actions). Then, install the APK on your Android-powered device. This app is called **Digital
+ Credentials Demo**.
+ - **Build from source**: Clone the repositories mentioned and install the app using Android Studio.
+2. **Open the sample holder app**: Open the sample holder app. This registers the holder credentials with Credential Manager.
+3. **Open the sample verifier app** : Open the sample verifier app. Then, select the option to request digital credentials from wallets. A bottomsheet displaying available credentials from **CMWallet** should appear.
+4. **Select a credential**: Select a credential to send back to the verifier app. The verifier app should now display fields from the returned credential.
+
+> [!NOTE]
+> **Note:** You can also initiate the request from a web browser on another device by navigating to and selecting **Request Credentials (OpenID4VP 1.0)**.
+
+## Resources
+
+- For more information about issuing digital credentials, see the [issuer
+ guide](https://developer.android.com/identity/digital-credentials/credential-issuer/issue-credentials).
+- For more information about holder apps, see the [holder guide](https://developer.android.com/identity/digital-credentials/credential-holder/credential-holder).
+- For more information about verifying users based on digital credentials, see the [verifier guide](https://developer.android.com/identity/digital-credentials/credential-verifier).
+- For more information about digital credentials on the web, see [digital
+ credentials on the web](https://developer.chrome.com/blog/digital-credentials-api-shipped).
\ No newline at end of file
diff --git a/identity/verified-email/references/android/identity/passkeys/create-passkeys.md b/identity/verified-email/references/android/identity/passkeys/create-passkeys.md
new file mode 100644
index 0000000..089cc21
--- /dev/null
+++ b/identity/verified-email/references/android/identity/passkeys/create-passkeys.md
@@ -0,0 +1,327 @@
+Before your users can authenticate with passkeys, your app must first register
+or create the passkey for their account.
+
+To create the passkey, obtain details required to create the passkey from your
+app server, and then call the Credential Manager API, which returns a public and
+private key pair. The returned private key is stored in a credential provider,
+such as Google Password Manager, as a passkey. The public key is stored on your
+app server.
+
+> [!TIP]
+> **Tip:** While designing authentication flows with passkeys, make sure that you follow the [UX guidelines for passkeys](https://developer.android.com/design/ui/mobile/guides/patterns/passkeys).
+
+ **Figure 1:**Creation of passkeys
+
+## Prerequisites
+
+Make sure that you have set up [Digital Asset Links](https://developer.android.com/identity/credential-manager/prerequisites) and that you target
+devices running Android 9 (API level 28) or higher.
+
+## Overview
+
+This guide focuses on the changes required in your [relying party client app](https://developer.android.com/identity/credential-manager#authentication-terminology)
+to create a passkey, and gives a brief overview of the [relying party app
+server](https://developer.android.com/identity/credential-manager#authentication-terminology) implementation. To learn more about the server-side integration, see
+[Server-side passkey registration](https://developers.google.com/identity/passkeys/developer-guides/server-registration).
+
+1. [**Add dependencies to your app**](https://developer.android.com/identity/passkeys/create-passkeys#add-dependencies): Add the required Credential Manager libraries.
+2. [**Instantiate Credential Manager**](https://developer.android.com/identity/passkeys/create-passkeys#instantiate): Create a Credential Manager instance.
+3. [**Get credential creation options from the app server**](https://developer.android.com/identity/passkeys/create-passkeys#get-options): From your app server, send the client app the details required to create the passkey, such as information about the app, the user, as well as a `challenge` and other fields.
+4. [**Request a passkey**](https://developer.android.com/identity/passkeys/create-passkeys#request-passkey): In your app, use the details received from the app server to create a [`GetPublicKeyCredentialOption`](https://developer.android.com/reference/androidx/credentials/GetPublicKeyCredentialOption) object and use this object to invoke the `credentialManager.getCredential()` method to create a passkey.
+5. [**Handle the passkey creation response**](https://developer.android.com/identity/passkeys/create-passkeys#handle-response): When you receive the credentials on your client app, you must encode, serialize and then send the public key to the app server. You must also handle each of the exceptions that can occur in case of passkey creation.
+6. [**Verify and save the public key on the server**](https://developer.android.com/identity/passkeys/create-passkeys#verify): Complete the server-side steps to verify the origin of the credential and then save the public key.
+7. [**Notify the user**](https://developer.android.com/identity/passkeys/create-passkeys#notify): Notify the user that their passkey has been created.
+
+> [!TIP]
+> **Tip:** To enhance the user experience during sign up, add functionality to [restore credentials](https://developer.android.com/identity/sign-in/restore-credentials) on a new device to let users seamlessly set up their existing accounts on new Android devices, and [suppressing autofill dialogs on
+> credential fields](https://developer.android.com/identity/passkeys/create-passkeys#suppress-autofill).
+
+## Add dependencies to your app
+
+Add the following dependencies to your app module's `build.gradle` file:
+
+### Kotlin
+
+```kotlin
+dependencies {
+ implementation("androidx.credentials:credentials:1.7.0-alpha03")
+ implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha03")
+}
+```
+
+### Groovy
+
+```groovy
+dependencies {
+ implementation "androidx.credentials:credentials:1.7.0-alpha03"
+ implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha03"
+}
+```
+
+> [!NOTE]
+> **Note:** Use the latest available versions of the dependencies. Versions of [`androidx.credentials`](https://developer.android.com/identity/passkeys/create-passkeys#automatic-upgrade) earlier than version 1.2 throw an `UnsupportedOperationException("Post-U not supported yet")` exception on Android 14.
+
+## Instantiate Credential Manager
+
+Use your app or activity context to create a `CredentialManager` object.
+
+ // Use your app or activity context to instantiate a client instance of
+ // CredentialManager.
+ private val credentialManager = CredentialManager.create(context)
+
+## Get credential creation options from your app server
+
+When the user clicks a "Create Passkey" button or when a new user signs up, make
+a request from your app to your app server to obtain the information required to
+start the passkey registration process.
+
+Use a FIDO-compliant library in your app server to send your client app the
+information required to create a passkey, such as information about the user,
+the app, and additional configuration properties. To learn more, see [Server
+side passkey registration](https://developers.google.com/identity/passkeys/developer-guides/server-registration).
+
+In the client app, decode the public key creation options sent by the app
+server. These are usually represented in JSON format. To learn more about how
+this decoding is done for web clients, see [Encoding and
+Decoding](https://developers.google.com/identity/passkeys/developer-guides/server-registration#encoding_and_decoding). For Android client apps, you must handle the decoding
+separately.
+
+> [!NOTE]
+> **Note:** On your app server, securely store the `challenge` so that you can later verify the origin of the credential.
+
+The following snippet shows the structure the public key creation options sent
+by the app server:
+
+ {
+ "challenge": "",
+ "rp": {
+ "name": "",
+ "id": ""
+ },
+ "user": {
+ "id": "",
+ "name": "",
+ "displayName": ""
+ },
+ "pubKeyCredParams": [
+ {
+ "type": "public-key",
+ "alg": -7
+ }
+ ],
+ "attestation": "none",
+ "excludeCredentials": [
+ {
+ "id": "",
+ "type": "public-key"
+ }
+ ],
+ "authenticatorSelection": {
+ "requireResidentKey": true,
+ "residentKey": "required",
+ "userVerification": "required"
+ }
+ }
+
+Key fields in the public key creation options include:
+
+- `challenge`: A server-generated random string that is used to prevent replay attacks.
+- `rp`: Details about the app.
+ - `rp.name`: The app's name.
+ - `rp.id`: The app's domain or subdomain.
+- `user`: Details about the user.
+ - `id`: The user's unique ID. This value must not include personally identifying information, for example, email addresses or usernames. You can use a random, 16-byte value.
+ - `name`: A unique identifier for the account that the user will recognise, such as their email address or username. This will be displayed in the account selector. If using a username, use the same value as in password authentication.
+ - `displayName`: An optional, user-friendly name for the account intended for display in the account selector.
+- `authenticatorSelection`: Details about the device that will be used for authentication.
+ - `authenticatorAttachment`: Indicates the preferred [authenticator](https://www.w3.org/TR/webauthn/#authenticator). The possible values are as follows:
+ - `platform`: This value is used for an authenticator built into the user's device, such as a fingerprint sensor.
+ - `cross-platform`: This value is used for roaming devices such as security keys. It is not typically used in the passkey context.
+ - Unspecified (recommended): Leaving this value unspecified provides users with the flexibility to create passkeys on their preferred devices. In most cases, leaving the parameter unspecified is the best option.
+ - `requireResidentKey`: To create a passkey, set the value of this `Boolean` field to `true`.
+ - `residentKey`: To create a passkey, set the value to `required`.
+ - `userVerification`: Used to specify the requirements for user verification during a passkey registration. The possible values are as follows:
+ - `preferred`: Use this value if you prioritize user experience over protection, such as in environments where user verification causes more friction than protection.
+ - `required`: Use this value if invoking a user verification method available on the device is required.
+ - `discouraged`: Use this value if using a user verification method is discouraged.
+ To learn more about `userVerification`, see [userVerification deep dive](https://web.dev/articles/webauthn-user-verification).
+- `excludeCredentials`: List credential IDs in an [array](https://w3c.github.io/webauthn/#dom-publickeycredentialcreationoptions-excludecredentials) to prevent the creation of a duplicate passkey if one already exists with the same credential provider.
+
+## Create a passkey
+
+After you have parsed the server-side public key creation options, create a
+passkey by wrapping these options in a `CreatePublicKeyCredentialRequest` object
+and calling `createCredential()`.
+
+The `createPublicKeyCredentialRequest` includes the following:
+
+- `requestJson`: The credential creation options sent by the app server.
+- `preferImmediatelyAvailableCredentials`: This is an optional Boolean field that defines whether to only use locally-available or credential provider-synced credentials to fulfill the request, instead of credentials from security keys or [hybrid](https://w3c.github.io/webauthn/#dom-authenticatortransport-hybrid) key flows. The possible usages are as follows:
+ - `false` (default): Use this value if the call to Credential Manager was triggered by an explicit user action.
+ - `true`: Use this value if Credential Manager is opportunistically called, such as when first opening the app.
+ If you set the value to `true` and there are no immediately available credentials, Credential Manager won't show any UI and the request will fail immediately, returning NoCredentialException for get requests and [`CreateCredentialNoCreateOptionException`](https://developer.android.com/reference/kotlin/androidx/credentials/exceptions/CreateCredentialNoCreateOptionException) for create requests.
+- `origin`: This field is automatically set for Android apps. For browsers and similarly privileged apps that need to set `origin`, see [Make Credential
+ Manager calls on behalf of other parties for privileged apps](https://developer.android.com/training/sign-in/privileged-apps).
+- `isConditional`: This is an optional field that defaults to `false`. For more information, see [Automatically create a passkey](https://developer.android.com/identity/passkeys/create-passkeys#automatic-upgrade).
+
+Calling the `createCredential()` function launches Credential Manager's built-in
+bottom sheet UI that prompts the user to use a passkey and to select a
+credential provider and account for storage. However, if `isConditional` is set
+to `true`, the bottom sheet UI does not display, and the passkey is
+automatically created.
+
+### Automatically create a passkey
+
+You can automatically create a passkey for a user after a successful password
+login by setting the `isConditional` parameter to `true` in your
+`CreatePublicKeyCredentialRequest` while creating a passkey. If the user doesn't
+already have a passkey, your app will automatically attempt to create one in the
+background and store it in the user's credential provider, such as Google
+Password Manager. For an example of how this is implemented, see the [public
+sample](https://github.com/android/identity-samples/blob/main/Shrine/app/src/main/java/com/authentication/shrine/ui/AuthenticationScreen.kt#L98).
+ **Figure 2:**Google Password Manager notification
+
+> [!NOTE]
+> **Note:** If a passkey is created automatically, credential providers are responsible for notifying users about a newly created passkey. Google Password Manager notifies users when a passkey is automatically created. However, other credential providers might have their own conditions and notifications for this feature.
+
+## Handle the response
+
+After the user is verified using the device's screen lock, a passkey is created
+and stored in the user's selected credential provider.
+
+The response after you successfully call `createCredential()` is a
+[PublicKeyCredential](https://developer.android.com/jetpack/androidx/releases/credentials) object.
+
+The `PublicKeyCredential` looks as follows:
+
+ {
+ "id": "",
+ "type": "public-key",
+ "rawId": "",
+ "response": {
+ "clientDataJSON": "",
+ "attestationObject": ""
+ },
+ "authenticatorAttachment": "platform"
+ }
+
+In the client app, serialize the object and send it to the app server.
+
+Add code to handle failures as shown in the following snippet:
+
+ fun handleFailure(e: CreateCredentialException) {
+ when (e) {
+ is CreatePublicKeyCredentialDomException -> {
+ // Handle the passkey DOM errors thrown according to the
+ // WebAuthn spec.
+ }
+ is CreateCredentialCancellationException -> {
+ // The user intentionally canceled the operation and chose not
+ // to register the credential.
+ }
+ is CreateCredentialInterruptedException -> {
+ // Retry-able error. Consider retrying the call.
+ }
+ is CreateCredentialProviderConfigurationException -> {
+ // Your app is missing the provider configuration dependency.
+ // Most likely, you're missing the
+ // "credentials-play-services-auth" module.
+ }
+ is CreateCredentialCustomException -> {
+ // You have encountered an error from a 3rd-party SDK. If you
+ // make the API call with a request object that's a subclass of
+ // CreateCustomCredentialRequest using a 3rd-party SDK, then you
+ // should check for any custom exception type constants within
+ // that SDK to match with e.type. Otherwise, drop or log the
+ // exception.
+ }
+ else -> Log.w(TAG, "Unexpected exception type ${e::class.java.name}")
+ }
+ }
+
+## Verify and save the public key on the app server
+
+On the app server, you must verify the public key credential and then [save the
+public key](https://web.dev/articles/passkey-registration#save-credential).
+
+To verify the public key credential's origin, compare it against an allow list
+of approved apps. If a key has an unrecognized origin, reject it.
+
+> [!NOTE]
+> **Note:** An app's origin is based on its unique identity, which is the SHA-256 fingerprint of its signing certificate.
+
+To obtain the app's SHA 256 fingerprint:
+
+1. Print your release app's signing certificate by running the following
+ command in a terminal:
+
+ keytool -list -keystore
+
+ In the response, identify the signing certificate's SHA 256 fingerprint,
+ mentioned as `Certificate fingerprints block` : `SHA256`.
+2. Encode the SHA256 fingerprint with base64url encoding. This Python example
+ demonstrates how to properly encode the fingerprint:
+
+ import binascii
+ import base64
+ fingerprint = '' # your app's SHA256 fingerprint
+ print(base64.urlsafe_b64encode(binascii.a2b_hex(fingerprint.replace(':', ''))).decode('utf8').replace('=', ''))
+
+3. Append `android:apk-key-hash`: to the start of the output from the previous
+ step so that you get something that is similar to the following:
+
+ android:apk-key-hash:
+
+ The result should match with an allowed origin on your app server. If you
+ have multiple signing certificates, such as certificates for debugging and
+ release, or multiple apps, then repeat the process and accept all the
+ origins as valid on the app server.
+
+> [!NOTE]
+> **Note:** When you save the passkey on the app server, make sure that you save the Authenticator Attestation Globally Unique Identifier ([AAGUID](https://web.dev/articles/webauthn-aaguid)) from the client data. The AAGUID is a unique number that identifies the model of the authenticator. For more information, see [Manage passkeys](https://developer.android.com/identity/passkeys/manage-passkeys).
+
+## Notify the user
+
+After the passkey is successfully created, notify your users about the passkey
+and inform them that they can manage their passkeys from their credential
+provider app or from [within the app settings](https://developer.android.com/identity/passkeys/manage-passkeys). Notify users by using a
+custom dialog, notification, or snackbar. Since an unexpected passkey creation
+by a malicious entity requires an immediate security alert, consider
+supplementing these in-app methods with external communication, such as an
+email.
+
+## Enhance the user experience
+
+To enhance the user experience while implementing sign up with Credential
+Manager, consider adding functionality for restore credentials and suppress
+autofill dialogs.
+
+### Add functionality to restore credentials on a new device
+
+To allow users to seamlessly log into their accounts on a new device, implement
+the [Restore Credentials](https://developer.android.com/identity/sign-in/restore-credentials) functionality. Adding restore credentials with
+`BackupAgent` logs users in when they open your restored app on a new device,
+letting them use your app right away.
+
+### Suppress autofill on credential fields (optional)
+
+For app screens where users are expected to use Credential Manager's bottom
+sheet UI for authentication, add the `isCredential` attribute to the username
+and password fields. This suppresses autofill dialogs (`FillDialog` and
+`SaveDialog`) from overlapping with Credential Manager's bottom sheet UI.
+
+The `isCredential` attribute is supported on Android 14 and higher.
+
+The following example demonstrates how you can add the `isCredential` attribute
+to the relevant username and password fields in the relevant views for your app:
+
+
+
+## Next steps
+
+- [Sign in with passkeys](https://developer.android.com/identity/passkeys/sign-in-with-passkeys)
+- [Manage passkeys](https://developer.android.com/identity/passkeys/manage-passkeys)
+- [Understand passkey user experience flows](https://developer.android.com/design/ui/mobile/guides/patterns/passkeys)
\ No newline at end of file
diff --git a/identity/verified-email/references/android/identity/sign-in/credential-manager-webview.md b/identity/verified-email/references/android/identity/sign-in/credential-manager-webview.md
new file mode 100644
index 0000000..a953795
--- /dev/null
+++ b/identity/verified-email/references/android/identity/sign-in/credential-manager-webview.md
@@ -0,0 +1,84 @@
+This document describes how to integrate the Credential Manager API with an
+Android app that uses WebView. Credential Manager is supported natively in the
+`android.webkit.WebView` library in [version 1.12.0](https://developer.android.com/jetpack/androidx/releases/webkit#1.12.0) and later.
+
+## Prerequisites
+
+To use Credential Manager in WebView, add the following dependencies to your app
+module's build script:
+
+ dependencies {
+ implementation("androidx.credentials:credentials:1.6.0-beta02")
+ implementation("androidx.credentials:credentials-play-services-auth:1.6.0-beta02")
+ implementation("androidx.webkit:webkit:1.14.0")
+ }
+
+You will also need to associate your app with a website that your app owns using
+digital asset linking. For more information, see
+[adding digital asset linking](https://developer.android.com/identity/credential-manager/prerequisites).
+
+## Use the WebKit library
+
+To use the WebKit library, check for feature support, and then enable support by
+calling `setWebAuthenticationSupport()`:
+
+ class WebViewActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ // Put your webview link here.
+ val url = "https://project-sesame-426206.appspot.com/passkey-signup"
+ AndroidView(
+ factory = { context ->
+ WebView(context).apply {
+ settings.javaScriptEnabled = true
+
+ webViewClient = WebViewClientImpl()
+ }
+ },
+ update = { webView ->
+ run {
+ webView.loadUrl(url)
+ if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_AUTHENTICATION)) {
+ WebSettingsCompat.setWebAuthenticationSupport(
+ webView.settings,
+ WebSettingsCompat.WEB_AUTHENTICATION_SUPPORT_FOR_APP,
+ )
+ // Check if getWebauthenticationSupport may have been disabled by the WebView.
+ Log.e(
+ "WebViewPasskeyDemo",
+ "getWebAuthenticationSupport result: " + WebSettingsCompat.getWebAuthenticationSupport(
+ webView.settings
+ ),
+ )
+ } else {
+ Log.e("WebViewPasskeyDemo", "WebView does not support passkeys.")
+ }
+ }
+ },
+ )
+ }
+ }
+ }
+
+> [!NOTE]
+> **Note:** The WebKit library doesn't support `mediation:"conditional"` requests.
+
+## Web integration
+
+To learn how to build Web integration see
+[Create a passkey for passwordless logins](https://web.dev/passkey-registration/).
+You can also reference the [demo site source](https://github.com/deephand/webauthn-in-webview).
+
+## Testing and deployment
+
+Test the entire flow thoroughly in a controlled environment to verify proper
+communication between the Android app, the web page, and the backend.
+
+Deploy the integrated solution to production, verifying that the backend can
+handle incoming registration and authentication requests. The backend code
+should generate initial JSON for registration (create) and authentication (get)
+processes. It should also handle validation and verification of the responses
+received from the web page.
+
+Verify the implementation corresponds to the [UX recommendations](https://developer.android.com/design/ui/mobile/guides/patterns/passkeys).
\ No newline at end of file
diff --git a/jetpack-compose/adaptive/SKILL.md b/jetpack-compose/adaptive/SKILL.md
new file mode 100644
index 0000000..64d8059
--- /dev/null
+++ b/jetpack-compose/adaptive/SKILL.md
@@ -0,0 +1,301 @@
+---
+name: adaptive
+description: Instructions to make or update an app's UI so that it adapts to different
+ Android devices including phones, tablets, foldables, laptops, desktop, TV, Auto
+ and XR. It includes how to handle different window sizes, pointing devices (such
+ as mouse) and text entry devices (such as keyboard) using the Compose MediaQuery
+ API. It also covers multi-pane layouts using Navigation3 Scenes, adaptive UI components
+ (such as buttons) with varying target sizes, and adaptive layouts (including navigation
+ areas - nav rails and nav bars) using the Compose Grid and FlexBox APIs.
+license: Complete terms in LICENSE.txt
+metadata:
+ author: Google LLC
+ last-updated: '2026-10-08'
+ keywords:
+ - android
+ - ui
+ - adaptive
+ - Grid
+ - FlexBox
+ - MediaQuery
+ - navigation
+---
+
+## Prerequisites
+
+The app must:
+
+- Use Compose for all screens. If it's still using Fragments or Views, suggest using the XML to Compose skill to migrate those screens.
+- Use Jetpack Navigation 3. If it doesn't, suggest the Navigation 3 skill to migrate the app.
+
+## Workflow to make an app adaptive
+
+To make an app adaptive, follow these steps or a subset of them adapting to the
+task.
+
+- Step 1: Verify current UI
+- Step 2: Make the navigation bar adaptive
+- Step 3: Add multi-pane layouts
+- Step 4: Make vertical lists adaptive by changing the number of columns
+- Step 5: Hide app bars when scrolling
+
+## Step 1. Verify current UI
+
+Ensure that screenshot tests exist to verify the current UI on different form
+factors. If they don't exist, add the [Compose Preview Screenshot Testing
+tool](references/android/studio/preview/compose-screenshot-testing-with-testsuites.md). Use the following annotation to create previews for all the major form
+factors. For example:
+
+
+```kotlin
+@Preview(name = "Phone", device = Devices.PHONE, showBackground = true)
+@Preview(name = "Foldable", device = Devices.FOLDABLE, showBackground = true)
+@Preview(name = "Tablet", device = Devices.TABLET, showBackground = true)
+@Preview(name = "Desktop", device = Devices.DESKTOP, showBackground = true)
+annotation class FormFactorPreviews
+
+@PreviewTest
+@FormFactorPreviews
+@Composable
+fun FeedScreenPreview() {
+ SnippetsTheme {
+ Box {
+ Text("My Screen")
+ }
+ }
+}
+```
+
+
+
+## Step 2. Make the navigation bar adaptive
+
+Bottom navigation bars are optimized for touch input when the user is holding a
+phone in portrait mode. On larger screen hand-held devices, like tablets and
+unfolded foldables, the navigation area must be accessible from the edge of the
+screen (navigation rail).
+
+If you need to provide more screen space for the content, hide the
+navigation area. Examples of this include:
+
+- Hiding the navigation bar when the user scrolls down and showing it again when the user scrolls up. The assumption is that when the user is scrolling down, they are consuming content but when scrolling up they are trying to navigate away from that content.
+- Hiding the navigation area when its content is distracting. For example, in camera previews or when displaying a full-screen photo.
+
+When the detail screen is displayed full-screen on mobile, full-screen mode must
+be deactivated on larger screens.
+
+Steps to migrate:
+
+- Locate the existing navigation bar.
+- Convert each item to a `NavigationSuiteItem`.
+- Identify whether the navigation bar's visibility changes. For example, if it is wrapped with an `AnimatedContent` or `AnimatedVisibility` composable. If so, follow the guidance in the "Control navigation area visibility".
+- Replace the container that held the navigation bar (often a `Scaffold`) with `NavigationSuiteScaffold` from the Material 3 adaptive layouts library.
+- Supply the navigation items using the `navigationItems` parameter of `NavigationSuiteScaffold`.
+
+### Step 2.1. Control navigation area visibility
+
+If the navigation bar's visibility changes - it is hidden under certain
+scenarios or on certain screens - this behavior must be maintained with the
+adaptive navigation area. This is done using `NavigationSuiteScaffold`'s `state`
+parameter.
+
+Steps to migrate:
+
+- Identify the scenarios under which the navigation bar is hidden. This is usually done with a boolean variable for the visibility. Use `isNavBarVisible` or `shouldShowNavBar` as the variable name.
+- Create an instance of `NavigationSuiteScaffoldState` using `rememberNavigationSuiteScaffoldState()` and pass it to `NavigationSuiteScaffold`.
+- When the navigation area visibility changes, use a `LaunchedEffect` to call `show` or `hide` on the `NavigationSuiteScaffoldState`.
+
+For example:
+
+
+```kotlin
+// Pass this variable to any composable that needs to control the navigation area visibility
+var isNavBarVisible by remember { mutableStateOf(true) }
+val scaffoldVisibilityState = rememberNavigationSuiteScaffoldState()
+
+NavigationSuiteScaffold(
+ navigationSuiteItems = navItems,
+ state = scaffoldVisibilityState
+) {
+ // Main content
+}
+
+LaunchedEffect(isNavBarVisible){
+ if (isNavBarVisible) {
+ scaffoldVisibilityState.show()
+ } else {
+ scaffoldVisibilityState.hide()
+ }
+}
+```
+
+
+
+## Step 3. Add multi-pane layouts using Navigation 3 Scenes
+
+Analyze the codebase looking for related screens - tapping on something in one
+screen opens another screen that shows information related to the first. There
+are two canonical screen relationships: list-detail and supporting pane.
+
+IMPORTANT: You must use the Navigation 3 `SceneStrategy` approach to implement
+multi-pane layouts. Do not use `ListDetailPaneScaffold` or
+`SupportingPaneScaffold`.
+
+### Step 3.1. List-detail
+
+#### Identify the list and detail screens
+
+List-detail layouts display a list of items (this is the list screen) and
+clicking on an item opens a new screen that shows more details about that item
+(the detail screen).
+
+Typical usage includes productivity apps like email, notes, and messaging.
+
+Unless requested explicitly, avoid this pattern when the detail content requires
+substantial screen space (e.g., images or media that benefits from a full-screen
+presentation).
+
+#### Add a Material list-detail SceneStrategy
+
+- Add the `androidx.compose.material3.adaptive:adaptive-navigation3` library
+- Create an `androidx.compose.material3.adaptive.navigation3.ListDetailSceneStrategy` using `rememberListDetailSceneStrategy`
+- Pass the `ListDetailSceneStrategy` to `NavDisplay` using its `sceneStrategies` parameter
+
+#### Use metadata to identify the list and detail screens
+
+- Add metadata using `entry(metadata = ...)` or `NavEntry(metadata = ...)` to the list entry using `ListDetailSceneStrategy.listPane(detailPlaceholder = {
+ })`.
+- Use the `detailPlaceholder` parameter to add a placeholder on the detail screen when no list items are selected.
+- Add metadata to the detail entry using `ListDetailSceneStrategy.detailPane()`.
+
+#### Important considerations
+
+- When a detail screen displays its content full-screen on mobile (content fills the entire screen, bars or rails are hidden), full-screen mode must be deactivated if it's part of a list-detail layout.
+- Detail screens must not show a back arrow when on a list-detail layout.
+
+For a reference implementation, check the [Nav3 **Material** List Detail
+recipe](references/android/guide/navigation/navigation-3/recipes/material-listdetail.md).
+
+### Step 3.2. Supporting pane
+
+Identify supporting pane screens where a main screen displays a single item, and
+selecting it opens a "supporting screen" with more details. The supporting
+screen complements the main screen and is shown in a supporting pane.
+
+#### Add a Material supporting pane `SceneStrategy`
+
+- If you haven't already, add the `androidx.compose.material3.adaptive:adaptive-navigation3` library
+- Create an `androidx.compose.material3.adaptive.navigation3.SupportingPaneSceneStrategy` using `rememberSupportingPaneSceneStrategy`
+- Pass the `SupportingPaneSceneStrategy` to `NavDisplay` using its `sceneStrategies` parameter
+
+#### Use metadata to identify the main and supporting screens
+
+- Add metadata using `entry(metadata = ...)` or `NavEntry(metadata = ...)` to the main entry using `SupportingPaneSceneStrategy.mainPane()`
+- Add metadata to the supporting entry using `SupportingPaneSceneStrategy.supportingPane()`
+
+### Step 3.3. Run screenshot tests
+
+If you have made changes, record new reference files. Ask the user to visually
+verify that the new layouts are correct.
+
+## Step 4. Make vertical lists adaptive by changing the number of columns
+
+### Step 4.1. Make lazy lists adaptive
+
+Look for the following vertical list composables: `LazyColumn`,
+`LazyVerticalGrid`, `LazyVerticalStaggeredGrid`.
+
+Steps to migrate:
+
+- Choose a suitable minimum width in dp for the column. The item must be clearly visible to the user at this width.
+- For `LazyColumn`: change to a `LazyVerticalGrid` and follow the instruction later
+- For `LazyVerticalGrid`: change the `columns` parameter to use `GridCells.Adaptive(.dp)`
+- For `LazyVerticalStaggeredGrid`: change the `columns` parameter to use `StaggeredGridCells.Adaptive(.dp)`
+
+### Step 4.2. Migrate non-lazy lists to Grid
+
+WARNING: Grid is an experimental API available from Compose 1.11.0-beta01.
+Confirm with the user that they are happy to use an experimental API in their
+codebase.
+
+Look for any `Column` that contains multiple items of the same type and replace
+it with `Grid`. Do not replace it with `LazyVerticalGrid` or any other lazy
+layout. Do not place `Grid` inside the existing `Column`. Completely replace it.
+
+`Grid` is configured by supplying a lambda (an extension function on
+`GridConfigurationScope`) to its `config` parameter. Inside the lambda,
+`constraints` provides the minimum and maximum dimensions of the grid container
+and can be used to change the number of rows and columns based on the available
+size. For example, the following code configures `Grid` such that when the
+available width is:
+
+- less than 800dp, a 2x4 grid is used
+- 800dp or more, a 4x2 grid is used
+
+
+```kotlin
+Grid(
+ config = {
+ val maxWidthDp = constraints.maxWidth.toDp()
+ val (cols, rows) = if (maxWidthDp < 800.dp){
+ 2 to 4
+ } else{
+ 4 to 2
+ }
+
+ val gapSizeDp = 8.dp
+ val cellSize = ((maxWidthDp - (gapSizeDp * (cols - 1))) / cols).coerceAtLeast(0.dp)
+ repeat(cols) { column(cellSize) }
+ repeat(rows) { row(cellSize) }
+ gap(gapSizeDp)
+ }
+) { /** items **/ }
+```
+
+
+
+`Grid` is an experimental API so add the `@OptIn(ExperimentalGridApi::class)`
+annotation to any function that uses it.
+
+## Step 5: Hide App Bars when scrolling
+
+In an app with multiple top-level destinations, each screen must manage its own
+app bar state independently. There are two main scroll behaviors:
+
+- `exitUntilCollapsedScrollBehavior`: Hides on scroll down, stays hidden while you scroll up until you reach the very top (0 offset).
+- `enterAlwaysScrollBehavior`: Hides on scroll down, shows immediately on scroll up.
+
+## Final step: Build and test
+
+Build the app and run the local tests. If the project has screenshot tests, run
+them but DO NOT update the reference images. Prompt the user to do this after
+they have viewed the screenshot diffs.
+
+## Additional documentation for experimental adaptive APIs
+
+The following APIs are available from Compose 1.11.0-beta01.
+
+### FlexBox
+
+Check the FlexBox documentation:
+
+- [Overview](references/android/develop/ui/compose/layouts/adaptive/flexbox/index.md)
+- [Get started - setup](references/android/develop/ui/compose/layouts/adaptive/flexbox/get-started.md)
+- [Set container behavior](references/android/develop/ui/compose/layouts/adaptive/flexbox/container-behavior.md)
+- [Set item behavior](references/android/develop/ui/compose/layouts/adaptive/flexbox/item-behavior.md)
+
+## MediaQuery
+
+Check the [MediaQuery documentation](references/android/develop/ui/compose/layouts/adaptive/mediaquery/index.md) when you need to query the device's
+screen size, pointer precision, keyboard type, whether it has cameras or
+microphones, and other device capabilities.
+
+## Grid
+
+Check the Grid documentation when you need to display a fixed number of items in
+a grid layout:
+
+- [Overview](references/android/develop/ui/compose/layouts/adaptive/grid/index.md)
+- [Get started - setup](references/android/develop/ui/compose/layouts/adaptive/grid/get-started.md)
+- [Set container properties](references/android/develop/ui/compose/layouts/adaptive/grid/container-properties.md)
+- [Set item properties](references/android/develop/ui/compose/layouts/adaptive/grid/item-properties.md)
diff --git a/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/container-behavior.md b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/container-behavior.md
new file mode 100644
index 0000000..41112fc
--- /dev/null
+++ b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/container-behavior.md
@@ -0,0 +1,112 @@
+To configure the behavior of the `FlexBox` container, create a `FlexBoxConfig`
+block and supply it using the `config` parameter.
+
+
+```kotlin
+FlexBox(
+ config = {
+ direction(FlexDirection.Column)
+ wrap(FlexWrap.Wrap)
+ alignItems(FlexAlignItems.Center)
+ alignContent(FlexAlignContent.SpaceAround)
+ justifyContent(FlexJustifyContent.Center)
+ gap(16.dp)
+ }
+) { // child items
+}
+```
+
+
+
+Use `FlexBoxConfig` to define the layout direction, wrapping behavior,
+alignment, and gaps between items.
+
+## Layout direction
+
+The `direction` function sets the main axis, which dictates the direction
+items are laid out in. It accepts the following values:
+
+- `Row` (default): Sets the main axis to be horizontal. In left-to-right locales this will be left-to-right, with the opposite in right-to-left.
+- `RowReverse`: Reverses the direction of `Row`.
+- `Column`: Sets the main axis to be vertical, top-to-bottom.
+- `ColumnReverse`: Reverses the direction of `Column`.
+
+## Align items and distribute extra space
+
+The following sections describe how to align items and distribute extra space
+along the main and cross axes.
+
+### Along the main axis
+
+Use `justifyContent` to distribute items along the main axis. The following
+table shows the behavior when the direction is `Row`.
+
+|---|---|
+| |  |
+| `Start` |  |
+| `Center` |  |
+| `End` |  |
+| `SpaceBetween` |  |
+| `SpaceAround` |  |
+| `SpaceEvenly` |  |
+
+### Along the cross axis
+
+Use `alignItems` to align items along the cross axis within a single line. This
+behavior can be overridden by individual items using the
+[`alignSelf` modifier](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/item-behavior#item-alignment).
+
+The following images show the behavior when the direction is `Row`:
+
+|---|---|---|---|---|---|
+|  |  |  |  |  |  |
+| | `Start` | `End` | `Center` | `Stretch` | `Baseline` |
+
+Use `alignContent` to align lines to the cross axis and to distribute extra
+space between lines. This property only applies when there are multiple lines
+(wrapping is enabled). The following images show the behavior when the direction
+is `Row`:
+
+|---|---|---|---|---|---|---|
+|  |  |  |  |  |  |  |
+| | `Start` | `End` | `Center` | `Stretch` | `SpaceBetween` | `SpaceAround` |
+
+## Wrap items
+
+Wrapping lets a `FlexBox` container become multi-line, moving items that don't
+fit onto a new row or column along the cross-axis. Configure wrapping behavior
+using `wrap`.
+
+|---|---|
+| **`FlexWrap` value** | **Example using direction `Row`** |
+| `NoWrap` (default): Prevents items from wrapping. Items overflow if the main size is insufficient. |  |
+| `Wrap`: When there is insufficient space for an item (plus any [gap](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/container-behavior#add-gaps)), a new line is created in the direction of the cross axis. For example, if the direction is `Row`, a new line is added **below**. |  |
+| `WrapReverse`: The same as `Wrap`, except the new line is added in the opposite direction to the cross axis. For example, if the direction is `Row`, a new line is added **above**. |  |
+
+The following example shows how the `FlexBox` wrapping algorithm works. The
+`FlexBox` container has a main size of `100dp`, with `wrap` set to
+`FlexWrap.Wrap` and a gap of `8dp`. It contains three items with `basis` `20dp`,
+`40dp`, and `50dp`, respectively.
+
+There is `100dp` available space in the line. Child 1 is `20dp`.
+There is space, so Child 1 is placed into the line.
+ **Figure 1.** First item placed in the `FlexBox` container.
+
+There is `80dp` available space in the line. The gap is `8dp`. Child 2 is
+`40dp`. The required space is `48dp`. There is space, so the gap and Child 2
+are placed into the line.
+ **Figure 2.** Second item placed in the `FlexBox` container after the first item.
+
+There is `32dp` available space in the line. The gap is `8dp`. Child 3 is
+`50dp`. The required space is `58dp`. There is not enough space in the current
+line, so Child 3 is placed in a new line.
+ **Figure 3.** Third item placed on a new line because it doesn't fit on the first line.
+
+## Add gaps between items
+
+Add gaps between rows and columns using `rowGap` and `columnGap`. This is useful
+to avoid adding spacing modifiers to children.
+
+|---|---|---|
+|  |  |  |
+| `rowGap` adds vertical space between items and lines. | `columnGap` adds horizontal space between items and lines. | `gap` is a convenience function that adds both `columnGap` and `rowGap`. |
\ No newline at end of file
diff --git a/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/get-started.md b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/get-started.md
new file mode 100644
index 0000000..44d6d13
--- /dev/null
+++ b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/get-started.md
@@ -0,0 +1,69 @@
+This page describes how to implement basic `FlexBox` layouts.
+
+## Set up project
+
+1. Add the [`androidx.compose.foundation.layout`](https://developer.android.com/jetpack/androidx/versions) library to your project's
+ `lib.versions.toml`.
+
+ [versions]
+ compose = "1.13.0-beta01"
+
+ [libraries]
+ androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "compose" }
+
+2. Add the library dependency to your app's `build.gradle.kts`.
+
+ dependencies {
+ implementation(libs.androidx.compose.foundation.layout)
+ }
+
+## Create basic FlexBox layouts
+
+**Example 1** : `FlexBox` lays out two `Text` elements that are centrally
+aligned.
+
+
+```kotlin
+FlexBox(
+ config = {
+ direction(FlexDirection.Column)
+ alignItems(FlexAlignItems.Center)
+ }
+) {
+ Text(text = "Hello", fontSize = 48.sp)
+ Text(text = "World!", fontSize = 48.sp)
+}
+```
+
+
+
+
+
+**Example 2** : `FlexBox` wraps five items onto two rows and grows them unequally
+to fill the available space on each row. There is an `8.dp`
+gap, both vertically and horizontally, between the items.
+
+
+```kotlin
+FlexBox(
+ config = {
+ wrap(FlexWrap.Wrap)
+ gap(8.dp)
+ }
+) {
+ // All boxes have an intrinsic width of 100.dp
+ // Some grow to fill any remaining space on the row.
+ RedRoundedBox()
+ BlueRoundedBox()
+ GreenRoundedBox(modifier = Modifier.flex { grow(1.0f) })
+ OrangeRoundedBox(modifier = Modifier.flex { grow(1.0f) })
+ PinkRoundedBox(modifier = Modifier.flex { grow(1.0f) })
+}
+```
+
+
+
+
+
+To learn more about `FlexBox` behavior, see [Set container behavior](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/container-behavior) and [Set
+item behavior](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/item-behavior).
\ No newline at end of file
diff --git a/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/index.md b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/index.md
new file mode 100644
index 0000000..64bd05f
--- /dev/null
+++ b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/index.md
@@ -0,0 +1,78 @@
+[`FlexBox`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/FlexBox.composable#FlexBox(androidx.compose.ui.Modifier,androidx.compose.foundation.layout.FlexBoxConfig,kotlin.Function1)) is a container that lays out items in a single direction. It can
+resize, wrap, align, and distribute space among items to optimally fill the
+available space. It's a useful layout for different sized items and for resizing
+items when the available space changes.
+
+With `FlexBox`, you can:
+
+- Control how items grow and shrink to fill the available space
+- Wrap items onto new rows or columns when there isn't enough space for them
+- Distribute extra space between items using convenient presets
+
+## When to use FlexBox
+
+`FlexBox` is usually used to display a small number of items *within* an
+overall screen layout. For an overall screen layout,
+`Grid` is usually a better choice. `FlexBox` does not support lazy-loading of
+items. To display large numbers of items, use [lazy lists and grids](https://developer.android.com/develop/ui/compose/lists). If you
+need to wrap items, use `FlexBox` instead of `FlowRow` and `FlowColumn`.
+
+## Terminology and concepts
+
+> [!IMPORTANT]
+> **Key Point:** `FlexBox` is heavily influenced by the [CSS Flexible Box Layout specification](https://www.w3.org/TR/css-flexbox-1/) and has almost identical concepts, terminology, and behavior. If you're familiar with `display: flex`, you'll find `FlexBox`'s properties and behavior almost identical.
+
+`FlexBox` lays out its items in either horizontal or vertical *lines* . This
+direction of these lines establishes the *main axis* . 90 degrees to the main
+axis is the *cross axis* . The length of the `FlexBox` along the main axis is
+known as the *main size* . The corresponding cross axis length is known as the
+*cross size* . These sizes and axes form the basis of `FlexBox`'s behavior.
+
+
+ **Figure 1.** Axes and sizes when the `FlexBox` direction is `Row`.  **Figure 2.** Axes and sizes when the `FlexBox` direction is `Column`.
+
+
+
+### Apply properties
+
+You can apply `FlexBox` properties in two ways:
+
+- To the `FlexBox` container using `FlexBox(config)`
+- To an item inside the `FlexBox` using `Modifier.flex`
+
+| **Container properties (`config`**) | **Item properties (`Modifier.flex`**) |
+|---|---|
+| - `direction` - the item layout direction - `wrap` - whether to wrap items if the **main size** is insufficient - `justifyContent` - how to **distribute** items along the **main axis** - `alignItems` - how to **align** items along the **cross axis** - `alignContent` - how to distribute extra space from the **cross size** when there are multiple lines - `rowGap` / `columnGap` - adds space between items and lines See [Set container behavior](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/container-behavior) for more information about these properties. | - `basis` - the size of the item before any extra space from the **main size** is distributed - `grow` - the share of extra space from the **main size** that this item should receive - `shrink` - the share of space deficit from the **main size** that this item should receive - `alignSelf` - how to distribute extra space from the **cross size** to this item, overrides `alignItems` - `order` - controls the layout order See [Set item behavior](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/item-behavior) for more information about these properties. |
+
+### Understand the `FlexBox` layout algorithm
+
+One of `FlexBox`'s most powerful features is its ability to resize its children
+to best fit the space available to it. Understanding how `FlexBox` does this can
+help you set `FlexBox` properties to optimize your UI for all possible sizes.
+
+`FlexBox`'s layout algorithm works in the following way:
+
+1. **Calculate child base size** : Use the child's [`basis` value](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/item-behavior#set-initial-size)
+ to calculate its initial size along the main axis before any extra space is
+ distributed.
+
+2. **Sort the children** : Sort the children by their [`order`](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/item-behavior#item-order) values, if
+ present.
+
+3. **Build lines** : For each child, check if its initial size plus
+ [`gap`](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/container-behavior#add-gaps) will fit into the remaining space on the current line.
+ If so, place this child into the line. If not, place it onto a new line if
+ [wrapping is enabled](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/container-behavior#wrap-items), or place the item into the current line
+ where it will overflow (it will be partially obscured by the edge of the
+ container).
+
+4. **Align or resize items in the main axis** : For each line, distribute extra
+ space *to* or between items by [resizing](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/item-behavior#item-size) or
+ [aligning](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/container-behavior#main-axis) them.
+
+5. **Align or resize items in the cross axis** : For each line, distribute extra
+ space to or between items and lines by [stretching or aligning
+ them](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/container-behavior#cross-axis).
+
+Now that you're familiar with `FlexBox` concepts, see [Get started](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/get-started) to
+create a basic `FlexBox`.
\ No newline at end of file
diff --git a/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/item-behavior.md b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/item-behavior.md
new file mode 100644
index 0000000..e4a5c0f
--- /dev/null
+++ b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/item-behavior.md
@@ -0,0 +1,170 @@
+Use `Modifier.flex` to control how an item changes size, order, and is aligned
+inside a `FlexBox`.
+
+## Item size
+
+Use the `basis`, `grow`, and `shrink` functions to control an item's size.
+
+
+```kotlin
+FlexBox {
+ RedRoundedBox(
+ modifier = Modifier.flex {
+ basis(FlexBasis.Auto)
+ grow(1.0f)
+ shrink(0.5f)
+ }
+ )
+}
+```
+
+
+
+### Set initial size
+
+Use `basis` to specify the item's initial size before any extra space is
+distributed. You can think of this as the item's *preferred* size.
+
+|---|---|---|---|
+| **Value type** | **Behavior** | **Code snippet** Note: The boxes have a maximum intrinsic size of `100dp` | **Example using container width `600dp`** |
+| `Auto` (default) | Use the item's maximum intrinsic size. For example, a `Text` composable's maximum intrinsic width is the width of all its text on a single line - no wrapping. | ```kotlin FlexBox { RedRoundedBox( Modifier.flex { basis(FlexBasis.Auto) } ) BlueRoundedBox( Modifier.flex { basis(FlexBasis.Auto) } ) } ``` |  |
+| Fixed `dp` | A fixed size in Dp. | ```kotlin FlexBox { RedRoundedBox( Modifier.flex { basis(200.dp) } ) BlueRoundedBox( Modifier.flex { basis(100.dp) } ) } ``` |  |
+| Percentage | A percentage of the container size. | ```kotlin FlexBox { RedRoundedBox( Modifier.flex { basis(0.7f) } ) BlueRoundedBox( Modifier.flex { basis(0.3f) } ) } ``` |  |
+
+If the basis value is less than the item's intrinsic minimum size, the intrinsic
+minimum size is used instead. For example, if a `Text` item that contains a word
+requires `50dp` to display, but also has `basis = 10.dp`, a
+value of `50dp` is used.
+
+### Grow items when there's space
+
+Use `grow` to specify how much an item grows when there is extra space. This is
+space remaining in the `FlexBox` container after all the items' `basis` values
+have been added up. The `grow` value indicates *how much* of the extra space a
+given child will receive, relative to its siblings. By default, items won't
+grow.
+
+The following example shows a `FlexBox` with three child items. Each has a basis
+value of `100dp`. The first child has a positive `grow` value. Since there is
+only one child with a `grow` value, the actual value is irrelevant - as long as
+it's positive, the child receives all the extra space.
+
+The images show the `FlexBox` behavior when its container size is `600dp`.
+
+|---|---|
+| ```kotlin FlexBox { RedRoundedBox( title = "400dp", modifier = Modifier.flex { grow(1f) } ) BlueRoundedBox(title = "100dp") GreenRoundedBox(title = "100dp") } ``` | Each child has a basis value of `100dp`. There is `300dp` of extra space.  Child 1 grows by `300dp` to fill the extra space.  |
+
+In the following example, the container size and `basis` size are the same. The
+difference is that each child has a different `grow` value.
+
+|---|---|
+| ```kotlin FlexBox { RedRoundedBox( title = "150dp", modifier = Modifier.flex { grow(1f) } ) BlueRoundedBox( title = "200dp", modifier = Modifier.flex { grow(2f) } ) GreenRoundedBox( title = "250dp", modifier = Modifier.flex { grow(3f) } ) } ``` | Each child has a basis value of `100dp`. There is `300dp` of extra space.  The total grow value is 6. Child 1 grows by (1 / 6) \* 300 = `50dp` Child 2 grows by (2 / 6) \* 300 = `100dp` Child 3 grows by (3 / 6) \* 300 = `150dp`  |
+
+### Shrink items when there's insufficient space
+
+Use `shrink` to specify how much an item shrinks when the `FlexBox` container
+has insufficient space for all the items. `shrink` works the same way as `grow`
+except that, instead of distributing *extra space* to items, the *space deficit*
+is distributed to items. The `shrink` value specifies how much of the space
+deficit the item receives, or rather, how much the item will shrink by. By
+default, items have a `shrink` value of `1f`, meaning they shrink equally.
+
+The following example shows two `Text` composables with the same text. The first
+child has a shrink value of `1f`, meaning it shrinks to absorb all the space
+deficit.
+
+
+```kotlin
+FlexBox {
+ Text(
+ "The quick brown fox",
+ fontSize = 36.sp,
+ modifier = Modifier
+ .background(PastelRed)
+ .flex { shrink(1f) }
+ )
+ Text(
+ "The quick brown fox",
+ fontSize = 36.sp,
+ modifier = Modifier
+ .background(PastelBlue)
+ .flex { shrink(0f) }
+ )
+}
+```
+
+
+
+As the container size shrinks, Child 1 shrinks.
+
+|---|---|
+| **Container size** | **FlexBox UI** |
+| `700dp` |  |
+| `500dp` |  |
+| `450dp` |  |
+
+## Item alignment
+
+Use `alignSelf` to control how an item is aligned to the cross axis. This
+overrides the [`alignItems` property](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/container-behavior#align-distribute) of the container for this item. It
+has all the same possible values, with the addition of `Auto` which inherits the
+behavior of the `FlexBox` container.
+
+For example, this `FlexBox` has `alignItems` set to `Start` and five children
+which override the cross axis alignment.
+
+
+```kotlin
+FlexBox(
+ config = {
+ alignItems(FlexAlignItems.Start)
+ }
+) {
+ RedRoundedBox()
+ BlueRoundedBox(modifier = Modifier.flex { alignSelf(FlexAlignSelf.Center) })
+ GreenRoundedBox(modifier = Modifier.flex { alignSelf(FlexAlignSelf.End) })
+ PinkRoundedBox(modifier = Modifier.flex { alignSelf(FlexAlignSelf.Stretch) })
+ OrangeRoundedBox(modifier = Modifier.flex { alignSelf(FlexAlignSelf.Baseline) })
+}
+```
+
+
+
+
+
+## Item order
+
+By default, `FlexBox` lays out items in the order that they are declared in
+code. Override this behavior using `order`.
+
+The default value for `order` is zero, and `FlexBox` sorts items based on this
+value in ascending order. Any items that have the same `order` value are
+laid out in the same order they are declared in. Use negative and positive
+`order` values to move items to the start or end of a layout without changing
+where they are declared.
+
+The following example shows two child items. The first has the default `order`
+of zero, and the second has an order of `-1`. After sorting, Child 1 appears
+after Child 2.
+
+
+```kotlin
+FlexBox {
+ // Declared first, but will be placed after visually
+ RedRoundedBox(
+ title = "World"
+ )
+
+ // Declared second, but will be placed first visually
+ BlueRoundedBox(
+ title = "Hello",
+ modifier = Modifier.flex {
+ order(-1)
+ }
+ )
+}
+```
+
+
+
+
\ No newline at end of file
diff --git a/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/container-properties.md b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/container-properties.md
new file mode 100644
index 0000000..fc4e3a5
--- /dev/null
+++ b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/container-properties.md
@@ -0,0 +1,415 @@
+You can define a Grid container configuration to create flexible layouts
+that respond to different screen sizes and content types.
+This page describes how to do the following:
+
+- [Define a grid](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid/container-properties#grid-definition): Set up the basic structure of rows and columns.
+- [Place items in a grid](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid/container-properties#item-placement): Understand how items are placed into grid cells and how to change flow direction.
+- [Manage track sizing](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid/container-properties#grid-track-size): Use fixed, percentage, flexible, and intrinsic sizing to set track sizes.
+- [Set gaps](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid/container-properties#grid-gap): Manage the "gutters" between rows and columns.
+
+## Define a grid
+
+A grid consists of columns and rows.
+The [`Grid`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/Grid.composable#Grid(kotlin.Function1,androidx.compose.ui.Modifier,kotlin.Function1)) composable has a `config` parameter
+that accepts a lambda to define the columns and rows
+within [`GridConfigurationScope`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridConfigurationScope).
+The following example defines a grid that has three rows and two columns,
+each with a fixed size specified in [`Dp`](https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/Dp):
+
+
+```kotlin
+Grid(
+ config = {
+ repeat(2) {
+ column(160.dp)
+ }
+ repeat(3) {
+ row(90.dp)
+ }
+ }
+) {
+}
+```
+
+
+
+## Place items in a grid
+
+`Grid` takes the UI elements
+in the `content` lambda and places them into grid cells.
+The grid lays out items regardless of
+whether you have explicitly defined the rows and columns.
+By default,
+`Grid` tries to place a UI element in the available grid cell in the row;
+if it can't, it places it in an available grid cell in the next row.
+If there are no empty cells, `Grid` creates a new row.
+
+In the following example, the grid has six grid cells
+and places a card into each one (Figure 1).
+Each grid cell is `160dp` x `90dp`,
+making the total grid size `320dp` x `270dp`.
+
+
+```kotlin
+Grid(
+ config = {
+ repeat(2) {
+ column(160.dp)
+ }
+ repeat(3) {
+ row(90.dp)
+ }
+ }
+) {
+ Card1()
+ Card2()
+ Card3()
+ Card4()
+ Card5()
+ Card6()
+}
+```
+
+
+
+ **Figure 1**. Six cards are placed in a grid that has three rows and two columns.
+
+To change this default behavior to filling by column,
+set the [`flow`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridConfigurationScope#flow()) property to [`GridFlow.Column`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridFlow#Column()).
+
+
+```kotlin
+Grid(
+ config = {
+ repeat(2) {
+ column(160.dp)
+ }
+ repeat(3) {
+ row(90.dp)
+ }
+ gap(8.dp)
+ flow = GridFlow.Column // Grid tries to place items to fill the column
+ },
+) {
+ Card1()
+ Card2()
+ Card3()
+ Card4()
+ Card5()
+ Card6()
+}
+```
+
+
+
+ **Figure 2** . `GridFlow.Row` (left) and `GridFlow.Column` (right).
+
+## Manage track sizing
+
+Rows and columns are collectively referred to as a [grid track](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid#grid-track).
+You can specify the size of a grid track using one of the following methods:
+
+- **Fixed** (`Dp`): Allocates a specific size (e.g., `column(180.dp)`).
+- **Percentage** (`Float`): Allocates a percentage of the total available space from `0.0f` to `1.0f` (e.g., `row(0.5f)` for 50%).
+- **Flexible** ([`Fr`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/Fr)): Distributes remaining space proportionally after fixed and percentage tracks are calculated. For example, if two rows are set to `1.fr` and `3.fr`, the latter receives 75% of the remaining height.
+- **Intrinsic** : Sizes the track based on the content inside it. For more information, see [Determine grid track size intrinsically](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid/container-properties#intrisic-grid-track-size).
+
+The following example uses the different track sizing options
+to define the row heights:
+
+
+```kotlin
+Grid(
+ config = {
+ column(1f)
+
+ row(100.dp)
+ row(0.2f)
+ row(1.fr)
+ row(GridTrackSize.Auto)
+ },
+ modifier = Modifier.height(480.dp)
+) {
+ PastelRedCard("Fixed(100.dp)")
+ PastelGreenCard("Percentage(0.2f)")
+ PastelBlueCard("Flex(1.fr)")
+ PastelYellowCard("Auto")
+
+}
+```
+
+
+
+ **Figure 3** . Row heights defined using the four primary track sizing options in `Grid`.
+
+### Set the minimum size for flexible grid tracks
+
+When a grid container has no remaining space,
+a standard flexible track can shrink to `0.dp`.
+To prevent this and ensure content isn't crushed,
+use [`GridTrackSize.MinMax`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridTrackSize#MinMax(androidx.compose.ui.unit.Dp,androidx.compose.foundation.layout.Fr))
+to enforce an explicit minimum size while keeping the track flexible.
+
+The following example allocates at least `100.dp` to the first row:
+
+
+```kotlin
+Grid(
+ config = {
+ column(1f)
+ // The first row has a minimum height of 100.dp and can expand to
+ // the half of the remaining space.
+ row(GridTrackSize.MinMax(100.dp, 1.fr))
+ // The second row takes the half of the remaining space.
+ row(1.fr)
+ // The third row has a fixed height of 200.dp.
+ row(200.dp)
+ },
+ modifier = Modifier.size(360.dp) // Total grid height is 360.dp
+) {
+ PastelRedCard("MinMax(100.dp, 1.fr)")
+ PastelGreenCard("Flex(1.fr)")
+ PastelBlueCard("Fixed(200.dp)")
+}
+```
+
+
+
+ **Figure 4** . The first row has at least `100.dp` height.
+
+### Set the minimum grid track size to place lazy lists
+
+Standard flexible tracks automatically query the intrinsic sizes of
+their children to establish a base size.
+However, Jetpack Compose prohibits querying the intrinsic sizes of
+[`SubcomposeLayout`](https://developer.android.com/reference/kotlin/androidx/compose/ui/layout/SubcomposeLayout.composable#SubcomposeLayout(androidx.compose.ui.Modifier,kotlin.Function2)), which backs components,
+such as [`LazyColumn`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyColumn.composable) and [`LazyRow`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyRow.composable).
+
+Placing a lazy list inside a standard flexible track causes
+an [`IllegalStateException`](https://developer.android.com/reference/java/lang/IllegalStateException) crash.
+To safely place lazy lists inside a flexible grid track,
+use `MinMax` with an explicit minimum size (such as `0.dp`)
+to bypass the intrinsic measurement pass.
+
+
+```kotlin
+Grid(
+ config = {
+ column(1f)
+ // The first row's height is determined by the height of the Text composable.
+ row(GridTrackSize.Auto)
+ // The second row occupies the remaining space, allowing the LazyColumn to scroll.
+ row(GridTrackSize.MinMax(0.dp, 1.fr))
+
+ gap(8.dp)
+ },
+ modifier = Modifier.size(width = 170.dp, height = 240.dp)
+) {
+ Text("LazyColumn in a Grid")
+ // The LazyColumn is placed in the second row, filling the remaining space.
+ LazyColumn(verticalArrangement = Arrangement.spacedBy(4.dp)) {
+ items(100) { number ->
+ PastelGreenCard("Card $number")
+ }
+ }
+}
+```
+
+
+
+ **Figure 5** . `LazyColumn` in a grid cell.
+
+### Determine grid track size intrinsically
+
+You can use [intrinsic sizing](https://developer.android.com/develop/ui/compose/layouts/intrinsic-measurements) for a `Grid`
+when you want the layout to adapt to the content,
+rather than forcing it into a fixed container.
+The grid track size is determined with the following values:
+
+- [`GridTrackSize.MaxContent`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridTrackSize#MaxContent()): Use the content's maximum intrinsic size (e.g., the width is determined by the full length of the text in a text block with no wrapping).
+- [`GridTrackSize.MinContent`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridTrackSize#MinContent()): Use the content's minimum intrinsic size (e.g., the width is determined by the longest single word in a text block).
+- [`GridTrackSize.Auto`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridTrackSize#Auto()): Use a flexible size for a track that adapts based on available space. It behaves like `MaxContent` by default, but shrinks and wraps its content to fit within the parent container.
+
+The following example places two texts side by side.
+The column size for the first text is determined
+by the required minimum width to display the text,
+and the second column width depends on the required maximum width of the text.
+
+
+```kotlin
+Grid(
+ config = {
+ column(GridTrackSize.MinContent)
+ column(GridTrackSize.MaxContent)
+ row(1.0f)
+ },
+ modifier = Modifier.width(480.dp)
+) {
+ Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras imperdiet.")
+ Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras imperdiet.")
+}
+```
+
+
+
+ **Figure 5**. Intrinsic sizes specified in the columns.
+
+## Set gaps between rows and columns
+
+Once your grid tracks are sized,
+you can modify the [grid gap](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid#grid-gap) to refine the spacing between the tracks.
+You can specify the column gap with the [`columnGap`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridConfigurationScope#columnGap(androidx.compose.ui.unit.Dp)) function,
+and the row gap with [`rowGap`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridConfigurationScope#rowGap(androidx.compose.ui.unit.Dp)). In the following example,
+there is a `16dp` gap between each row,
+and an `8dp` gap between each column (Figure 5).
+
+
+```kotlin
+Grid(
+ config = {
+ repeat(2) {
+ column(160.dp)
+ }
+ repeat(3) {
+ row(90.dp)
+ }
+ rowGap(16.dp)
+ columnGap(8.dp)
+ }
+) {
+ Card1()
+ Card2()
+ Card3()
+ Card4()
+ Card5()
+ Card6()
+}
+```
+
+
+
+ **Figure 6**. Gaps between rows and columns.
+
+You can also use the convenience function [`gap`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridConfigurationScope#gap(androidx.compose.ui.unit.Dp))
+to define gaps of the same column and row size,
+and to define column and gap sizes separately using a single function.
+The following code adds `8dp` gaps to the grid:
+
+
+```kotlin
+Grid(
+ config = {
+ repeat(2) {
+ column(160.dp)
+ }
+ repeat(3) {
+ row(90.dp)
+ }
+ gap(8.dp) // Equivalent to columnGap(8.dp) and rowGap(8.dp)
+ }
+) {
+ Card1()
+ Card2()
+ Card3()
+ Card4()
+ Card5()
+ Card6()
+}
+```
+
+
+
+## Define grid areas with named areas
+
+Named areas allow you to attach names to groups of grid cells,
+which are called [grid areas](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid#grid-area).
+You can use these names instead of [coordinate indexes](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid/item-properties#position)
+when placing UI elements in the grid.
+
+Using named areas has two main benefits to code readability:
+
+- When defining the grid layout, the purpose and placement of the expected content is clear.
+- When adding the content, the purpose of that content is clear.
+
+To organize complex layouts clearly, you can decouple your physical grid
+structure from child placement by defining semantic grid areas.
+
+Inside the `config` lambda, use the [`area`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridConfigurationScope#area(kotlin.Any,kotlin.Int,kotlin.Int,kotlin.Int,kotlin.Int)) function in
+`GridConfigurationScope` to register named areas in the grid.
+You can then assign child composables to these areas
+using the [`gridItem`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridScope#(androidx.compose.ui.Modifier).gridItem(kotlin.Any,androidx.compose.ui.Alignment)) modifier
+with the corresponding area identifier. The `area` function maps
+a semantic identifier (such as an `enum` class value or a string key) to
+a set of physical grid coordinates. Grid lines and indexes are **1-based**
+(that is, the first row is `1`, and the first column is `1`).
+
+For example, you define a grid that has four area IDs:
+
+
+```kotlin
+/**
+ * An enum representing the IDs for named areas within the grid.
+ */
+enum class GridAreaNames {
+ Area1,
+ Area2,
+ Area3,
+ Area4
+}
+```
+
+
+
+Provide the name for the area using the `areaId` parameter along with the area's
+cell coordinates and spans.
+The `gridItem` modifier uses the `areaId` as a key
+to assign each child item to its designated grid area,
+as shown in the following example:
+
+
+```kotlin
+Grid(
+ config = {
+ // Define a single column that takes all available width.
+ repeat(2) { column(0.5f) }
+
+ // Define four rows, each taking 25% of the total height.
+ repeat(4) { row(0.25f) }
+
+ // Define named grid areas by associating an areaId with specific row and column indices.
+ // Row and column indices are 1-based.
+ area(areaId = GridAreaNames.Area1, row = 1, column = 1, columnSpan = 2)
+ area(areaId = GridAreaNames.Area2, row = 2, column = 1, rowSpan = 3)
+ area(areaId = GridAreaNames.Area3, rows = 2..3, columns = 2..2)
+ area(areaId = GridAreaNames.Area4, row = 4, column = 2)
+
+ gap(4.dp)
+ },
+ modifier = Modifier.size(360.dp)
+) {
+ PastelRedCard(
+ "Area 1",
+ // Use Modifier.gridItem(areaId) to place this composable into the
+ // grid area defined with the matching ID in the config block.
+ modifier = Modifier.gridItem(areaId = GridAreaNames.Area1)
+ )
+ PastelGreenCard(
+ "Area 2",
+ modifier = Modifier.gridItem(areaId = GridAreaNames.Area2)
+ )
+ PastelBlueCard(
+ "Area 3",
+ modifier = Modifier.gridItem(areaId = GridAreaNames.Area3)
+ )
+ PastelYellowCard(
+ "Area 4",
+ modifier = Modifier.gridItem(areaId = GridAreaNames.Area4)
+ )
+}
+```
+
+
+
+By using named areas, you can reorganize or adjust the physical layout grid (for
+example, changing rows, columns, or track sizes) in the `config` lambda
+without needing to modify the order or parameters of the child composables.
\ No newline at end of file
diff --git a/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/get-started.md b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/get-started.md
new file mode 100644
index 0000000..d3e010d
--- /dev/null
+++ b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/get-started.md
@@ -0,0 +1,51 @@
+This page describes how to implement basic [`Grid`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/Grid.composable#Grid(kotlin.Function1,androidx.compose.ui.Modifier,kotlin.Function1)) layouts.
+
+## Set up project
+
+1. Add the [`androidx.compose.foundation.layout`](https://developer.android.com/jetpack/androidx/versions) library to your project's
+ `lib.versions.toml`.
+
+ [versions]
+ compose = "1.13.0-beta01"
+
+ [libraries]
+ androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "compose" }
+
+2. Add the library dependency to your app's `build.gradle.kts`.
+
+ dependencies {
+ implementation(libs.androidx.compose.foundation.layout)
+ }
+
+## Create a basic grid
+
+The following example creates a basic 2x3 grid,
+with the columns and rows having a fixed size of `100.dp`.
+
+
+```kotlin
+Grid(
+ config = {
+ repeat(2) {
+ column(100.dp)
+ }
+ repeat(3) {
+ row(100.dp)
+ }
+ }
+) {
+ Card1(containerColor = PastelRed)
+ Card2(containerColor = PastelGreen)
+ Card3(containerColor = PastelBlue)
+ Card4(containerColor = PastelPink)
+ Card5(containerColor = PastelOrange)
+ Card6(containerColor = PastelYellow)
+}
+```
+
+
+
+ **Figure 1**. A basic grid consists of rows and columns with fixed size.
+
+To learn how to implement more advanced grids,
+see [Set container properties](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid/container-properties) and [Set item properties](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid/item-properties).
\ No newline at end of file
diff --git a/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/index.md b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/index.md
new file mode 100644
index 0000000..0789e46
--- /dev/null
+++ b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/index.md
@@ -0,0 +1,70 @@
+[`Grid`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/Grid.composable#Grid(kotlin.Function1,androidx.compose.ui.Modifier,kotlin.Function1)) is a Jetpack Compose API
+that lets you flexibly implement a two-dimensional layout.
+With this API, you can display items in multi-column
+or multi-row layouts that adapt to the available container size.
+ **Figure 1.** A flexible and adaptive two-dimensional layout with `Grid`.
+
+## How is Grid different from similar composables?
+
+Compose already offers similar components, such as [`LazyVerticalGrid`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/grid/LazyVerticalGrid.composable#LazyVerticalGrid(androidx.compose.foundation.lazy.grid.GridCells,androidx.compose.ui.Modifier,androidx.compose.foundation.lazy.grid.LazyGridState,androidx.compose.foundation.layout.PaddingValues,kotlin.Boolean,androidx.compose.foundation.layout.Arrangement.Vertical,androidx.compose.foundation.layout.Arrangement.Horizontal,androidx.compose.foundation.gestures.FlingBehavior,kotlin.Boolean,androidx.compose.foundation.OverscrollEffect,kotlin.Function1)).
+These components are mainly for visualization of large, homogeneous data sets---
+for example, displaying a content catalog in a video streaming app.
+These components are NOT designed
+for the structural layout of a screen or complex component.
+
+You can also implement a two-dimensional layout
+by combining multiple `Row` and `Column` composables.
+However, this approach has some downsides,
+such as deep hierarchies and difficulties in adaptability.
+
+The following table provides an overview
+of which layouts are suitable for each API:
+
+| Component | Purpose |
+|---|---|
+| `LazyVerticalGrid`, `LazyStaggeredGrid`, `LazyHorizontalGrid` | Visualization of large, homogeneous data sets that require lazy loading. |
+| `Row`, `Column`, `FlexBox` | One-dimensional layout |
+| `Grid` | Two-dimensional layout |
+
+> [!NOTE]
+> **Note:** `Grid` doesn't support lazy loading.
+
+## Terminology
+
+Familiarize yourself with the following terminology
+to understand how `Grid` works.
+
+### Grid line
+
+A grid is made up of lines, which run horizontally and vertically.
+If your grid has three rows, it has four horizontal lines,
+including the one after the last row.
+In the following image, each dotted line represents a grid line:
+ **Figure 2**. The grid consists of four horizontal lines and three vertical lines.
+
+### Grid track
+
+A grid track is the space between two grid lines.
+A row track is between two horizontal lines,
+and a column track is between two vertical lines.
+To define the size of these tracks,
+assign a size to them when you create the grid.
+ **Figure 3**. A grid track for the first row.
+
+### Grid cell
+
+A grid cell is the intersection of a row and column track.
+ **Figure 4**. A grid cell that is an intersection of the second row and the second column.
+
+### Grid area
+
+A grid area consists of several grid cells.
+You can define a grid area by making an item span multiple tracks.
+ **Figure 5**. A grid area that consists of four grid cells.
+
+### Grid gap
+
+A grid gap is the gutter between grid tracks.
+You can't place a UI element into a gap,
+but you can span a UI element across it.
+ **Figure 6**. A grid gap between the first column and the second column.
\ No newline at end of file
diff --git a/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/item-properties.md b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/item-properties.md
new file mode 100644
index 0000000..bf4205d
--- /dev/null
+++ b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/item-properties.md
@@ -0,0 +1,170 @@
+While the `Grid` config defines the overall structure,
+you use the [`gridItem`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridScope#(androidx.compose.ui.Modifier).gridItem(kotlin.Int,kotlin.Int,kotlin.Int,kotlin.Int,androidx.compose.ui.Alignment)) modifier to control the position, spanning,
+and alignment of items within that structure.
+
+## Set the item position
+
+Place an item into a specific track or cell
+with the `row` and `column` parameters.
+Coordinate indexes work best for straightforward grid layouts.
+For larger or more complex layouts, define [named areas](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid/container-properties#named-area) instead.
+
+The `row` and `column` parameters specify the row and column track indexes
+that the item is placed in.
+Track indexes are 1-based---they start at one.
+Specifying only `row` or `column` (not both) places the item
+in the next available space in that track.
+Specifying both places the item into that cell.
+
+Use a positive integer to specify the track index from the start.
+For example, to place an item in the first row and column,
+use `gridItem(row = 1, column = 1)`.
+
+Use a negative integer to specify the track relative to the end.
+For example, to place an item in the second-to-last row and column, use
+`gridItem(row = -2, column = -2)`.
+
+In the following example, Card **#2** is placed
+in the second row and the second column.
+Card **#3** is assigned to the last row (indexed by -1),
+where it automatically occupies
+the first available column in that track (Figure 1).
+
+
+```kotlin
+Grid(
+ config = {
+ repeat(2) {
+ column(160.dp)
+ }
+ repeat(3) {
+ row(90.dp)
+ }
+ gap(8.dp)
+ }
+) {
+ Card1()
+ Card2(modifier = Modifier.gridItem(row = 2, column = 2))
+ Card3(modifier = Modifier.gridItem(row = -1, column = -2))
+}
+```
+
+
+
+ **Figure 1** . Card **#2** is placed in the grid cell in the second row and the second column, and Card **#3** is placed in the first column in the third row.
+
+## Span rows and columns
+
+Use the `rowSpan` and `columnSpan` parameters
+to span an item over multiple cells.
+You can place a UI element into a [grid area](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid#grid-area),
+which is the area consisting of several [grid cells](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid#grid-cell).
+The `gridItem` modifier lets you specify the grid area
+with the `rowSpan` and `columnSpan` parameters.
+In the following example,
+Card **#1** is placed in the area consisting of two rows and two columns
+(Figure 2).
+
+
+```kotlin
+Grid(
+ config = {
+ repeat(3) {
+ column(160.dp)
+ }
+ repeat(3) {
+ row(90.dp)
+ }
+ rowGap(8.dp)
+ columnGap(8.dp)
+ }
+) {
+ Card1(modifier = Modifier.gridItem(rowSpan = 2, columnSpan = 2))
+ Card2()
+ Card3()
+ Card4(modifier = Modifier.gridItem(columnSpan = 3))
+}
+```
+
+
+
+ **Figure 2** . Card **#4** spans three columns.
+
+## Set the alignment in a grid area
+
+You can set the alignment of the UI element in a grid area
+by specifying it in the `alignment` parameter of the [`gridItem`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridScope#(androidx.compose.ui.Modifier).gridItem(kotlin.Int,kotlin.Int,kotlin.Int,kotlin.Int,androidx.compose.ui.Alignment)) modifier.
+In the following example, **#1** is placed in the center of the grid area
+consisting of two columns and two rows.
+
+
+```kotlin
+Grid(
+ config = {
+ repeat(3) {
+ column(160.dp)
+ }
+ repeat(3) {
+ row(90.dp)
+ }
+ rowGap(8.dp)
+ columnGap(8.dp)
+ },
+) {
+ Text(
+ text = "#1",
+ modifier = Modifier
+ .gridItem(
+ rowSpan = 2,
+ columnSpan = 2,
+ alignment = Alignment.Center
+ ),
+ )
+ Card2()
+ Card3()
+ Card4(modifier = Modifier.gridItem(columnSpan = 3))
+}
+```
+
+
+
+ **Figure 3** . The Text with **#1** is placed in the center of the grid area consisting of two rows and two columns.
+
+## Auto-placement mixed with placed items
+
+A UI element in `Grid`
+that has no position specification undergoes auto-placement.
+This example shows how you can mix auto-placed elements
+and the UI elements with specified grid cells.
+Card **#2** and Card **#4** are placed in specified grid cells,
+and the other items are auto-placed.
+
+
+```kotlin
+Grid(
+ config = {
+ repeat(2) {
+ column(160.dp)
+ }
+ repeat(3) {
+ row(90.dp)
+ }
+ rowGap(16.dp)
+ columnGap(8.dp)
+ }
+) {
+ Card1()
+ Card2(modifier = Modifier.gridItem(row = 2, column = 2))
+ Card3()
+ Card4(modifier = Modifier.gridItem(row = 3, column = 1))
+ Card5()
+ Card6()
+}
+```
+
+
+
+ **Figure 4** . Card **#3** is placed next to Card **#1**, as it is an auto-placement.
\ No newline at end of file
diff --git a/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/mediaquery/index.md b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/mediaquery/index.md
new file mode 100644
index 0000000..37128b7
--- /dev/null
+++ b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/mediaquery/index.md
@@ -0,0 +1,333 @@
+> [!NOTE]
+> **Note:** The `mediaQuery` function and the related data types are experimental and subject to change. File any issues on the [issue tracker](https://issuetracker.google.com/issues?q=componentid:1876021).
+
+You need various types of information, such as device capability
+and app status, to update your app layout.
+Window width and height are the most commonly used information.
+In addition to that, you can refer to the following information:
+
+- Window posture
+- Pointing devices precision
+- Keyboard type
+- Whether the camera and microphone are supported by the device
+- The distance between a user and the device display
+
+Because the information is updated dynamically,
+you need to monitor it and trigger recomposition when any update happens.
+The [`mediaQuery`](https://developer.android.com/reference/kotlin/androidx/compose/ui/mediaQuery.composable#mediaQuery(kotlin.Function1)) function abstracts the details of the information retrieval
+and lets you focus on defining the condition to trigger the layout updates.
+
+> [!NOTE]
+> **Note:** "media" refers to the presentation medium that the UI is rendered on. The term "media" originates from [CSS media queries](https://www.w3.org/TR/mediaqueries-3/) which inspired the `mediaQuery` API.
+
+The following example switches the layout to `TabletopLayout`
+when the foldable posture is tabletop:
+
+
+```kotlin
+@Composable
+fun VideoPlayer(
+ // ...
+) {
+ // ...
+ if (mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop }) {
+ TabletopLayout()
+ } else {
+ FlatLayout()
+ }
+ // ...
+}
+```
+
+
+
+## Enable the `mediaQuery` function
+
+To enable the `mediaQuery` function,
+set the `isMediaQueryIntegrationEnabled` attribute of
+the [`ComposeUiFlags`](https://developer.android.com/reference/kotlin/androidx/compose/ui/ComposeUiFlags) object to `true`:
+
+
+```kotlin
+class MyApplication : Application() {
+ override fun onCreate() {
+ ComposeUiFlags.isMediaQueryIntegrationEnabled = true
+ super.onCreate()
+ }
+}
+```
+
+
+
+## Define a condition with parameters
+
+You can define a condition as a lambda
+that is evaluated within [`UiMediaScope`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope).
+The `mediaQuery` function evaluates the condition according to
+the current status and the device capabilities.
+The function returns a boolean value,
+so you can determine the layout with conditional branches
+like an `if` expression.
+Table 1 describes the parameters available in `UiMediaScope`.
+
+| Parameter | Value type | Description |
+|---|---|---|
+| `windowWidth` | [`Dp`](https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/Dp) | The current window width in dp. |
+| `windowHeight` | `Dp` | The current window height in dp. |
+| `windowPosture` | [`UiMediaScope.Posture`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.Posture) | The current posture of the application window. |
+| `pointerPrecision` | [`UiMediaScope.PointerPrecision`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.PointerPrecision) | The highest precision of the available pointing devices. |
+| `keyboardKind` | [`UiMediaScope.KeyboardKind`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.KeyboardKind) | The type of keyboard available or connected. |
+| `hasCamera` | `Boolean` | Whether the camera is supported on the device. |
+| `hasMicrophone` | `Boolean` | Whether the microphone is supported on the device. |
+| `viewingDistance` | [`UiMediaScope.ViewingDistance`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.ViewingDistance) | The typical distance between the user and the device screen. |
+
+A `UiMediaScope` object resolves the values of the parameters.
+The `mediaQuery` function uses [`LocalUiMediaScope.current`](https://developer.android.com/reference/kotlin/androidx/compose/ui/package-summary#LocalUiMediaScope())
+to access the `UiMediaScope` object,
+which represents the current device capabilities and context.
+This object is dynamically updated when any changes are made,
+such as when the user changes the device posture.
+The `mediaQuery` function then evaluates the `query` lambda
+with the updated `UiMediaScope` object and returns a boolean value.
+For example, the following snippet chooses between `TabletopLayout`
+and `FlatLayout` based on the `windowPosture` parameter value.
+
+
+```kotlin
+@Composable
+fun VideoPlayer(
+ // ...
+) {
+ // ...
+ if (mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop }) {
+ TabletopLayout()
+ } else {
+ FlatLayout()
+ }
+ // ...
+}
+```
+
+
+
+### Make a decision based on the window size
+
+[Window size classes](https://developer.android.com/develop/ui/compose/layouts/adaptive/use-window-size-classes) are a set of opinionated viewport breakpoints
+that help you design, develop, and test adaptive layouts.
+You can compare the two parameters representing the current window size
+with the threshold defined in the window size classes.
+The following example changes the number of panes according to the window width.
+[`WindowSizeClass`](https://developer.android.com/reference/androidx/window/core/layout/WindowSizeClass) class has constants for the thresholds
+of window size classes (Figure 1).
+
+The [`derivedMediaQuery`](https://developer.android.com/reference/kotlin/androidx/compose/ui/derivedMediaQuery.composable#derivedMediaQuery(kotlin.Function1)) function evaluates the `query` lambda
+and wraps the result in a [`derivedStateOf`](https://developer.android.com/develop/ui/compose/side-effects#derivedstateof).
+Because `windowWidth` and `windowHeight` can update frequently,
+call the `derivedMediaQuery` function instead of the `mediaQuery` function
+when you refer to those parameters in the `query` lambda.
+
+
+```kotlin
+val narrowerThanMedium by derivedMediaQuery {
+ windowWidth < WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND.dp
+}
+val narrowerThanExpanded by derivedMediaQuery {
+ windowWidth < WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND.dp
+}
+when {
+ narrowerThanMedium -> SinglePaneLayout()
+ narrowerThanExpanded -> TwoPaneLayout()
+ else -> ThreePaneLayout()
+}
+```
+
+
+
+**Figure 1**. Layout is updated according to the window width.
+
+### Update layout according to the window posture
+
+The `windowPosture` parameter describes the current window posture
+as a `UiMediaScope.Posture` object.
+You can check the current [posture](https://developer.android.com/develop/ui/compose/layouts/adaptive/foldables/learn-about-foldables) by comparing the parameter
+with the values defined in the `UiMediaScope.Posture` class.
+The following example switches layout according to the window posture:
+
+
+```kotlin
+when {
+ mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop } -> TabletopLayout()
+ mediaQuery { windowPosture == UiMediaScope.Posture.Book } -> BookLayout()
+ mediaQuery { windowPosture == UiMediaScope.Posture.Flat } -> FlatLayout()
+}
+```
+
+
+
+### Check the precision of the available pointing device
+
+A high precision pointing device helps users to point a UI element precisely.
+The precision of a pointing device depends on the device type.
+
+The `pointerPrecision` parameter describes the precision
+of the available pointing devices, such as a mouse and touchscreen.
+There are four values defined in the `UiMediaScope.PointerPrecision` class:
+[`Fine`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.PointerPrecision#Fine()), [`Coarse`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.PointerPrecision#Coarse()), [`Blunt`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.PointerPrecision#Blunt()), and [`None`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.PointerPrecision#None()).
+`None` means that no pointing device is available.
+The precision ranges from highest to lowest in this order:
+`Fine`, `Coarse`, and `Blunt`.
+
+If multiple pointing devices are available and their precisions are different,
+the parameter is resolved with the highest one.
+For example, if there are two pointing devices --- a `Fine` precision device and
+a `Blunt` precision device ---
+`Fine` is the value of the `pointerPrecision` parameter.
+
+The following example shows a larger button
+when the user is using a pointing device with low precision:
+
+
+```kotlin
+if (mediaQuery { pointerPrecision == UiMediaScope.PointerPrecision.Blunt }) {
+ LargeSizeButton()
+} else {
+ NormalSizeButton()
+}
+```
+
+
+
+### Check the available keyboard type
+
+The `keyboardKind` parameter represents the type of the available keyboards:
+[`Physical`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.KeyboardKind#Physical()), [`Virtual`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.KeyboardKind#Virtual()), and [`None`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.KeyboardKind#None()).
+If an on-screen keyboard is displayed and
+a hardware keyboard is available at the same time,
+the parameter is resolved as `Physical`.
+If neither is detected, `None` is the value of the parameter.
+The following example shows a message suggesting that users connect a keyboard
+when no keyboard is detected:
+
+
+```kotlin
+if (mediaQuery { keyboardKind == UiMediaScope.KeyboardKind.None }) {
+ SuggestKeyboardConnect()
+}
+```
+
+
+
+### Check if the device supports camera and microphone
+
+Some devices don't support cameras or microphones.
+You can check if the device supports a camera and a microphone
+with the `hasCamera` parameter and the `hasMicrophone` parameter.
+The following example shows buttons to use with camera and microphone
+when the device supports them:
+
+
+```kotlin
+Row {
+ OutlinedTextField(state = rememberTextFieldState())
+ // Show the MicButton when the device supports a microphone.
+ if (mediaQuery { hasMicrophone }) {
+ MicButton()
+ }
+ // Show the CameraButton when the device supports a camera.
+ if (mediaQuery { hasCamera }) {
+ CameraButton()
+ }
+}
+```
+
+
+
+### Adjust UI with the estimated viewing distance
+
+Viewing distance is a factor that helps determine layout.
+If the user is using the app from a distance,
+they would expect the text and UI elements to be bigger.
+The `viewingDistance` parameter provides an estimate of the viewing distance
+based on the device type and its typical usage context.
+
+There are three values defined in the `UiMediaScope.ViewingDistance` class:
+[`Near`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.ViewingDistance#Near()), [`Medium`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.ViewingDistance#Medium()), and [`Far`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.ViewingDistance#Far()).
+`Near` means that the screen is in close range,
+and `Far` means that the device is viewed from a distance.
+The following example increases the font size when the viewing distance is
+`Far` or `Medium`:
+
+
+```kotlin
+val fontSize = when {
+ mediaQuery { viewingDistance == UiMediaScope.ViewingDistance.Far } -> 20.sp
+ mediaQuery { viewingDistance == UiMediaScope.ViewingDistance.Medium } -> 18.sp
+ else -> 16.sp
+}
+```
+
+
+
+## Preview a UI component
+
+You can call the `mediaQuery` and `derivedMediaQuery` functions in the
+composable functions to preview UI components.
+The following snippet chooses between `TabletopLayout`
+and `FlatLayout` based on the `windowPosture` parameter value.
+To preview the `TabletopLayout`, the `windowPosture` parameter should be
+[`UiMediaScope.Posture.Tabletop`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.Posture#Tabletop()).
+
+
+```kotlin
+when {
+ mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop } -> TabletopLayout()
+ mediaQuery { windowPosture == UiMediaScope.Posture.Book } -> BookLayout()
+ mediaQuery { windowPosture == UiMediaScope.Posture.Flat } -> FlatLayout()
+}
+```
+
+
+
+The `mediaQuery` and `derivedMediaQuery` functions evaluate
+the given `query` lambda within a `UiMediaScope` object,
+which is provided as `LocalUiMediaScope.current`.
+You can override it with the following steps:
+
+1. Enable the `mediaQuery` function.
+2. Define a custom object that implements the `UiMediaScope` interface.
+3. Set the custom object to the `LocalUiMediaScope` with the [`CompositionLocalProvider`](https://developer.android.com/reference/kotlin/androidx/compose/runtime/CompositionLocalProvider.composable#CompositionLocalProvider(androidx.compose.runtime.CompositionLocalContext,kotlin.Function0)) function.
+4. Call the composable to preview in the content lambda of the `CompositionLocalProvider` function.
+
+You can preview the `TabletopLayout` with the following example:
+
+
+```kotlin
+@Preview
+@Composable
+fun PreviewLayoutForTabletop() {
+ // Step 1: Enable the mediaQuery function
+ ComposeUiFlags.isMediaQueryIntegrationEnabled = true
+
+ val currentUiMediaScope = LocalUiMediaScope.current
+ // Step 2: Define a custom object implementing the UiMediaScope interface.
+ // The object overrides the windowPosture parameter.
+ // The resolution of the remaining parameters is deferred to the currentUiMediaScope object.
+ val uiMediaScope = remember(currentUiMediaScope) {
+ object : UiMediaScope by currentUiMediaScope {
+ override val windowPosture: UiMediaScope.Posture = UiMediaScope.Posture.Tabletop
+ }
+ }
+
+ // Step 3: Set the object to the LocalUiMediaScope.
+ CompositionLocalProvider(LocalUiMediaScope provides uiMediaScope) {
+ // Step 4: Call the composable to preview.
+ when {
+ mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop } -> TabletopLayout()
+ mediaQuery { windowPosture == UiMediaScope.Posture.Book } -> BookLayout()
+ mediaQuery { windowPosture == UiMediaScope.Posture.Flat } -> FlatLayout()
+ }
+ }
+}
+```
+
+
\ No newline at end of file
diff --git a/jetpack-compose/adaptive/references/android/guide/navigation/navigation-3/recipes/material-listdetail.md b/jetpack-compose/adaptive/references/android/guide/navigation/navigation-3/recipes/material-listdetail.md
new file mode 100644
index 0000000..fbaae79
--- /dev/null
+++ b/jetpack-compose/adaptive/references/android/guide/navigation/navigation-3/recipes/material-listdetail.md
@@ -0,0 +1,141 @@
+# Material List-Detail Recipe
+
+This recipe demonstrates how to create an adaptive list-detail layout using the `ListDetailSceneStrategy` from the Material 3 Adaptive library. This layout automatically adjusts to show one, two, or three panes depending on the available screen width.
+
+## How it works
+
+This example has three destinations: `ConversationList`, `ConversationDetail`, and `Profile`.
+
+### `ListDetailSceneStrategy`
+
+The key to this recipe is the `rememberListDetailSceneStrategy`, which provides the logic for the adaptive layout.
+
+- **Pane Roles**: Each destination is assigned a role using metadata:
+
+ - `ListDetailSceneStrategy.listPane()`: For the primary (list) content. This pane is always visible. A placeholder can be provided to be shown in the detail pane area when no detail content is selected.
+ - `ListDetailSceneStrategy.detailPane()`: For the secondary (detail) content.
+ - `ListDetailSceneStrategy.extraPane()`: For tertiary content.
+- **Adaptive Layout** : The `ListDetailSceneStrategy` automatically handles the layout. On smaller screens, only one pane is shown at a time. On wider screens, it will show the list and detail panes side-by-side. On very wide screens, it can show all three panes: list, detail, and extra.
+
+- **Navigation** : Navigation between the panes is handled by adding and removing destinations from the back stack as usual. The `ListDetailSceneStrategy` observes the back stack and adjusts the layout accordingly.
+
+[ Explore View the full recipe on GitHub.](https://github.com/android/nav3-recipes/tree/main/app/src/main/java/com/example/nav3recipes/material/listdetail)
+
+```
+package com.example.nav3recipes.material.listdetail
+
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.currentWindowAdaptiveInfoV2
+import androidx.compose.material3.adaptive.layout.calculatePaneScaffoldDirective
+import androidx.compose.material3.adaptive.navigation3.ListDetailSceneStrategy
+import androidx.compose.material3.adaptive.navigation3.rememberListDetailSceneStrategy
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.dropUnlessResumed
+import androidx.navigation3.runtime.NavKey
+import androidx.navigation3.runtime.entryProvider
+import androidx.navigation3.runtime.rememberNavBackStack
+import androidx.navigation3.ui.NavDisplay
+import com.example.nav3recipes.content.ContentBlue
+import com.example.nav3recipes.content.ContentGreen
+import com.example.nav3recipes.content.ContentRed
+import com.example.nav3recipes.content.ContentYellow
+import com.example.nav3recipes.ui.setEdgeToEdgeConfig
+import kotlinx.serialization.Serializable
+
+@Serializable
+private object ConversationList : NavKey
+
+@Serializable
+private data class ConversationDetail(val id: String) : NavKey
+
+@Serializable
+private data object Profile : NavKey
+
+class MaterialListDetailActivity : ComponentActivity() {
+
+ @OptIn(ExperimentalMaterial3AdaptiveApi::class)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ setEdgeToEdgeConfig()
+ super.onCreate(savedInstanceState)
+
+ setContent {
+
+ val backStack = rememberNavBackStack(ConversationList)
+
+ // Override the defaults so that there isn't a horizontal space between the panes.
+ // See b/418201867
+ val windowAdaptiveInfo = currentWindowAdaptiveInfoV2()
+ val directive = remember(windowAdaptiveInfo) {
+ calculatePaneScaffoldDirective(windowAdaptiveInfo)
+ .copy(horizontalPartitionSpacerSize = 0.dp)
+ }
+ val listDetailStrategy = rememberListDetailSceneStrategy(directive = directive)
+
+ NavDisplay(
+ backStack = backStack,
+ onBack = { backStack.removeLastOrNull() },
+ sceneStrategies = listOf(listDetailStrategy),
+ entryProvider = entryProvider {
+ entry(
+ metadata = ListDetailSceneStrategy.listPane(
+ detailPlaceholder = {
+ ContentYellow("Choose a conversation from the list")
+ }
+ )
+ ) {
+ ContentRed("Welcome to Nav3") {
+ Button(onClick = dropUnlessResumed {
+ backStack.add(ConversationDetail("ABC"))
+ }) {
+ Text("View conversation")
+ }
+ }
+ }
+ entry(
+ metadata = ListDetailSceneStrategy.detailPane()
+ ) { conversation ->
+ ContentBlue("Conversation ${conversation.id} ") {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Button(onClick = dropUnlessResumed {
+ backStack.add(Profile)
+ }) {
+ Text("View profile")
+ }
+ }
+ }
+ }
+ entry(
+ metadata = ListDetailSceneStrategy.extraPane()
+ ) {
+ ContentGreen("Profile")
+ }
+ }
+ )
+ }
+ }
+}
+```
\ No newline at end of file
diff --git a/jetpack-compose/adaptive/references/android/studio/preview/compose-screenshot-testing-with-testsuites.md b/jetpack-compose/adaptive/references/android/studio/preview/compose-screenshot-testing-with-testsuites.md
new file mode 100644
index 0000000..1691d1d
--- /dev/null
+++ b/jetpack-compose/adaptive/references/android/studio/preview/compose-screenshot-testing-with-testsuites.md
@@ -0,0 +1,181 @@
+> [!WARNING]
+> **Experimental:** Compose Preview Screenshot Testing with AGP test suites is in active development. APIs and DSL syntax are subject to change. Report feedback and issues through the [issue tracker](https://issuetracker.google.com/issues/new?component=192708&template=840533).
+
+Starting with Android Gradle Plugin (AGP) 9.5.0-alpha03 and Compose Preview
+Screenshot Testing engine `0.0.1-alpha16`, screenshot testing is integrated with
+AGP's native [test suites](https://developer.android.com/reference/tools/gradle-api/9.5/com/android/build/api/dsl/AgpTestSuite) framework.
+
+This approach replaces the standalone screenshot plugin
+(`com.android.compose.screenshot`). We recommend adopting AGP test suites for
+the following reasons:
+
+- **Native Gradle task lifecycle**: Screenshot tests integrate directly into standard Gradle and AGP testing lifecycles, improving task isolation and test execution reliability.
+- **Multi-variant and custom suite support** : You can create multiple distinct screenshot test suites (such as `screenshotTest`, `uiTests`, or `smokeTests`) within a single module and target specific build variants (such as `demoDebug` or `release`), rather than being restricted to a single pre-configured source set.
+- **Enhanced build performance and isolation**: AGP test suites use built-in artifact transforms (such as Layoutlib runtime extraction) and isolated classloading, with full support for Gradle Configuration Caching and Project Isolation.
+
+
+## Android skills
+
+[View on GitHub](https://github.com/android/skills/blob/main/testing/testing-setup)
+
+### Create screenshot tests
+
+Use the `testing-setup` [Android skill](https://developer.android.com/tools/agents/android-skills) to develop a testing strategy and create screenshot tests. To install the skill from the [Android CLI](https://developer.android.com/tools/agents/android-cli), run:
+
+ android skills add testing-setup
+
+
+
+## Requirements
+
+To use Compose Screenshot Testing with test suites, ensure your environment
+meets the following requirements:
+
+- Android Studio Rabbit 1 Canary 4 or higher.
+- Android Gradle Plugin (AGP) version 9.5.0-alpha03 or higher.
+- Compose Screenshot Engine version 0.0.1-alpha16 or higher.
+- JDK version 17 or higher.
+- Compose enabled for your project. We recommend enabling Compose using the [Compose Compiler Gradle plugin](https://developer.android.com/develop/ui/compose/compiler).
+
+## Setup and configuration
+
+To configure Compose screenshot testing with test suites, complete the
+following steps:
+
+### 1. Enable experimental flags
+
+In your project's root `gradle.properties` file, enable screenshot testing and
+test suite support:
+
+ android.experimental.enableScreenshotTest=true
+ android.experimental.testSuiteSupport=true
+
+### 2. Configure the test suite in the `build.gradle.kts` file
+
+In your module's `build.gradle.kts` file, define a screenshot test suite within
+the `testOptions` block:
+
+ android {
+ testOptions {
+ screenshotTests.create("screenshotTest") { // suiteName can be customized (for example, "uiTests")
+ engineVersion = "0.0.1-alpha16"
+ targetVariants.add("demoDebug") // Add specific variants to test
+
+ dependencies {
+ implementation(libs.androidx.compose.ui.tooling)
+ implementation("com.android.tools.screenshot:screenshot-validation-api:0.0.1-alpha16")
+ }
+ }
+ }
+ }
+
+### 3. Create the test source set
+
+Create a dedicated source set directory matching your suite name:
+
+`{module}/src/{suiteName}/kotlin/`
+
+For example, for a suite named `screenshotTest`:
+
+`feature/foryou/impl/src/screenshotTest/kotlin/com/example/app/ForYouScreenTest.kt`
+
+### 4. Define composable preview tests
+
+Annotate composables with `@PreviewTest` and standard `@Preview` or
+multi-preview annotations:
+
+ package com.example.app
+
+ import androidx.compose.runtime.Composable
+ import androidx.compose.ui.tooling.preview.Preview
+ import com.android.tools.screenshot.PreviewTest
+ import com.example.app.ui.theme.AppTheme
+
+ @PreviewTest
+ @Preview(showBackground = true)
+ @Composable
+ fun ForYouScreenPreview() {
+ AppTheme {
+ ForYouScreen(isSyncing = false)
+ }
+ }
+
+## Run screenshot tests
+
+AGP test suites generate dedicated Gradle tasks based on your suite name,
+target, and variants.
+
+### 1. Generate or update reference images
+
+Render composable previews and store the golden baseline reference images:
+
+- **Linux and macOS** : `./gradlew update{SuiteName}{Target}{Variant}TestSuite` (for example, `./gradlew updateScreenshotTestDefaultDemoDebugTestSuite`)
+- **Windows** : `gradlew updateScreenshotTestDefaultDemoDebugTestSuite`
+
+Reference images are generated and saved at:
+
+`{module}/src/{suiteName}{Target}{Variant}/reference/`
+
+### 2. Verify and run tests
+
+Render fresh screenshots and compare them against reference images:
+
+- **Linux and macOS** : `./gradlew test{SuiteName}{Target}{Variant}TestSuite` (for example, `./gradlew testScreenshotTestDefaultDemoDebugTestSuite`)
+- **Windows** : `gradlew testScreenshotTestDefaultDemoDebugTestSuite`
+
+## Inspect test reports
+
+If differences are detected or tests fail, AGP generates an HTML test report.
+
+- **Report location** : `{module}/build/reports/tests/{taskName}/index.html` (for example, `app/build/reports/tests/testScreenshotTestDefaultDemoDebugTestSuite/index.html`)
+
+The updated report includes:
+
+- **Header metadata card**: Displays test name, preview method, variant, suite, and status badge.
+- **Error categorization** : Clearly flags `Reference Image Missing`, `Image Size Mismatch`, or `Pixel Mismatch` with copyable stack traces.
+- **Dynamic visual diff**: Highlights subtle modifications with lower intensity and major changes with high-contrast emphasis to prevent nested element swallowing.
+
+## Migrate from the legacy standalone plugin
+
+To migrate from the legacy standalone screenshot plugin to AGP test suites,
+update your Gradle configuration and task commands.
+
+### Build configuration DSL comparison
+
+#### Legacy standalone plugin (deprecated)
+
+ // In build.gradle.kts
+ plugins {
+ alias(libs.plugins.screenshot)
+ }
+
+ dependencies {
+ screenshotTestImplementation(libs.androidx.compose.ui.tooling)
+ screenshotTestImplementation("com.android.tools.screenshot:screenshot-validation-api:0.0.1-alpha16")
+ }
+
+#### AGP test suites (recommended)
+
+ // In build.gradle.kts
+ android {
+ testOptions {
+ screenshotTests.create("screenshotTest") {
+ engineVersion = "0.0.1-alpha16"
+ targetVariants.add("demoDebug")
+
+ dependencies {
+ implementation(libs.androidx.compose.ui.tooling)
+ implementation("com.android.tools.screenshot:screenshot-validation-api:0.0.1-alpha16")
+ }
+ }
+ }
+ }
+
+### Tasks and paths mapping
+
+| Concept | Legacy setup (deprecated) | AGP test suites (recommended) |
+|---|---|---|
+| **Update task** | `./gradlew updateDebugScreenshotTest` | `./gradlew update{SuiteName}{Target}{Variant}TestSuite` |
+| **Test task** | `./gradlew validateDebugScreenshotTest` | `./gradlew test{SuiteName}{Target}{Variant}TestSuite` |
+| **Reference path** | `src/screenshotTestDebug/reference` | `src/{suiteName}{Target}{Variant}/reference` |
+| **Report path** | `build/reports/screenshotTest/debug/` | `build/reports/tests/{taskName}/` |
\ No newline at end of file
diff --git a/jetpack-compose/jetpack-compose-performance/SKILL.md b/jetpack-compose/jetpack-compose-performance/SKILL.md
new file mode 100644
index 0000000..3a07a5b
--- /dev/null
+++ b/jetpack-compose/jetpack-compose-performance/SKILL.md
@@ -0,0 +1,193 @@
+---
+name: jetpack-compose-performance
+description: Diagnose and optimize Android Jetpack Compose UI code. Use this skill
+ when analyzing performance bottlenecks of existing code to minimize recomposition
+ overhead and prevent dropped frames and UI lag.
+license: Complete terms in LICENSE.txt
+metadata:
+ author: Google LLC
+ last-updated: '2026-10-13'
+ keywords:
+ - Jetpack Compose
+ - performance
+ - UI development
+ - optimization
+ - recomposition
+ - lazy layouts
+ - graphics
+ - stability
+ - memory
+ - allocation
+ - jank
+ - rendering
+ - state computation
+ - state derivation
+ - snapshotFlow
+ - produceState
+ - derivedStateOf
+ - computedStateOf
+ - sharedElement
+ - sharedBounds
+ - CompositionLocal
+---
+
+## Foundational prerequisites
+
+Before micro-optimizing composables, ensure standard production flags are in
+place:
+
+1. **Release Build**: Never profile or benchmark debuggable builds; ART runtime verification severely degrades performance.
+2. **R8 Minification**: Keep R8 enabled to optimize bytecode and coroutine dispatch.
+3. **Baseline Profiles**: Generate baseline profiles for critical user journeys (cold start, key scrolling feeds) so the ART VM pre-compiles and preloads classes ahead of time.
+
+## Optimization audit workflow
+
+1. **Exhaustive Scan** : Scan the target code against all 7 core categories in the following sections. Evaluate every `@Composable` function, modifier chain, and lazy-layout data model against all checklist rules rather than stopping after the first issue found.
+2. **Mandatory Reference Read Before Flagging or Editing** : Whenever a file matches a rule in Categories 1--7, you **MUST** open and read the corresponding `references/*.md` file for the approved fix pattern and full scope guards **before** reporting a finding or editing code. Never rely on the summary bullets alone to write fixes.
+
+## 1. State, snapshots, and stability ([references/state-and-stability.md](references/state-and-stability.md))
+
+**MANDATORY** : Before flagging or fixing any issue in this category, you
+**MUST** read [references/state-and-stability.md](references/state-and-stability.md).
+
+- **Phase Deferral (Lambdas)** : Wrap frequently changing state reads in lambdas (such as `Modifier.offset` or `Modifier.graphicsLayer`) so reads occur in Layout or Draw phase rather than Composition.
+- **Prohibit Backwards Writes (Verify Read-Before-Write Order)** : Never modify state in a later phase or after it has already been read in the same composition pass (`Read -> Write`, or `Composition Read -> Layout or Draw
+ Write`). Prevent backwards writes across phases to avoid first-frame popping, jank, and infinite recomposition loops ([Backwards Write](references/android/develop/ui/compose/performance/backwards-write.md)). Do **not** flag a state assignment at the start of a composable (`Write ->
+ Read` or `Composition Write -> Draw Read`) as a backwards write---a backwards write strictly requires a read of that state to precede the write.
+- **Avoid Redundant State Wrapping in Composition (`Write -> Read`)** : Do not wrap synchronous values in `remember { mutableStateOf(...) }` only to overwrite them immediately at the top of composition before reading them (for example, `var path by remember { mutableStateOf(Path()) }; path =
+ viewModel.getPath()`). While not a backwards write, this adds unnecessary slot table and `MutableState` snapshot overhead (and risks becoming a backwards write if a read is later added preceding the assignment); use a plain local `val` or read the value directly inside the target phase (such as `Modifier.drawWithCache`).
+- **Explicit `remember` Keys \& Slot Overhead** : Always pass all input variables and composable parameters referenced inside `remember` as calculation keys. Remember that `remember` stores `keys + 1` objects in the slot table and checks key equality on every recomposition; only `remember` when allocations are heavy or identity must be preserved. Do **not** pass snapshot state values/properties (e.g. `state.value`) as keys when wrapping `derivedStateOf` (passing the `State` instance itself is fine if the instance reference can change).
+- **`computedStateOf` vs `derivedStateOf` (Compose 1.13+)** : On Compose 1.13+, use `computedStateOf` for simple condition checks and filtered state from fast-changing inputs (such as `scrollState.value > 0`), which provides significantly faster invalidation than `derivedStateOf` (on Compose `< 1.13`, use `remember { derivedStateOf { ... } }`). Reserve `derivedStateOf` for heavy calculations that benefit from multi-read caching, or trivial calculations read frequently enough per composition (roughly `10 / numberOfStatesReadPerCalculation` times without invalidating between reads) to make up for the invalidation cost.
+- **Synchronous State Derivation** : Audit for asynchronous `produceState` + `snapshotFlow` blocks observing Compose states (such as scroll positions or collection sizes); replace them with synchronous `remember { derivedStateOf
+ { ... } }` (or `computedStateOf` on Compose 1.13+).
+- **Trivial Primitives** : Prefer plain custom Kotlin getters (`get()`) over `derivedStateOf` or `computedStateOf` for simple boolean expressions and property lookups.
+- **Collection State Tracking** : Back whole-list updates with standard `mutableListOf()` and `mutableStateOf(list, neverEqualPolicy())` with load-bearing assignments instead of `mutableStateListOf()`.
+- **Snapshot Collections** : Use `SnapshotStateList.toList()` for repeated queries or iterations to avoid per-element read observation overhead.
+- **Repeated State Reads** : Extract repeated Compose `State` property reads into a local variable at the top of the block.
+- **State in Setters** : Avoid reading Compose `State` inside custom property setters; use a raw backing field when triggering change side-effects and batch multi-property updates.
+- **Stability \& Skipping Trade-Offs (Skipping Isn't Free)** : Do not fixate on stability or treat `@Immutable`, `@Stable`, or `ImmutableList` as default solutions. Skipping a composable is not free: each parameter is stored in the slot table as if remembered, and all inputs must be compared on recomposition. [Strong Skipping Mode](references/android/develop/ui/compose/performance/stability/strongskipping.md) already skips composables with unstable parameters (including standard `kotlin.collections.List`) when instance equality (`===`) matches. Annotating models or using `ImmutableList` forces structural equality (`.equals()`), which can be significantly more expensive than recomposition itself for large collections or complex classes.
+- **Never Add `@Stable`, `@Immutable`, or `ImmutableList` to Collection
+ Models** : When writing or optimizing code, never add `@Immutable` or `@Stable` to models containing collections (`List`, `Set`, `Map`), and **never** recommend replacing `kotlin.collections.List` with `ImmutableList` (or adding `ImmutableList` to `stability_config.txt`). Calling `O(N)` `.equals()` on collections on every recomposition pass is more expensive than `O(1)` instance equality (`===`) under Strong Skipping Mode.
+- **Scope Guard for Removing Existing `@Stable` / `@Immutable` Annotations
+ (Large Lazy Datasets Only)** :
+ - **When to Remove** : **Only** recommend removing existing `@Stable` or `@Immutable` annotations from data models when **both** conditions are met: (1) the model is used within a **lazy layout** (`LazyColumn`, `LazyRow`, `LazyGrid`, `Pager`, `ScalingLazyColumn`) as the input feed or item model, **and** (2) it contains a **large dataset or deep nested
+ collection hierarchy** (e.g., feed streams, large lists of tags/items, or multi-level nested lists like `Post` with `List` and `List`), where recursive `O(N)` `.equals()` checks degrade scrolling and recomposition performance.
+ - **When to Leave Untouched** : For all other existing `@Stable`, `@Immutable`, or `ImmutableList` usages in a codebase---such as non-lazy UI models, standalone components, or models with small/bounded collections (e.g., a card with a few badges, thumbnails, or metadata rows) where dataset size is small or unknown---**leave the existing
+ annotations untouched and do not flag them** . Never blindly grep and flag all `@Stable`/`@Immutable` classes across a codebase without verifying they feed a lazy layout with a large dataset.
+- **When to Add `@Stable` / `@Immutable` Under Strong Skipping Mode** : Flat `data class`es with `val` primitive/`String` properties in Compose modules are **already inferred as stable** by the compiler and use `.equals()` automatically---do not add redundant `@Immutable` annotations to them. Only add `@Stable` or `@Immutable` when the compiler cannot infer stability (e.g. classes from non-Compose modules or interfaces) **and** a data source (such as Room or MVI reducers) allocates new object instances for unchanged items on every emission, **provided** the class is small, flat, has no collections (`List`, `Set`, `Map`), and its `.equals()` check is trivial `O(1)`.
+- **Make Composables Cheaper Instead of Forcing Skipping**: Do not overindex on recomposition counts. Skipping one composable during an active recomposition pass saves little time once composition is scheduled (leaving the house is the bulk of the cost). Focus on making composables cheap to execute, deferring state reads to Layout/Draw, and verifying with benchmarks rather than assuming skipping improves performance.
+
+## 2. Layouts and measurement optimization ([references/layouts.md](references/layouts.md))
+
+**MANDATORY** : Before flagging or fixing any issue in this category, you
+**MUST** read [references/layouts.md](references/layouts.md).
+
+- **Coordinates \& Placement** : Avoid `onGloballyPositioned`; use `Modifier.onVisibilityChanged` for viewport tracking, `Modifier.onPlaced` or `Modifier.onLayoutRectChanged` for local bounds, and query `requireLayoutCoordinates()` on demand inside `Modifier.Node` event handlers.
+- **Anchor Floating Content via Custom `Layout`** : When attaching floating labels or tooltips to a component, use a custom `Layout` measuring and placing both children relative to each other. Never track `onGloballyPositioned` coordinates in `MutableState` to position floating content with `offset`, which causes a 1-frame lag and schedules a second composition pass.
+- **Phase Deferral for Offsets** : Wrap dynamic offsets in lambdas (`Modifier.offset { IntOffset(...) }`) to skip Composition straight to Layout.
+- **Tree Depth \& Decorative Wrappers** : Avoid redundant wrapper `Box`es and decorative `Canvas` nodes; attach modifiers directly or use `Modifier.drawBehind`.
+- **Subcomposition Overhead** : Avoid `BoxWithConstraints` / `SubcomposeLayout` inside hot paths and lazy items; prefer standard modifiers, `Modifier.layout`, or `LayoutModifierNode`.
+- **Intrinsic Measurements**: Use intrinsic measurements judiciously and avoid deeply nested intrinsic chains.
+
+## 3. Lazy layouts optimization ([references/lazy-layouts.md](references/lazy-layouts.md))
+
+**MANDATORY** : Before flagging or fixing any issue in this category, you
+**MUST** read [references/lazy-layouts.md](references/lazy-layouts.md).
+
+- **Lazy Layout Scope Guard** : Directives for `key` and `contentType` apply **strictly** to Lazy containers (`LazyColumn`, `LazyRow`, `LazyGrid`). Never flag standard non-lazy containers (`Column`, `Row`, `Box`) iterating over collections.
+- **Mandatory `contentType` and Primitive `key`** : Always specify both primitive `key`s (`Int`, `Long`, `String`) **and** `contentType`s for all items in lazy layouts (`LazyColumn`, `LazyRow`, `LazyGrid`). When using `items` or `itemsIndexed`, never omit `contentType`: `contentType = { _, item -> item.type }`. Omitting `contentType` prevents Compose from reusing composition slots during scrolling.
+- **In-Item Index Lookups** : Replace `list.indexOf(item)` with `itemsIndexed` to eliminate `O(N²)` lookups during layout passes.
+- **Observable Selection \& State Queries** : Convert selection tracking from `mutableStateListOf` to an immutable `Set` in `MutableState` (`mutableStateOf(emptySet())`), `mutableStateSetOf()`, or Map for `O(1)` lookups. Pass lambda providers (`isSelected: () -> Boolean`) to leaf composables to isolate recomposition from parent `items` scopes.
+- **Propagate CompositionLocals in Lazy Items** : Avoid querying `CompositionLocal`s (`LocalContext.current`, `LocalDensity.current`, `LocalSharedTransitionScope.current`, scopes, etc.) inside individual lazy item composables; resolve them once in the parent container or screen and propagate the resolved values down as parameters.
+- **Defer Shared Transitions in Lazy Items** : Put `Modifier.sharedBounds` or `Modifier.sharedElement` on lazy items behind visibility indicators (such as `Modifier.onVisibilityChanged` or viewport visibility checks) so offscreen or pre-fetched items do not instantiate transition scopes.
+- **Item Count Derivations** : Read collection `.size` directly instead of wrapping list counts in `derivedStateOf`.
+- **Small \& Bounded Lists** : Use standard `Row` or `Column` with `for` or `forEach` loops for small or bounded collections (e.g. 1 to 10 items) to eliminate lazy virtualization, measuring, and pre-fetching overhead.
+- **Hoist Shared Flows** : Collect shared `Flow`s once in the parent container preceding lazy layouts; never call `collectAsStateWithLifecycle()` per visible item.
+- **Avoid Side Effects in Pre-Fetched Items** : Never trigger analytics in `LaunchedEffect(Unit)` inside lazy items; use `Modifier.onVisibilityChanged` with dwell thresholds instead.
+
+## 4. Animation and shared transitions ([references/animation.md](references/animation.md))
+
+**MANDATORY** : Before flagging or fixing any issue in this category, you
+**MUST** read [references/animation.md](references/animation.md).
+
+- **Phase Deferral for Animated Properties** : Wrap fast-changing animated state reads (alpha, scale, rotation, color) in `Modifier.graphicsLayer { ...
+ }` to bypass Composition and evaluate directly in the Draw phase.
+- **Phase Deferral for Offsets \& Size** : Wrap animated offsets in `Modifier.offset { IntOffset(...) }` and animate constraints using `Modifier.layout { ... }` or `Modifier.animateBounds` to bypass recomposition during size transitions.
+- **Defer Shared Transitions in Fast-Scrolling Lists** : Attach `Modifier.sharedBounds` or `Modifier.sharedElement` only when visible using `Modifier.onVisibilityChanged` to avoid modifier and observation instantiation costs on discarded pre-fetched items.
+- **Lambda Providers** : Pass lambda providers (`() -> Float`) instead of raw animated values to child composables to keep state reads deferred to Draw or Layout.
+- **Persistent `Animatable` in Nodes** : Store `Animatable` as a persistent member property on `Modifier.Node` and launch in `onAttach()` using `coroutineScope.launch`.
+
+## 5. Effects, lifecycle, and threading ([effects-and-threading.md](references/effects-and-threading.md))
+
+**MANDATORY** : Before flagging or fixing any issue in this category, you
+**MUST** read [references/effects-and-threading.md](references/effects-and-threading.md).
+
+- **Prohibit Direct Side-Effects in Body** : Never perform side-effects directly in the Composable body during composition; wrap them in `SideEffect`, `LaunchedEffect`, or user event callbacks.
+- **Prohibit Non-Suspending `LaunchedEffect` \& Empty `DisposableEffect`** : Never use `LaunchedEffect` for synchronous operations (e.g. logging views or updating objects). Never use `DisposableEffect` with an empty `onDispose {}` block as a workaround. Use `SideEffect` instead (in Compose 1.12+, `SideEffect(keys) { ... }` supports keys directly; on Compose `<
+ 1.12`, `SideEffect` takes no arguments, so check the project's Compose version and track key changes with a `remember`ed holder inside `SideEffect { ... }`).
+- **Cache Allocations in Body** : Wrap heavy object allocations (such as date formatters or complex paths) and expensive computations in `remember`.
+- **Combine Related `LaunchedEffect`s** : Combine multiple `LaunchedEffect(Unit)` calls into a single `LaunchedEffect` with nested `launch { ... }` blocks to reduce effect node allocations and coroutine launcher overhead.
+- **Effect Ordering \& Teardown Costs** : Respect effect execution order (`DisposableEffect` \> `SideEffect` \> Layout and Draw \> `LaunchedEffect` after frame). Note that canceling a `LaunchedEffect` incurs coroutine cancellation and teardown overhead; avoid cycling `LaunchedEffect` keys frequently during animations or scrolling.
+- **Main Thread Offloading** : Offload blocking I/O, heavy file parsing, and database operations to `withContext(Dispatchers.IO)` using `produceState`.
+- **No Item-Level Impression Effects in Lazy Layouts** : Never use `LaunchedEffect` or `DisposableEffect` inside lazy items for impression logging. Lazy items are pre-composed before entering the viewport, causing premature or false impressions. Use `Modifier.onVisibilityChanged` with dwell thresholds instead.
+- **Lifecycle-Aware Flow Collection** : Use `collectAsStateWithLifecycle()` for UI Flow collections that actively produce background work on Android, and `collectAsState()` for lightweight in-memory StateFlows or non-Android scopes.
+- **Hoist Receivers \& Listeners** : Register system listeners and `BroadcastReceiver`s once at the screen level using `DisposableEffect`, not inside lazy list items.
+- **Guard Optional Effects** : Guard effects with `if (callback != null)` before launching to prevent allocating empty coroutines and effect nodes.
+- **Prevent Stale Captures** : Use `rememberUpdatedState` when long-running or looping coroutines in `LaunchedEffect` reference callbacks or parameters.
+- **Replace Legacy Timers** : Replace `Handler.postDelayed` or `TimerTask` with `LaunchedEffect` and `delay()`.
+
+## 6. Graphics and custom drawing ([references/graphics-performance.md](references/graphics-performance.md))
+
+**MANDATORY** : Before flagging or fixing any issue in this category, you
+**MUST** read [references/graphics-performance.md](references/graphics-performance.md).
+
+- **Cache Drawing Allocations** : Replace decorative `Canvas` nodes with `Modifier.drawWithCache` to cache `Path`, `Paint`, and `Brush` allocations.
+- **Prefer AsyncImage instead of Image** : Use Coil's `AsyncImage` for loading images from the internet and `painterResource` for drawables. `Image` is not optimized for caching and loading images and may cause performance issues.
+- **Scope Fast-Changing Uniforms** : `remember` the `RuntimeShader` and `ShaderBrush` in composition, update size uniforms in `drawWithCache`, and per-frame uniforms in `onDrawBehind`.
+- **Graphics Path Object Reuse** : Prefer `Path.rewind()` over `reset()` in hot draw loops to retain allocated buffers without reallocation.
+- **Value Classes \& Primitive Collections** : Avoid generic tuples (`Pair`, `Triple`) and generic sets (`HashSet`); use `androidx.collection.MutableLongSet` and `androidx.compose.ui.util.packInts`.
+
+## 7. Custom modifiers: `Modifier.Node` ([Custom modifiers guide](references/android/develop/ui/compose/custom-modifiers.md))
+
+- **Modifier.Node Migration** : Migrate legacy `Modifier.composed` to
+ `ModifierNodeElement` + `Modifier.Node` (or `DelegatingNode` when delegating
+ to sub-nodes such as `SuspendingPointerInputModifierNode`) with capability
+ interfaces (`DrawModifierNode`, `LayoutModifierNode`,
+ `SemanticsModifierNode`, `CompositionLocalConsumerModifierNode` using
+ `currentValueOf(...)`) to eliminate per-node subcomposition overhead and
+ enable node reuse (see [Create custom modifiers with Modifier.Node](references/android/develop/ui/compose/custom-modifiers.md)):
+
+
+ ```kotlin
+ // Modifier factory
+ fun Modifier.circle(color: Color) = this then CircleElement(color)
+
+ // ModifierNodeElement
+ private data class CircleElement(
+ val color: Color
+ ) : ModifierNodeElement() {
+ override fun create() = CircleNode(color)
+
+ override fun update(node: CircleNode) {
+ node.color = color
+ }
+ }
+
+ // Modifier.Node
+ private class CircleNode(
+ var color: Color
+ ) : DrawModifierNode, Modifier.Node() {
+ override fun ContentDrawScope.draw() {
+ drawCircle(color)
+ }
+ }
+ ```
+
+
+
+- **On-Demand Coordinates** : Query `requireLayoutCoordinates()` on demand
+ inside pointer or draw handlers in `Modifier.Node` rather than implementing
+ `GlobalPositionAwareModifierNode.onGloballyPositioned`.
+
+**IMPORTANT** : For more examples on how to migrate common modifiers to
+Modifier.Node, you must read [Create custom modifiers with Modifier.Node](references/android/develop/ui/compose/custom-modifiers.md).
diff --git a/jetpack-compose/jetpack-compose-performance/references/android/develop/ui/compose/custom-modifiers.md b/jetpack-compose/jetpack-compose-performance/references/android/develop/ui/compose/custom-modifiers.md
new file mode 100644
index 0000000..a48df35
--- /dev/null
+++ b/jetpack-compose/jetpack-compose-performance/references/android/develop/ui/compose/custom-modifiers.md
@@ -0,0 +1,552 @@
+Compose provides many [modifiers](https://developer.android.com/develop/ui/compose/modifiers) for common behaviors right out of the box,
+but you can also create your own custom modifiers.
+
+Modifiers have multiple parts:
+
+- A modifier factory
+ - This is an extension function on `Modifier`, which provides an idiomatic API for your modifier and allows modifiers to be chained together. The modifier factory produces the modifier elements used by Compose to modify your UI.
+- A modifier element
+ - This is where you can implement the behavior of your modifier.
+
+There are multiple ways to implement a custom modifier depending on the
+functionality needed. Often, the simplest way to implement a custom modifier is
+to implement a custom modifier factory that combines other already defined
+modifier factories. If you need more custom behavior, implement the modifier
+element using the `Modifier.Node` APIs, which are lower level but provide more
+flexibility.
+
+## Chain existing modifiers together
+
+It is often possible to create custom modifiers by using existing modifiers. For
+example, [`Modifier.clip()`](https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier#(androidx.compose.ui.Modifier).clip(androidx.compose.ui.graphics.Shape)) is implemented using the `graphicsLayer`
+modifier. This strategy uses existing modifier elements, and you provide your
+own custom modifier factory.
+
+Before implementing your own custom modifier, see if you can use the same
+strategy.
+
+
+```kotlin
+fun Modifier.clip(shape: Shape) = graphicsLayer(shape = shape, clip = true)
+```
+
+
+
+Or, if you find you are repeating the same group of modifiers often, you can
+wrap them into your own modifier:
+
+
+```kotlin
+fun Modifier.myBackground(color: Color) = padding(16.dp)
+ .clip(RoundedCornerShape(8.dp))
+ .background(color)
+```
+
+
+
+## Create a custom modifier using a composable modifier factory
+
+You can also create a custom modifier using a composable function to pass values
+to an existing modifier. This is known as a composable modifier factory.
+
+> [!NOTE]
+> **Note:** `composed {}` is no longer recommended due to performance concerns. See [Migrate from `Modifier.composed` to `Modifier.Node`](https://developer.android.com/develop/ui/compose/custom-modifiers-node) for details on how to migrate away from this modifier.
+
+Using a composable modifier factory to create a modifier also lets you use
+higher level compose APIs, such as [`animate*AsState`](https://developer.android.com/develop/ui/compose/animation/value-based#animate-as-state) and other [Compose
+state backed animation APIs](https://developer.android.com/develop/ui/compose/animation/choose-api). For example, the following snippet shows a
+modifier that animates an alpha change when enabled/disabled:
+
+
+```kotlin
+@Composable
+fun Modifier.fade(enable: Boolean): Modifier {
+ val alpha by animateFloatAsState(if (enable) 0.5f else 1.0f)
+ return this then Modifier.graphicsLayer { this.alpha = alpha }
+}
+```
+
+
+
+> [!WARNING]
+> **Warning:** When creating custom modifiers, don't break the modifier chain. You must always reference `this` or else any modifiers previously added will be dropped. You can use `this then Modifier` as in the preceding example or implicitly using `return graphicsLayer { this.alpha = alpha }`.
+
+If your custom modifier is a convenience method to provide default values from a
+`CompositionLocal`, the easiest way to implement this is to use a composable
+modifier factory:
+
+
+```kotlin
+@Composable
+fun Modifier.fadedBackground(): Modifier {
+ val color = LocalContentColor.current
+ return this then Modifier.background(color.copy(alpha = 0.5f))
+}
+```
+
+
+
+This approach has some caveats, which are detailed in the following sections.
+
+#### `CompositionLocal` values are resolved at the call site of the modifier factory
+
+When creating a custom modifier using a composable modifier factory, composition
+locals take the value from the composition tree where they are created, not
+used. This can lead to unexpected results. For example, consider the
+composition local modifier example mentioned previously, implemented slightly
+differently using a composable function:
+
+
+```kotlin
+@Composable
+fun Modifier.myBackground(): Modifier {
+ val color = LocalContentColor.current
+ return this then Modifier.background(color.copy(alpha = 0.5f))
+}
+
+@Composable
+fun MyScreen() {
+ CompositionLocalProvider(LocalContentColor provides Color.Green) {
+ // Background modifier created with green background
+ val backgroundModifier = Modifier.myBackground()
+
+ // LocalContentColor updated to red
+ CompositionLocalProvider(LocalContentColor provides Color.Red) {
+
+ // Box will have green background, not red as expected.
+ Box(modifier = backgroundModifier)
+ }
+ }
+}
+```
+
+
+
+If this is not how you expect your modifier to work, use a custom
+[`Modifier.Node`](https://developer.android.com/develop/ui/compose/custom-modifiers#implement-custom) instead, as composition locals will be
+correctly resolved at the usage site and can be safely hoisted.
+
+#### Composable function modifiers are never skipped
+
+Composable factory modifiers are never [skipped](https://developer.android.com/develop/ui/compose/mental-model#skips) because composable functions
+that have return values cannot be skipped. This means your modifier function
+will be called on every recomposition, which may be expensive if it recomposes
+frequently.
+
+#### Composable function modifiers must be called within a composable function
+
+Like all composable functions, a composable factory modifier must be called from
+within composition. This limits where a modifier can be hoisted to, as it can
+never be hoisted out of composition. In comparison, non-composable modifier
+factories can be hoisted out of composable functions to allow easier reuse and
+improve performance:
+
+
+```kotlin
+val extractedModifier = Modifier.background(Color.Red) // Hoisted to save allocations
+
+@Composable
+fun Modifier.composableModifier(): Modifier {
+ val color = LocalContentColor.current.copy(alpha = 0.5f)
+ return this then Modifier.background(color)
+}
+
+@Composable
+fun MyComposable() {
+ val composedModifier = Modifier.composableModifier() // Cannot be extracted any higher
+}
+```
+
+
+
+## Implement custom modifier behavior using `Modifier.Node`
+
+[`Modifier.Node`](https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier.Node) is a lower level API for creating modifiers in Compose. It
+is the same API that Compose implements its own modifiers in and is the most
+performant way to create custom modifiers.
+
+> [!NOTE]
+> **Note:** There is another API for creating custom modifiers, [`composed {}`](https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier#(androidx.compose.ui.Modifier).composed(kotlin.Function1,kotlin.Function1)). This API is no longer recommended due to the performance issues it created. `Modifier.Node` was designed from the ground up to be far more performant than composed modifiers. For more details on the problems with composed modifiers, see the Android Dev Summit talk [Compose Modifiers Deep Dive](https://www.youtube.com/watch?v=BjGX2RftXsU).
+
+### Implement a custom modifier using `Modifier.Node`
+
+There are three parts to implementing a custom modifier using Modifier.Node:
+
+- A [`Modifier.Node`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt;l=184) implementation that holds the logic and state of your modifier.
+- A [`ModifierNodeElement`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifierNodeElement.kt;l=39) that creates and updates modifier node instances.
+- An optional modifier factory, as detailed previously.
+
+`ModifierNodeElement` classes are stateless and new instances are allocated each
+recomposition, whereas `Modifier.Node` classes can be stateful and will survive
+across multiple recompositions, and can even be reused.
+
+The following section describes each part and shows an example of building a
+custom modifier to draw a circle.
+
+#### `Modifier.Node`
+
+The `Modifier.Node` implementation (in this example, `CircleNode`) implements
+the functionality of your custom modifier.
+
+
+```kotlin
+// Modifier.Node
+private class CircleNode(var color: Color) : DrawModifierNode, Modifier.Node() {
+ override fun ContentDrawScope.draw() {
+ drawCircle(color)
+ }
+}
+```
+
+
+
+In this example, it draws the circle with the color passed in to the modifier
+function.
+
+A node implements `Modifier.Node` as well as zero or more node types. There are
+different node types based on the functionality your modifier requires. The
+preceding example needs to be able to draw, so it implements `DrawModifierNode`,
+which lets it override the draw method.
+
+The available types are as follows:
+
+|---|---|---|
+| **Node** | **Usage** | **Sample Link** |
+| [`LayoutModifierNode`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNode.kt) | A `Modifier.Node` that changes how its wrapped content is measured and laid out. | [Sample](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LayoutSample.kt;l=198) |
+| [`DrawModifierNode`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DrawModifierNode.kt) | A `Modifier.Node` that draws into the space of the layout. | [Sample](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ModifierSamples.kt;l=313) |
+| [`CompositionLocalConsumerModifierNode`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/CompositionLocalConsumerModifierNode.kt) | Implementing this interface lets your `Modifier.Node` read composition locals. | [Sample](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ModifierCompositionLocalSample.kt;l=64) |
+| [`SemanticsModifierNode`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/SemanticsModifierNode.kt) | A `Modifier.Node` that adds semantics key/value for use in testing, accessibility, and similar use cases. | [Sample](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ModifierSamples.kt;l=338) |
+| [`PointerInputModifierNode`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/PointerInputModifierNode.kt) | A `Modifier.Node` that receives [PointerInputChanges.](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt) | [Sample](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ModifierSamples.kt;l=366) |
+| [`ParentDataModifierNode`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ParentDataModifierNode.kt) | A `Modifier.Node` that provides data to the parent layout. | [Sample](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Box.kt;l=295) |
+| [`LayoutAwareModifierNode`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutAwareModifierNode.kt) | A `Modifier.Node` which receives `onMeasured` and `onPlaced` callbacks. | [Sample](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ModifierSamples.kt;l=405) |
+| [`GlobalPositionAwareModifierNode`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/GlobalPositionAwareModifierNode.kt) | A `Modifier.Node` which receives an `onGloballyPositioned` callback with the final `LayoutCoordinates` of the layout when the global position of the content may have changed. | [Sample](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ModifierSamples.kt;l=405) |
+| [`ObserverModifierNode`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ObserverModifierNode.kt) | `Modifier.Node`s that implement `ObserverNode` can provide their own implementation of `onObservedReadsChanged` that will be called in response to changes to snapshot objects read within an `observeReads` block. | [Sample](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ModifierCompositionLocalSample.kt;l=64) |
+| [`DelegatingNode`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingNode.kt) | A `Modifier.Node` which is able to delegate work to other `Modifier.Node` instances. This can be useful to compose multiple node implementations into one. | [Sample](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ModifierSamples.kt) |
+| [`TraversableNode`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/TraversableNode.kt;l=28) | Allows `Modifier.Node` classes to traverse up/down the node tree for classes of the same type or for a particular key. | [Sample](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/modifier/TraverseModifierDemo.kt;l=123) |
+
+Nodes are automatically invalidated when update is called on their corresponding
+element. Because our example is a `DrawModifierNode`, any time update is called
+on the element, the node triggers a redraw and its color correctly updates. It
+is possible to opt out of auto-invalidation, as detailed in the
+[Opt out of node auto-invalidation](https://developer.android.com/develop/ui/compose/custom-modifiers#autoinvalidation) section.
+
+#### `ModifierNodeElement`
+
+A `ModifierNodeElement` is an immutable class that holds the data to create or
+update your custom modifier:
+
+
+```kotlin
+// ModifierNodeElement
+private data class CircleElement(val color: Color) : ModifierNodeElement() {
+ override fun create() = CircleNode(color)
+
+ override fun update(node: CircleNode) {
+ node.color = color
+ }
+}
+```
+
+
+
+`ModifierNodeElement` implementations need to override the following methods:
+
+1. `create`: This is the function that instantiates your modifier node. This gets called to create the node when your modifier is first applied. Usually, this amounts to constructing the node and configuring it with the parameters that were passed in to the modifier factory.
+2. `update`: This function is called whenever this modifier is provided in the same spot this node already exists, but a property has changed. This is determined by the `equals` method of the class. The modifier node that was previously created is sent as a parameter to the `update` call. At this point, you should update the nodes' properties to correspond with the updated parameters. The ability for nodes to be reused this way is key to the performance gains that `Modifier.Node` brings; therefore, you must update the existing node rather than creating a new one in the `update` method. In our circle example, the color of the node is updated.
+
+Additionally, `ModifierNodeElement` implementations also need to implement
+`equals` and `hashCode`. `update` will only get called if an equals comparison
+with the previous element returns false.
+
+> [!WARNING]
+> **Warning:** Your `ModifierNodeElement` must implement `equals` and `hashCode` correctly and not rely on instance equality. Without this, your modifier node will be updated unnecessarily and perform poorly. Use a data class to achieve this automatically.
+
+The preceding example uses a data class to achieve this. These methods are used
+to check if a node needs updating or not. If your element has properties that
+don't contribute to whether a node needs to be updated, or you want to avoid
+data classes for binary compatibility reasons, then you can manually implement
+`equals` and `hashCode`, for example, the
+[padding modifier element](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Padding.kt;l=358).
+
+#### Modifier factory
+
+This is the public API surface of your modifier. Most implementations create the
+modifier element and add it to the modifier chain:
+
+
+```kotlin
+// Modifier factory
+fun Modifier.circle(color: Color) = this then CircleElement(color)
+```
+
+
+
+#### Complete example
+
+These three parts come together to create the custom modifier to draw a circle
+using the `Modifier.Node` APIs:
+
+
+```kotlin
+// Modifier factory
+fun Modifier.circle(color: Color) = this then CircleElement(color)
+
+// ModifierNodeElement
+private data class CircleElement(val color: Color) : ModifierNodeElement() {
+ override fun create() = CircleNode(color)
+
+ override fun update(node: CircleNode) {
+ node.color = color
+ }
+}
+
+// Modifier.Node
+private class CircleNode(var color: Color) : DrawModifierNode, Modifier.Node() {
+ override fun ContentDrawScope.draw() {
+ drawCircle(color)
+ }
+}
+```
+
+
+
+## Common situations using `Modifier.Node`
+
+When creating custom modifiers with `Modifier.Node`, here are some common
+situations you might encounter.
+
+### Zero parameters
+
+If your modifier has no parameters, then it never needs to update and,
+furthermore, doesn't need to be a data class. The following is a sample
+implementation of a modifier that applies a fixed amount of padding to a
+composable:
+
+
+```kotlin
+fun Modifier.fixedPadding() = this then FixedPaddingElement
+
+data object FixedPaddingElement : ModifierNodeElement() {
+ override fun create() = FixedPaddingNode()
+ override fun update(node: FixedPaddingNode) {}
+}
+
+class FixedPaddingNode : LayoutModifierNode, Modifier.Node() {
+ private val PADDING = 16.dp
+
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints
+ ): MeasureResult {
+ val paddingPx = PADDING.roundToPx()
+ val horizontal = paddingPx * 2
+ val vertical = paddingPx * 2
+
+ val placeable = measurable.measure(constraints.offset(-horizontal, -vertical))
+
+ val width = constraints.constrainWidth(placeable.width + horizontal)
+ val height = constraints.constrainHeight(placeable.height + vertical)
+ return layout(width, height) {
+ placeable.place(paddingPx, paddingPx)
+ }
+ }
+}
+```
+
+
+
+### Reference composition locals
+
+`Modifier.Node` modifiers don't automatically observe changes to Compose state
+objects, like `CompositionLocal`. The advantage `Modifier.Node` modifiers have
+over modifiers that are just created with a composable factory is that they can
+read the value of the composition local from where the modifier is used in your
+UI tree, not where the modifier is allocated, using [`currentValueOf`](https://developer.android.com/reference/kotlin/androidx/compose/ui/node/CompositionLocalConsumerModifierNode#(androidx.compose.ui.node.CompositionLocalConsumerModifierNode).currentValueOf(androidx.compose.runtime.CompositionLocal)).
+
+However, modifier node instances don't automatically observe state changes. To
+automatically react to a composition local changing, you can read its current
+value inside a scope:
+
+- `DrawModifierNode`: [`ContentDrawScope`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DrawModifierNode.kt;l=31)
+- `LayoutModifierNode`: [`MeasureScope`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNode.kt;l=64) \& [`IntrinsicMeasureScope`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNode.kt;l=87)
+- `SemanticsModifierNode`: [`SemanticsPropertyReceiver`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt;l=788)
+
+This example observes the value of `LocalContentColor` to draw a background
+based on its color. As `ContentDrawScope` does observe snapshot changes, this
+automatically redraws when the value of `LocalContentColor` changes:
+
+
+```kotlin
+class BackgroundColorConsumerNode :
+ Modifier.Node(),
+ DrawModifierNode,
+ CompositionLocalConsumerModifierNode {
+ override fun ContentDrawScope.draw() {
+ val currentColor = currentValueOf(LocalContentColor)
+ drawRect(color = currentColor)
+ drawContent()
+ }
+}
+```
+
+
+
+To react to state changes outside of a scope and automatically update your
+modifier, use an [`ObserverModifierNode`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ObserverModifierNode.kt).
+
+For example, [`Modifier.scrollable`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt;l=269) uses this technique to
+observe changes in `LocalDensity`. A simplified example is shown in the
+following example:
+
+
+```kotlin
+class ScrollableNode :
+ Modifier.Node(),
+ ObserverModifierNode,
+ CompositionLocalConsumerModifierNode {
+
+ // Place holder fling behavior, we'll initialize it when the density is available.
+ val defaultFlingBehavior = DefaultFlingBehavior(splineBasedDecay(UnityDensity))
+
+ override fun onAttach() {
+ updateDefaultFlingBehavior()
+ observeReads { currentValueOf(LocalDensity) } // monitor change in Density
+ }
+
+ override fun onObservedReadsChanged() {
+ // if density changes, update the default fling behavior.
+ updateDefaultFlingBehavior()
+ }
+
+ private fun updateDefaultFlingBehavior() {
+ val density = currentValueOf(LocalDensity)
+ defaultFlingBehavior.flingDecay = splineBasedDecay(density)
+ }
+}
+```
+
+
+
+### Animate a modifier
+
+`Modifier.Node` implementations have access to a `coroutineScope`. This allows
+for use of the [Compose Animatable APIs](https://developer.android.com/develop/ui/compose/animation/value-based#animatable). For example, this snippet modifies
+the `CircleNode` shown previously to fade in and out repeatedly:
+
+> [!NOTE]
+> **Note:** `Modifier.Node` can be reused in a composable. You can set the new state either by setting it in [`onAttach`](https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier.Node#onAttach()), or by resetting in [`onReset`](https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier.Node#onReset()).
+
+
+```kotlin
+class CircleNode(var color: Color) : Modifier.Node(), DrawModifierNode {
+ private lateinit var alpha: Animatable
+
+ override fun ContentDrawScope.draw() {
+ drawCircle(color = color, alpha = alpha.value)
+ drawContent()
+ }
+
+ override fun onAttach() {
+ alpha = Animatable(1f)
+ coroutineScope.launch {
+ alpha.animateTo(
+ 0f,
+ infiniteRepeatable(tween(1000), RepeatMode.Reverse)
+ ) {
+ }
+ }
+ }
+}
+```
+
+
+
+### Share state between modifiers using delegation
+
+`Modifier.Node` modifiers can delegate to other nodes. There are many use cases
+for this, such as extracting common implementations across different modifiers,
+but it can also be used to share common state across modifiers.
+
+For example, a basic implementation of a clickable modifier node that shares
+interaction data:
+
+
+```kotlin
+class ClickableNode : DelegatingNode() {
+ val interactionData = InteractionData()
+ val focusableNode = delegate(
+ FocusableNode(interactionData)
+ )
+ val indicationNode = delegate(
+ IndicationNode(interactionData)
+ )
+}
+```
+
+
+
+### Opt out of node auto-invalidation
+
+`Modifier.Node` nodes automatically invalidate when their corresponding
+`ModifierNodeElement` calls update. For complex modifiers, you might want to opt
+out of this behavior to gain more fine-grained control over when your modifier
+invalidates phases.
+
+This is particularly useful if your custom modifier modifies both layout and
+draw. Opting out of auto-invalidation lets you just invalidate draw when only
+draw-related properties, such as `color`, change. This avoids invalidating
+layout and can improve your modifier's performance.
+
+A hypothetical example of this is shown in the following example with a modifier
+that has a `color`, `size`, and `onClick` lambda as properties. This modifier
+invalidates only what is required, skipping any unneeded invalidation:
+
+
+```kotlin
+class SampleInvalidatingNode(
+ var color: Color,
+ var size: IntSize,
+ var onClick: () -> Unit
+) : DelegatingNode(), LayoutModifierNode, DrawModifierNode {
+ override val shouldAutoInvalidate: Boolean
+ get() = false
+
+ private val clickableNode = delegate(
+ ClickablePointerInputNode(onClick)
+ )
+
+ fun update(color: Color, size: IntSize, onClick: () -> Unit) {
+ if (this.color != color) {
+ this.color = color
+ // Only invalidate draw when color changes
+ invalidateDraw()
+ }
+
+ if (this.size != size) {
+ this.size = size
+ // Only invalidate layout when size changes
+ invalidateMeasurement()
+ }
+
+ // If only onClick changes, we don't need to invalidate anything
+ clickableNode.update(onClick)
+ }
+
+ override fun ContentDrawScope.draw() {
+ drawRect(color)
+ }
+
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints
+ ): MeasureResult {
+ val size = constraints.constrain(size)
+ val placeable = measurable.measure(constraints)
+ return layout(size.width, size.height) {
+ placeable.place(0, 0)
+ }
+ }
+}
+```
+
+
\ No newline at end of file
diff --git a/jetpack-compose/jetpack-compose-performance/references/android/develop/ui/compose/performance/backwards-write.md b/jetpack-compose/jetpack-compose-performance/references/android/develop/ui/compose/performance/backwards-write.md
new file mode 100644
index 0000000..7ee2939
--- /dev/null
+++ b/jetpack-compose/jetpack-compose-performance/references/android/develop/ui/compose/performance/backwards-write.md
@@ -0,0 +1,186 @@
+Compose executes a frame in three strictly ordered, forward-flowing phases:
+ **Figure 1.** The three phases of a Compose frame
+
+1. **Composition** : Runs `@Composable` functions to build and update the UI tree.
+2. **Layout**: Measures children and then places them.
+3. **Drawing**: Emits canvas draw commands to render pixels to the screen.
+
+Whenever a Compose `State` is **read** during any phase, Compose automatically
+**records a dependency** between that state and the corresponding phase.
+
+*** ** * ** ***
+
+## What makes a write "backwards"?
+
+A **backwards write** occurs whenever **a state is modified in a later phase**
+(or in a downstream scope---meaning a composable scope executed later in order
+within the same composition pass) **than where it was read**, forcing Compose to
+recompose by scheduling an earlier phase or composable to run again. A backwards
+write is an unoptimized recomposition loop.
+ **Figure 2.** Example of backwards write through the recomposition loop phases
+
+## Consequences of backwards writes
+
+Backwards writes aren't necessarily a bad thing, and they don't always trigger
+crashes, but they are inefficient and can harm app performance in several ways:
+
+- **Extra frame rendering and dropped frames**: A backwards write forces Compose to execute redundant composition passes across consecutive frames, wasting CPU and GPU resources and potentially causing jank.
+- **First-frame correctness issues**: If your component requires a backwards write to resolve its final dimensions or state, the first frame is rendered with invalid, default, or unsettled data (such as zero size or an incorrect offset). This causes visible visual popping or layout flashing when the second frame renders.
+- **Infinite recomposition loops**: If a state change alters layout sizing, and layout sizing continuously writes back a new value to state, you can risk creating an infinite frame loop where the screen constantly recomposes every frame without ever stabilizing.
+
+## Forward flow through phases
+
+State changes should always flow **forward** through the phases:
+
+| **Read Phase** | **Write Context** | **Acceptable?** | **Why** |
+|---|---|---|---|
+| **Layout** (`Modifier.offset { }`) | **Composition** | Yes | Composition updates the state → Layout reads it later in the same frame without recomposing. |
+| **Draw** (`graphicsLayer { }`, `drawBehind { }`) | **Composition** | Yes | Composition updates the state → Draw reads it in the final phase. Composition and Layout are skipped entirely. |
+| **Draw** | **Layout** | Yes | Layout updates the state, and draw reads it, valid flow. |
+| **Composition** | Event Callback (`onClick`, `onValueChange`) driving state change. **Note**: layout callbacks do not count as events. | Yes | An event mutates state that is used to drive composition. If the event happens out-of-frame (not in Composition, Layout or Draw), this is valid. |
+| **Composition** | Coroutine (`LaunchedEffect`) | Yes - with caution | Asynchronously updates state in response to lifecycle/events. Writes from effects can be valid, but can point to inefficient state layering. They should be avoided where possible. |
+| **Placement (In Layout)** | **Measure (In Layout)** | Yes | In Layout, updating the state then reading that state in placement is acceptable. |
+| **Measure (In Layout)** | **Placement (In Layout)** | **No - backwards write** | Writing to state in placement that's then further in the read state, causes remeasure loop. |
+| **Composition** | **Layout** (`onSizeChanged`, `LayoutModifier`) | **No** - **Backwards write** | Layout invalidates Composition → Recomposition loop. |
+| **Composition** | **Draw** (`drawWithContent`, `Canvas`) | **No - Backwards write** | Draw invalidates Composition → Recomposition loop. |
+
+## Phase combinations: Backwards versus forward
+
+The following are examples of backwards writes within Compose and how you can
+resolve them.
+
+### Backwards: Reading in Composition, writing in Layout
+
+- **What happens** : Composition reads `componentHeight` to determine what UI to emit. Later in the frame, the Layout phase measures or places views and writes a new value to `componentHeight` (for example, using `onSizeChanged`, `onGloballyPositioned`, or custom `LayoutModifier`).
+- **Result** : Modifying `componentHeight` in Layout invalidates the Composition phase that was just completed. Note that `onSizeChanged` reports size after the layout measurement pass completes. If the updated state value stabilizes on the next pass, the recomposition may halt after **one extra
+ frame** ; however, if the new value continues to alter the size, it results in an infinite frame loop. Furthermore, `onGloballyPositioned` runs after both layout and placement, making state writes inside it even more susceptible to continuous recomposition and relayout loops across consecutive frames.
+
+
+```kotlin
+// ❌ BAD: Read in Composition, Written in Layout (onSizeChanged)
+@Composable
+fun BadAspectRatioImage(painter: Painter) {
+ var calculatedHeight by remember { mutableStateOf(0.dp) }
+ val density = LocalDensity.current
+
+ // State read during COMPOSITION:
+ Image(
+ painter = painter,
+ contentDescription = "Dynamic Image",
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(calculatedHeight)
+ .onSizeChanged { size ->
+ // State write during LAYOUT phase!
+ // Triggers backwards write and recomposition pass
+ val aspectRatio = 16f / 9f
+ val widthDp = with(density) { size.width.toDp() }
+ calculatedHeight = widthDp / aspectRatio
+ }
+ )
+}
+
+// ✅ GOOD: Measure and calculate aspect ratio height in Phase 2 (Layout) without recomposition
+@Composable
+fun GoodAspectRatioImage(
+ painter: Painter,
+ aspectRatio: Float = 16f / 9f,
+ modifier: Modifier = Modifier
+) {
+ Layout(
+ content = {
+ Image(
+ painter = painter,
+ contentDescription = "Dynamic Image"
+ )
+ },
+ modifier = modifier
+ ) { measurables, constraints ->
+ val width = constraints.maxWidth
+ val height = (width / aspectRatio).toInt() // Illustrative, you can use Modifier.aspectRatio()
+ val imageConstraints = constraints.copy(
+ minWidth = width,
+ maxWidth = width,
+ minHeight = height,
+ maxHeight = height
+ )
+ val placeable = measurables.first().measure(imageConstraints)
+ layout(width, height) {
+ placeable.placeRelative(0, 0)
+ }
+ }
+}
+```
+
+
+
+### Backwards: Reading in Composition, writing in Drawing
+
+- **What happens** : State is read in the Composable body (Composition phase), but mutated inside `Modifier.drawWithContent`, `Modifier.drawBehind`, or `Canvas` (Draw phase).
+- **Result**: Draw phase mutates state → Composition invalidated → endless loop.
+
+
+```kotlin
+// ❌ BAD: Read in Composition, Written in Draw ()
+@Composable
+fun BadBackwardsWriteDraw() {
+ var componentHeight by remember { mutableStateOf(0.dp) }
+ // State read during COMPOSITION:
+ Text(
+ text = "Height is: $componentHeight",
+ modifier = Modifier.drawBehind {
+ // State write during the DRAW phase!
+ // Invalidates Composition -> triggers recomposition loop!
+ componentHeight = size.height.dp
+ }
+ )
+}
+```
+
+
+
+### Backwards: Reading in Composition, writing in Composition (same phase)
+
+- **What happens** : Reading `count` in composable function, and modifying `count` directly in another composables content slot after reading it.
+- **Result**: The snapshot system records the read and subsequent write within the same composition pass, immediately invalidating the current scope.
+
+
+```kotlin
+// ❌ BAD: Direct write in Composable body after read
+@Composable
+fun BadCounter() {
+ var count by remember { mutableIntStateOf(0) }
+ Text("Count: $count") // State read in Composition
+ Button(onClick = {}) {
+ count++ // State write in Composition (Backwards write!)
+ }
+}
+// Acceptable - but error-prone as someone may add a read before the write : Direct write in Composable body before read
+@Composable
+fun OkCounter() {
+ var count by remember { mutableIntStateOf(0) }
+ Button(onClick = {}) {
+ count++ // State write in Composition
+ }
+ Text("Count: $count") // State read in Composition
+}
+```
+
+
+
+*** ** * ** ***
+
+## Key rules to prevent backwards writes
+
+1. **Don't write to state in `onGloballyPositioned`, `onSizeChanged`, or
+ `LayoutModifier` if that state is read in Composition** as this causes the first-frame-correctness problem.
+ - If layout coordinates or sizes are only needed for custom drawing, read them directly in the Draw or Layout phase (for example, using `Modifier.drawWithCache` or `Modifier.layout`).
+ - **For Window-Level Sizing (`WindowWidthSizeClass`):** Hoist size observation to the window level. Composition branches on window size classes before local measurement occurs.
+ - **Keep Composition Uniform:** Use a single custom Layout or components like `FlowRow` or `LazyVerticalGrid` that adjust measurement and placement during Phase 2 without recomposing or altering state used in composition.
+ - **Use Sub-composition:** Use `BoxWithConstraints` or `SubcomposeLayout` when child composables must branch based on local width or height. Use caution: Subcomposition carries a performance cost and can usually be avoided.
+ - **As a last resort:** Allow the first frame to be wrong, storing size in `onSizeChanged` to trigger a second frame recomposition. This causes visible layout popping, jank, and risks infinite loops.
+2. **Don't mutate state after its read the first time in composition** :
+ - Although you may safely write to `MutableState` objects during composition outside of a`SideEffect`, take special care to ensure that you don't write to a state that you may have previously read in composition. It's recommended to use `rememberUpdatedState` when this need for a state write in composition arises. Writing to a state during composition in another way is usually a sign of a missing effect or an inappropriately designed state or composable. Remember that composition is optimistic and always executes with the latest value of a state, so you may not see all state changes in recompositions. UI updates shouldn't be used as a way of handling one-time events, which makes it uncommon for a state's value to require being updated as a result of recomposing.
+ - Avoid mutating states that are observed outside of composition (for example, `ViewModel` fields or `isVisible` flags). State writes that affect composition belong in event lambdas (`onClick`), coroutines (`LaunchedEffect`), or side effects (`SideEffect`). `rememberUpdatedState` is an exception as it is designed to mutate state that is only used in `@Composable` body.
+3. **Defer state reads to the latest possible phase** :
+ - Reading states in Draw (`Modifier.graphicsLayer { alpha = ... }`) or Layout (`Modifier.offset { IntOffset(...) }`) ensures that changes only invalidate Phase 2 or 3, completely skipping Phase 1 (Composition).
\ No newline at end of file
diff --git a/jetpack-compose/jetpack-compose-performance/references/android/develop/ui/compose/performance/stability/strongskipping.md b/jetpack-compose/jetpack-compose-performance/references/android/develop/ui/compose/performance/stability/strongskipping.md
new file mode 100644
index 0000000..8dbe2a3
--- /dev/null
+++ b/jetpack-compose/jetpack-compose-performance/references/android/develop/ui/compose/performance/stability/strongskipping.md
@@ -0,0 +1,137 @@
+Strong Skipping is a mode available in the Compose compiler. When enabled, it
+changes the compiler's behavior in two ways:
+
+- Composables with unstable parameters become [skippable](https://developer.android.com/develop/ui/compose/performance/stability#functions)
+- Lambdas with unstable captures are [remembered](https://developer.android.com/develop/ui/compose/state#state-in-composables)
+
+> [!NOTE]
+> **Note:** Strong Skipping is enabled by default in Kotlin 2.0.20. The full list of options are available in the [Compose Compiler Gradle Plugin reference](https://kotlinlang.org/docs/compose-compiler-options.html).
+
+## Enable strong skipping mode
+
+Strong skipping is enabled by default in Kotlin 2.0.20.
+
+To enable strong skipping for a Gradle module in a release prior to 2.0.20,
+include the following option in
+the `composeCompiler` block of your Gradle configuration:
+
+ android { ... }
+
+ composeCompiler {
+ enableStrongSkippingMode = true
+ }
+
+## Composable skippability
+
+Strong skipping mode relaxes some of the [stability](https://developer.android.com/develop/ui/compose/performance/stability) rules normally applied
+by the Compose compiler when it comes to skipping and composable functions. By
+default, the Compose compiler marks a composable function as skippable if all of
+its arguments have stable values. Strong skipping mode changes this.
+
+With strong skipping enabled, all restartable composable functions become
+skippable. This applies whether or not they have unstable parameters.
+Non-restartable composable functions remain unskippable.
+
+### When to skip
+
+To determine whether to skip a composable during recomposition, Compose compares
+the value of each parameter with their previous values. The type of comparison
+depends on the [stability](https://developer.android.com/develop/ui/compose/performance/stability) of the parameter.
+
+- Unstable parameters are compared using instance equality (`===`)
+- Stable parameters are compared using object equality (`Object.equals()`)
+
+If all parameters meet these requirements, Compose skips the composable during
+recomposition.
+
+You might want a composable to opt out of strong skipping. That is, you might
+want a restartable but non-skippable composable. In this case, use the
+`@NonSkippableComposable` annotation.
+
+ @NonSkippableComposable
+ @Composable
+ fun MyNonSkippableComposable {}
+
+### Annotate classes as stable
+
+If you want an object using object equality instead of instance equality,
+continue to annotate the given class with `@Stable`.
+An example of when you might
+have to do this is when observing an entire list of objects, data sources such
+as Room will allocate new objects for every item in the list any time one of
+them changes.
+
+## Lambda memoization
+
+Strong skipping mode also enables more [memoization](https://en.wikipedia.org/wiki/Memoization) of lambdas
+inside composables. With strong skipping enabled, every lambda inside a
+composable function will be automatically remembered.
+
+### Examples
+
+To achieve memoization of lambdas inside composables when using strong skipping,
+the compiler wraps your lambda with a `remember` call. It is keyed with the
+captures of the lambda.
+
+Consider a case where you have a lambda as in the following example:
+
+ @Composable
+ fun MyComposable(unstableObject: Unstable, stableObject: Stable) {
+ val lambda = {
+ use(unstableObject)
+ use(stableObject)
+ }
+ }
+
+With strong skipping enabled, the compiler memoizes the lambda by wrapping it in
+a `remember` call:
+
+ @Composable
+ fun MyComposable(unstableObject: Unstable, stableObject: Stable) {
+ val lambda = remember(unstableObject, stableObject) {
+ {
+ use(unstableObject)
+ use(stableObject)
+ }
+ }
+ }
+
+The keys follow the same comparison rules as composable functions. The runtime
+compares unstable keys using instance equality. It compares stable keys using
+object equality.
+
+> [!NOTE]
+> **Note:** This is slightly different to a typical `remember` call where the Compose runtime compares all keys using object equality.
+
+### Memoization and recomposition
+
+This optimization greatly increases the number of composables that the runtime
+skips during recomposition. Without memoization, the runtime is much more likely
+to allocate a new lambda to any composable that takes a lambda parameter during
+recomposition. As a result, the new lambda has parameters that are not equal to
+the last composition. This results in recomposition.
+
+> [!NOTE]
+> **Note:** A common misconception is that lambdas with unstable captures are themselves unstable objects. This is not true. The Compose compiler always considers lambdas to be stable. However, with Strong skipping disabled, the compiler does not memoize them and the runtime allocates them during recomposition. As such, lambdas with unstable captures cause Compose not to skip composables because they have unequal parameters.
+
+### Avoid memoization
+
+If you have a lambda that you don't want to memoize, use the `@DontMemoize`
+annotation.
+
+ val lambda = @DontMemoize {
+ ...
+ }
+
+## APK Size
+
+When compiled, skippable Composables result in more generated code than
+composables that are not skippable. With strong skipping enabled, the compiler
+marks nearly all composables as skippable and wraps all lambdas in a
+`remember{...}`. Due to this, enabling strong skipping mode has a very small
+impact on the APK size of your application.
+
+Enabling strong skipping in [Now In Android](https://github.com/android/nowinandroid) increased the APK
+size by 4kB. The difference in size largely depends on the number of previously
+unskippable composables that were present in the given app but should be
+relatively minor.
\ No newline at end of file
diff --git a/jetpack-compose/jetpack-compose-performance/references/animation.md b/jetpack-compose/jetpack-compose-performance/references/animation.md
new file mode 100644
index 0000000..6fdce35
--- /dev/null
+++ b/jetpack-compose/jetpack-compose-performance/references/animation.md
@@ -0,0 +1,290 @@
+## 1. Phase deferral for animations (skip Composition to Draw and Layout)
+
+When animating visual properties (such as alpha, scale, rotation, color, or
+translation), never read the animated state directly in the Composable body
+during the Composition phase. Reading state during composition causes the entire
+composable tree to recompose on every animation tick (60 to 120 times per
+second).
+
+Wrap animated state reads in lambdas so evaluation is deferred to the **Draw**
+or
+**Layout** phase.
+
+### A. Alpha, rotation, and scale: defer to Draw with `graphicsLayer`
+
+- **Bad (Recomposes on every animation frame)**:
+
+
+ ```kotlin
+ val alpha by animateFloatAsState(targetValue = targetAlpha, label = "Alpha")
+ // Reading alpha here registers a composition dependency on every frame
+ Box(modifier = Modifier.alpha(alpha))
+ ```
+
+
+
+- **Optimized (Bypasses Composition and Layout, evaluates in Draw phase)**:
+
+
+ ```kotlin
+ val alpha by animateFloatAsState(targetValue = targetAlpha, label = "Alpha")
+ // graphicsLayer with lambda skips composition and runs in the draw phase
+ Box(
+ modifier = Modifier.graphicsLayer {
+ this.alpha = alpha
+ }
+ )
+ ```
+
+
+
+### B. Animated offsets: defer to Layout with `Modifier.offset`
+
+- **Bad (Recomposes on every pixel shift)**:
+
+
+ ```kotlin
+ val animatedOffset by animateDpAsState(
+ targetValue = targetOffset,
+ label = "Offset",
+ )
+ Box(modifier = Modifier.offset(x = animatedOffset, y = 0.dp))
+ ```
+
+
+
+- **Optimized (Skips Composition, goes straight to Layout phase)**:
+
+
+ ```kotlin
+ val animatedOffset by animateIntAsState(
+ targetValue = targetOffsetPx,
+ label = "Offset",
+ )
+ Box(
+ modifier = Modifier.offset {
+ IntOffset(x = animatedOffset, y = 0)
+ }
+ )
+ ```
+
+
+
+### C. Animated size: defer to Layout with `Modifier.layout` or `animateBounds`
+
+Animating container dimensions using `animateDpAsState` and `Modifier.size(...)`
+directly in the Composable body triggers recomposition of the composable and its
+children on every animation tick.
+
+Use layout-phase constraint animation or Lookahead bounds animation instead:
+
+- **Bad (Recomposes entire subtree on every animation frame)**:
+
+
+ ```kotlin
+ val animatedSize by animateDpAsState(
+ targetValue = targetSize,
+ label = "Size",
+ )
+ Box(modifier = Modifier.size(animatedSize))
+ ```
+
+
+
+- **Optimized (Phase Deferral with `Modifier.layout`)**: Animate constraints
+ in the layout pass without re-triggering composition:
+
+
+ ```kotlin
+ val animatedWidth by animateIntAsState(
+ targetValue = targetWidthPx,
+ label = "Width",
+ )
+ Box(
+ modifier = Modifier.layout { measurable, constraints ->
+ val placeable = measurable.measure(
+ constraints.copy(
+ minWidth = animatedWidth,
+ maxWidth = animatedWidth
+ )
+ )
+ layout(placeable.width, placeable.height) {
+ placeable.placeRelative(0, 0)
+ }
+ }
+ )
+ ```
+
+
+
+- **Optimized (Lookahead Scope with `animateBounds`)** : Inside a
+ `LookaheadScope`, animate size changes smoothly using `animateBounds`:
+
+
+ ```kotlin
+ with(lookaheadScope) {
+ Box(modifier = Modifier.animateBounds(this, Modifier.size(finalSize)))
+ }
+ ```
+
+
+
+### D. Custom composable parameters (lambda providers)
+
+When creating custom composables that animate properties internally, pass a
+lambda provider (`alphaProvider: () -> Float` or `offsetProvider: () ->
+IntOffset`) instead of the raw animated value. This allows the caller to avoid
+reading the state in Composition and allows the child to defer the read to
+`graphicsLayer` or `offset`.
+
+- **Bad (Caller must read state in Composition to pass Float)**:
+
+
+ ```kotlin
+ @Composable
+ fun AnimatedCard(alpha: Float) {
+ Box(modifier = Modifier.graphicsLayer { this.alpha = alpha })
+ }
+ ```
+
+
+
+- **Optimized (Caller passes lambda; read is deferred until child's Draw
+ phase)**:
+
+
+ ```kotlin
+ @Composable
+ fun AnimatedCard(alphaProvider: () -> Float) {
+ Box(modifier = Modifier.graphicsLayer { this.alpha = alphaProvider() })
+ }
+ ```
+
+
+
+## 2. Shared transitions and shared elements (`SharedTransitionScope`)
+
+`SharedTransitionScope` enables coordinated bounds and element transitions
+across layouts and navigation destinations.
+
+### 1. Defer `sharedBounds` and `sharedElement` in fast-scrolling lists
+
+**Recommendation:** For long or fast-scrolling lists (`LazyColumn`, `LazyRow`,
+`LazyVerticalGrid`, `HorizontalPager`), consider attaching
+`Modifier.sharedBounds` or `Modifier.sharedElement` only when items enter the
+visible viewport using `Modifier.onVisibilityChanged`.
+
+While spatial coordinate tracking is only enabled when a match is found,
+unconditionally attaching shared transition modifiers introduces modifier
+instantiation and state observation overhead for **pre-fetched items that may
+never end up on screen** during fast scroll flings. Deferring attachment until
+visible avoids this instantiation cost for offscreen items.
+> **Note:**
+> This recommendation applies to current versions of Compose. Upstream platform
+> performance optimizations to make `Modifier.sharedBounds` and
+> `Modifier.sharedElement` lighter-weight and avoid prefetch overhead are in
+> active development.
+
+- **Before (Instantiation runs on all pre-fetched items even if never
+ displayed)**:
+
+
+ ```kotlin
+ @Composable
+ fun FeedItemRow(
+ item: FeedItem,
+ sharedTransitionScope: SharedTransitionScope,
+ animatedVisibilityScope: AnimatedVisibilityScope,
+ ) {
+ with(sharedTransitionScope) {
+ ItemContent(
+ modifier = Modifier
+ .fillMaxWidth()
+ .sharedBounds(
+ rememberSharedContentState(key = item.id),
+ animatedVisibilityScope = animatedVisibilityScope,
+ )
+ )
+ }
+ }
+ ```
+
+
+
+- **Optimized (Shared transition modifier deferred until item enters
+ viewport)**:
+
+
+ ```kotlin
+ @Composable
+ fun FeedItemRow(
+ item: FeedItem,
+ sharedTransitionScope: SharedTransitionScope,
+ animatedVisibilityScope: AnimatedVisibilityScope,
+ ) {
+ var isVisibleInWindow by remember { mutableStateOf(false) }
+
+ with(sharedTransitionScope) {
+ val sharedBoundsModifier = if (isVisibleInWindow) {
+ Modifier.sharedBounds(
+ rememberSharedContentState(key = item.id),
+ animatedVisibilityScope = animatedVisibilityScope,
+ )
+ } else {
+ Modifier
+ }
+
+ ItemContent(
+ modifier = Modifier
+ .fillMaxWidth()
+ .onVisibilityChanged(
+ minFractionVisible = 0.001f,
+ ) { visible ->
+ isVisibleInWindow = visible
+ }
+ .then(sharedBoundsModifier)
+ )
+ }
+ }
+ ```
+
+
+
+## 3. Persistent `Animatable` in custom modifiers (`Modifier.Node`)
+
+When building custom animated modifiers, do not use `composed { ... }` with
+`remember { Animatable(...) }` and `LaunchedEffect`.
+
+Store `Animatable` directly as a member property on the `Modifier.Node` and
+launch the animation in `onAttach()` using the node's built-in `coroutineScope`:
+
+
+```kotlin
+private class FadeRevealNode(
+ var targetAlpha: Float,
+ var durationMillis: Int
+) : Modifier.Node(), DrawModifierNode {
+ // Persistent node property (avoids remember overhead)
+ private val alpha = Animatable(0f)
+
+ override fun onAttach() {
+ // Launches animation tied directly to node lifecycle
+ coroutineScope.launch {
+ alpha.animateTo(targetAlpha, tween(durationMillis))
+ }
+ }
+
+ override fun ContentDrawScope.draw() {
+ drawContext.canvas.saveLayer(
+ bounds = size.toRect(),
+ paint = Paint().apply {
+ this.alpha = this@FadeRevealNode.alpha.value
+ }
+ )
+ drawContent()
+ drawContext.canvas.restore()
+ }
+}
+```
+
+
\ No newline at end of file
diff --git a/jetpack-compose/jetpack-compose-performance/references/effects-and-threading.md b/jetpack-compose/jetpack-compose-performance/references/effects-and-threading.md
new file mode 100644
index 0000000..46ad67b
--- /dev/null
+++ b/jetpack-compose/jetpack-compose-performance/references/effects-and-threading.md
@@ -0,0 +1,531 @@
+## 1. Main thread blocking, offloading, and caching
+
+Move non-UI work off the main thread, avoid heavy object allocations directly
+inside Composable bodies, and cache expensive calculations across recompositions
+using `remember`.
+
+### 1. Cache expensive computations in composable body
+
+If a Composable body performs sorting, filtering, or heavy string manipulation
+on a collection, wrap the calculation in `remember`:
+
+- **Bad (Recalculated on every recomposition pass)**:
+
+
+ ```kotlin
+ @Composable
+ fun FilteredFeed(rawList: List, query: String) {
+ // Avoid: Heavy filtering and sorting on every recomposition
+ val filteredList = rawList
+ .filter { it.title.contains(query) }
+ .sortedBy { it.timestamp }
+ PostList(posts = filteredList)
+ }
+ ```
+
+
+
+- **Optimized (Cached calculation using remember)**:
+
+
+ ```kotlin
+ @Composable
+ fun FilteredFeed(rawList: List, query: String) {
+ val filteredList = remember(rawList, query) {
+ rawList
+ .filter { it.title.contains(query) }
+ .sortedBy { it.timestamp }
+ }
+ PostList(posts = filteredList)
+ }
+ ```
+
+
+
+For collections with very few elements (3 to 5 items), wrapping calculations in
+`remember` introduces allocation overhead without measurable benefit. Benchmark
+caching changes using Macro benchmark.
+
+### 2. Avoid heavy object allocations in composition
+
+Never allocate or compute heavy objects (such as `RoundedPolygon`, complex
+paths, `Paint`, or date formatters) directly in the Composable body. Wrap them
+in `remember { ... }` or hoist them out of the Composable.
+
+- **Bad (New date formatter allocated on every frame/recomposition)**:
+
+
+ ```kotlin
+ @Composable
+ fun DateBadge(timestamp: Long) {
+ val formatter = SimpleDateFormat("MMM dd, yyyy", LocalLocale.current.platformLocale)
+ Text(text = formatter.format(Date(timestamp)))
+ }
+ ```
+
+
+
+- **Optimized (Formatter allocated once and reused across timestamp
+ updates)**:
+
+
+ ```kotlin
+ // Reusable formatter allocated once at top-level or remembered
+ private val dateFormatter =
+ SimpleDateFormat("MMM dd, yyyy", Locale.getDefault())
+
+ @Composable
+ fun DateBadge(timestamp: Long) {
+ val formattedDate = dateFormatter.format(Date(timestamp))
+
+ Text(text = formattedDate)
+ }
+ ```
+
+
+
+Conversion functions like `.toShape()` or `painterResource()` are `@Composable`
+themselves and internally handle caching.
+
+### 3. Offload I/O and heavy work to `Dispatchers.IO` with `produceState`
+
+Never perform blocking operations (like database queries, disk I/O, heavy file
+parsing, or blocking binder transactions) directly on the main thread. Offload
+them to `Dispatchers.IO` using `produceState`:
+
+- **Bad (Blocking I/O on main UI thread)**:
+
+
+ ```kotlin
+ @Composable
+ fun ProfileScreen(fileUri: Uri) {
+ var state by remember { mutableStateOf(null) }
+ LaunchedEffect(fileUri) {
+ val data = parseJsonFromDisk(fileUri) // Blocks main dispatcher!
+ state = data
+ }
+ }
+ ```
+
+
+
+- **Optimized (Offloaded to IO dispatcher via `produceState`)**:
+
+
+ ```kotlin
+ @Composable
+ fun ProfileScreen(fileUri: Uri) {
+ val state by produceState(initialValue = null, fileUri) {
+ value = withContext(Dispatchers.IO) { parseJsonFromDisk(fileUri) }
+ }
+ }
+ ```
+
+
+
+## 2. Side effect execution and lifecycle rules
+
+Structure side effects to be lifecycle-aware, idempotent, and properly scoped.
+Prohibit direct side-effects in the Composable body, prefer `SideEffect` for
+non-suspending operations, hoist flow or receiver subscriptions out of item
+composables, and always use `collectAsStateWithLifecycle()`.
+
+### 1. Prohibit direct side-effects in composable bodies
+
+The Composable body must remain idempotent and free of direct side effects. Side
+effects placed directly in composition run on every recomposition attempt
+(including speculative, interrupted, or discarded passes).
+
+- **Bad (Side-effect runs directly in composition pass)**:
+
+
+ ```kotlin
+ @Composable
+ fun UserProfile(userId: String, viewModel: ProfileViewModel) {
+ // Avoid: Runs on every recomposition and speculative pass
+ viewModel.trackProfileImpression(userId)
+ Text(text = "User: $userId")
+ }
+ ```
+
+
+
+- **Optimized (Wrapped in SideEffect or user event handler)**:
+
+
+ ```kotlin
+ @Composable
+ fun UserProfile(userId: String, viewModel: ProfileViewModel) {
+ // Compose 1.12+: keyed SideEffect
+ SideEffect(userId) {
+ viewModel.trackProfileImpression(userId)
+ }
+ Text(text = "User: $userId")
+ }
+ ```
+
+
+
+### 2. Prohibit `LaunchedEffect` for non-suspend operations
+
+Never wrap synchronous, non-suspending operations in `LaunchedEffect`.
+`LaunchedEffect` creates a standalone coroutine, allocates coroutine context
+machinery, and dispatches to a thread even when no suspension occurs.
+
+- **Bad (Unnecessary coroutine allocation for synchronous call)**:
+
+
+ ```kotlin
+ LaunchedEffect(itemId) {
+ analyticsTracker.trackScreenView(itemId) // Non-suspend function!
+ }
+ ```
+
+
+
+- **Optimized (Use `SideEffect` for synchronous execution on successful
+ composition)**:
+
+
+ ```kotlin
+ // Compose 1.12+ (supports keys directly):
+ SideEffect(itemId) {
+ analyticsTracker.trackScreenView(itemId)
+ }
+ ```
+
+
+
+- **Warning** : Never use `DisposableEffect(keys) { ... onDispose {} }` with an
+ empty `onDispose` block to trigger a keyed synchronous effect. Check the
+ project's Compose version in `gradle/libs.versions.toml` or
+ `build.gradle(.kts)`: use `SideEffect(keys)` on Compose 1.12+, or
+ `SideEffect { ... }` with a `remember`ed key check on pre-1.12 versions.
+
+### 3. Lifecycle-aware flow collection (`collectAsStateWithLifecycle`)
+
+In Android UI development, use `collectAsStateWithLifecycle()` when collecting
+Kotlin `Flow`s that actively produce background work.
+`collectAsStateWithLifecycle` (from
+`androidx.lifecycle:lifecycle-runtime-compose`) automatically subscribes and
+cancels flow collection based on the Android `Lifecycle` state (defaulting to
+`Lifecycle.State.STARTED`), preventing background CPU usage, battery drain,
+resource leaks, and unnecessary UI updates when the app or screen is stopped.
+
+- **Performance Nuance \& When to Prefer `collectAsState()`**:
+
+ - **Benchmark Overhead** : `collectAsStateWithLifecycle()` is \~3-4x slower to subscribe and dispatch compared to `collectAsState()` due to Android `Lifecycle` observer registration and lifecycle-aware state machine overhead.
+ - **In-Memory \& Scoped StateFlows** : For lightweight, in-memory `StateFlow`s (such as UI states emitted by a ViewModel already scoped with `SharingStarted.WhileSubscribed(5000)`), `collectAsState()` is often more performant and avoids this overhead.
+ - **When to Require `collectAsStateWithLifecycle()`** : Reserve `collectAsStateWithLifecycle()` for cold flows, database observation streams, sensors, location updates, or external polling streams that must halt emission when the screen is not visible.
+ - **Non-Android platforms** : In Compose Multiplatform (Desktop, Web, iOS) where the Android `Lifecycle` library is not present, use `collectAsState()`.
+ - **Headless / Unit Test environments** : In non-UI scopes or pure unit tests where no `LifecycleOwner` is available.
+- **Bad (Continues collecting in background on Android, leaking resources and
+ CPU cycles)**:
+
+
+ ```kotlin
+ @Composable
+ fun HomeFeed(viewModel: FeedViewModel) {
+ // Avoid on Android: Flow stays active when activity is stopped
+ val uiState by viewModel.feedState.collectAsState()
+ FeedContent(uiState = uiState)
+ }
+ ```
+
+
+
+- **Optimized (Lifecycle-aware: pauses collection when backgrounded)**:
+
+
+ ```kotlin
+ // import androidx.lifecycle.compose.collectAsStateWithLifecycle
+
+ @Composable
+ fun HomeFeed(viewModel: FeedViewModel) {
+ val uiState by viewModel.feedState.collectAsStateWithLifecycle()
+ FeedContent(uiState = uiState)
+ }
+ ```
+
+
+
+### 4. Hoist `BroadcastReceiver`s and listeners to prevent per-item registration
+
+Never register a `BroadcastReceiver` or system listener (such as
+`ACTION_TIMEZONE_CHANGED` or network connectivity changes) inside list item
+Composables. This registers a separate receiver for every visible item on the
+main thread, causing severe scrolling latency, memory leaks, and frame drops.
+
+Register a single receiver in a parent or screen-level Composable using
+`DisposableEffect`, and propagate the state down using parameters or
+`CompositionLocalProvider`.
+
+- **Bad (Registers a separate BroadcastReceiver for every visible item in
+ LazyColumn)**:
+
+
+ ```kotlin
+ @Composable
+ fun TimeZoneListItem(timeZoneId: String) {
+ val context = LocalContext.current
+ DisposableEffect(Unit) {
+ // Avoid: Multiplied BroadcastReceivers per visible item!
+ val receiver = object : BroadcastReceiver() {
+ // ...
+ }
+ context.registerReceiver(
+ receiver,
+ IntentFilter(Intent.ACTION_TIMEZONE_CHANGED),
+ )
+ onDispose { context.unregisterReceiver(receiver) }
+ }
+ ItemRow(timeZoneId)
+ }
+ ```
+
+
+
+- **Optimized (Single BroadcastReceiver hoisted to parent container)**:
+
+
+ ```kotlin
+ @Composable
+ fun TimeZoneList(timeZoneIds: List) {
+ val context = LocalContext.current
+ var currentTimeZone by remember {
+ mutableStateOf(TimeZone.getDefault())
+ }
+
+ DisposableEffect(context) {
+ val receiver = object : BroadcastReceiver() {
+ override fun onReceive(c: Context?, intent: Intent?) {
+ currentTimeZone = TimeZone.getDefault()
+ }
+ }
+ context.registerReceiver(
+ receiver,
+ IntentFilter(Intent.ACTION_TIMEZONE_CHANGED),
+ )
+ onDispose { context.unregisterReceiver(receiver) }
+ }
+
+ LazyColumn {
+ items(timeZoneIds, key = { it }) { id ->
+ TimeZoneListItem(
+ timeZoneId = id,
+ currentTimeZone = currentTimeZone,
+ )
+ }
+ }
+ }
+ ```
+
+
+
+### 5. Guard optional effects and match effect type to work
+
+Never spawn `LaunchedEffect` or `DisposableEffect` unconditionally if optional
+callbacks or trigger handlers are `null`. Guard the effect with `if (callback !=
+null)` to avoid allocating unnecessary coroutine jobs and snapshot effect
+tracking structures. Furthermore, ensure the effect API matches the execution
+type:
+
+- **Synchronous Callbacks (`() -> Unit`)** : Use `SideEffect` or keyed `SideEffect(onInit)` rather than launching a coroutine job.
+- **Suspending Callbacks (`suspend () -> Unit`)** : Use
+ `LaunchedEffect(onInit)` guarded by `if (onInit != null)`.
+
+- **Bad (Spawns empty coroutine and effect nodes when callback is null or
+ synchronous)**:
+
+
+ ```kotlin
+ // Avoid: Launches coroutine for synchronous work, runs even if null
+ LaunchedEffect(onInit) {
+ onInit?.invoke()
+ }
+ ```
+
+
+
+- **Optimized (Synchronous work uses SideEffect; guarded if optional)**:
+
+
+ ```kotlin
+ if (onInit != null) {
+ SideEffect(onInit) {
+ onInit()
+ }
+ }
+ ```
+
+
+
+### 6. Prevent stale captures with `rememberUpdatedState`
+
+If a long-running, periodic, or looping coroutine inside `LaunchedEffect`
+references a Composable parameter or callback, use `rememberUpdatedState` to
+ensure it always uses the latest value without restarting the effect:
+
+- **Bad (Stale capture if onTick parameter changes during loop lifetime)**:
+
+
+ ```kotlin
+ @Composable
+ fun PeriodicTicker(intervalMs: Long, onTick: () -> Unit) {
+ LaunchedEffect(intervalMs) {
+ while (isActive) {
+ delay(intervalMs)
+ onTick() // Stale reference if onTick callback instance changes!
+ }
+ }
+ }
+ ```
+
+
+
+- **Optimized (rememberUpdatedState provides latest callback without
+ cancelling timer loop)**:
+
+
+ ```kotlin
+ @Composable
+ fun PeriodicTicker(intervalMs: Long, onTick: () -> Unit) {
+ val currentOnTick by rememberUpdatedState(onTick)
+ LaunchedEffect(intervalMs) {
+ while (isActive) {
+ delay(intervalMs)
+ currentOnTick()
+ }
+ }
+ }
+ ```
+
+
+
+### 7. Replace legacy timers with coroutine delays
+
+Avoid using legacy Android handlers like `Handler.postDelayed` or `TimerTask` in
+UI code. Use `LaunchedEffect` combined with `delay()` for clean, lifecycle-aware
+timing that is automatically cancelled when the Composable leaves the
+composition.
+
+- **Bad (Legacy Handler risks memory leak and out-of-lifecycle execution)**:
+
+
+ ```kotlin
+ DisposableEffect(Unit) {
+ val handler = Handler(Looper.getMainLooper())
+ val runnable = Runnable { showBanner = false }
+ handler.postDelayed(runnable, 3000L)
+ onDispose { handler.removeCallbacks(runnable) }
+ }
+ ```
+
+
+
+- **Optimized (Clean, lifecycle-aware coroutine delay)**:
+
+
+ ```kotlin
+ LaunchedEffect(Unit) {
+ delay(3000L)
+ showBanner = false
+ }
+ ```
+
+
+
+### 8. Combine related `LaunchedEffect`s (reduce number of effects)
+
+Every `LaunchedEffect` attached to a composable allocates slot table records,
+registers coroutine launcher machinery, and requires cancellation tracking
+during teardown.
+
+When multiple asynchronous tasks share the same trigger key (such as `Unit` on
+initial screen load), combine them into a single `LaunchedEffect` block with
+nested `launch { ... }` calls instead of declaring multiple standalone
+`LaunchedEffect`s.
+
+- **Bad (Multiple separate LaunchedEffects incur redundant effect overhead)**:
+
+
+ ```kotlin
+ @Composable
+ fun ShoppingCartScreen(viewModel: CartViewModel) {
+ // Avoid: 3 LaunchedEffects allocate 3 effect nodes and launchers
+ LaunchedEffect(Unit) { viewModel.loadCart() }
+ LaunchedEffect(Unit) { viewModel.loadPaymentMethods() }
+ LaunchedEffect(Unit) { viewModel.loadDeliveryAddresses() }
+
+ CartContent()
+ }
+ ```
+
+
+
+- **Optimized (Single LaunchedEffect manages multiple concurrent
+ coroutines)**:
+
+
+ ```kotlin
+ @Composable
+ fun ShoppingCartScreen(viewModel: CartViewModel) {
+ // Optimized: 1 LaunchedEffect node launches concurrent jobs
+ LaunchedEffect(Unit) {
+ launch { viewModel.loadCart() }
+ launch { viewModel.loadPaymentMethods() }
+ launch { viewModel.loadDeliveryAddresses() }
+ }
+
+ CartContent()
+ }
+ ```
+
+
+
+### 9. Effect execution ordering and teardown costs
+
+Understanding the execution lifecycle and performance costs of Compose effects
+is critical when choosing between them:
+
+#### A. Execution order relative to frame rendering
+
+Compose executes effects in a strictly defined order across frame phases:
+
+1. **`DisposableEffect`**: Executes first, immediately after composition changes are applied, in the exact order declared in the composable.
+2. **`SideEffect`** : Executes second, after all `DisposableEffect`s have run, before layout and drawing occur.
+3. **Layout \& Draw**: The UI hierarchy measures, places, and draws pixels onto the screen.
+4. **`LaunchedEffect`** : Executes **after Draw at the end of the frame**, scheduled on the coroutine dispatcher.
+
+> **Warning:**
+> If your code implicitly relies on `LaunchedEffect` running after the frame has
+> rendered (e.g., waiting for layout bounds to be drawn), migrating to
+> `SideEffect` will execute *before* Layout and Draw. Always verify execution
+> order dependencies when refactoring effects.
+
+#### B. Effect allocation and teardown costs
+
+Effects have substantially different overheads:
+
+- **`SideEffect`**: Cheapest effect; executes synchronously on every successful composition pass without lifecycle registration or coroutine allocation.
+- **`DisposableEffect`** : Moderate overhead; registers callbacks and runs `onDispose` synchronously upon key change or exit from composition.
+- **`LaunchedEffect`**: Highest overhead; requires allocating a coroutine launcher, launching a new coroutine job on the composition dispatcher, and canceling or tearing down the coroutine job when keys change or the composable exits.
+
+Because canceling a `LaunchedEffect` incurs coroutine job cancellation and
+teardown overhead, avoid cycling `LaunchedEffect` keys frequently during
+animations or fast scrolling. For synchronous keyed triggers without teardown,
+always prefer keyed `SideEffect(keys)` over `LaunchedEffect(keys)`.
+
+## 3. Side effects API selection guide
+
+Choose the correct Side Effect API based on the execution context and lifecycle
+requirements:
+
+| Use Case | Recommended API | Example |
+|---|---|---|
+| **Coroutine tied to Composable lifecycle** | `LaunchedEffect(keys)` | Triggering network calls or animations when an ID changes |
+| **Synchronous cleanup on exit** | `DisposableEffect(keys)` | Registering and unregistering lifecycle listeners or observers |
+| **Launch coroutine from UI callback** | `rememberCoroutineScope()` | Triggering a scroll animation on button click or swipe |
+| **Convert Compose State into Flow** | `snapshotFlow { state }` | Debouncing search queries derived from text field state |
+| **Synchronous keyed triggers** | `SideEffect` or `SideEffect(keys)` | Updating external imperatively managed views or non-Compose state |
+| **Viewport visibility or impression tracking** | `Modifier.onVisibilityChanged` | Sending analytics or : logging impressions when an item enters the viewport \| |
\ No newline at end of file
diff --git a/jetpack-compose/jetpack-compose-performance/references/graphics-performance.md b/jetpack-compose/jetpack-compose-performance/references/graphics-performance.md
new file mode 100644
index 0000000..04bac6f
--- /dev/null
+++ b/jetpack-compose/jetpack-compose-performance/references/graphics-performance.md
@@ -0,0 +1,221 @@
+## 1. Custom drawing (`Canvas` versus `drawWithCache`)
+
+- **Avoid Nested Canvas Nodes** : Avoid using the `Canvas` composable solely to add decorative drawings to an existing layout. Instead, use `Modifier.drawBehind` or `Modifier.drawWithCache` on the existing Composable to avoid creating an extra node in the layout tree.
+- **Cache Allocations with `drawWithCache`** : If your drawing logic allocates objects that depend on the size of the drawing area (like `Path`, `Brush`, `Shader`, or `Paint`), use **`Modifier.drawWithCache`** to cache allocated objects and avoid GC pressure during the draw phase.
+
+### A. Caching `Path` allocations
+
+- **Bad (Allocates a new Path on every single draw frame or animation tick)**:
+
+
+ ```kotlin
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .drawBehind {
+ val path = Path().apply {
+ moveTo(0f, 0f)
+ lineTo(size.width, size.height)
+ }
+ drawPath(path, Color.Red)
+ }
+ )
+ ```
+
+
+
+- **Optimized (Caches the Path, only recreates it if the Box size changes)**:
+
+
+ ```kotlin
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .drawWithCache {
+ val path = Path().apply {
+ moveTo(0f, 0f)
+ lineTo(size.width, size.height)
+ }
+ onDrawBehind {
+ drawPath(path, Color.Red)
+ }
+ }
+ )
+ ```
+
+
+
+## 2. Scope fast-changing uniforms inside `drawWithCache`
+
+- **Remember the `RuntimeShader`** : Always `remember` the `RuntimeShader` (or lazily initialize it once at top-level / process scope) outside of `drawWithCache`. Although compiled shaders are cached internally by the runtime, parsing the AGSL shader source string incurs significant parsing overhead and should never be repeated inside `drawWithCache` size-change passes.
+- **Remember the `ShaderBrush`** : `ShaderBrush` is a lightweight wrapper holding a reference to the shader. Remember it keyed on the shader: `remember(shader) { ShaderBrush(shader) }`.
+- **Update layout- and size-dependent uniforms** (like viewport size or resolution using `size.width, size.height`) in the `drawWithCache` initialization block, as size changes only during layout passes.
+- **Move dynamic, per-frame uniform updates** (such as updating time using `shader.setFloatUniform(...)`) inside the `onDrawBehind` or `onDrawWithContent` block without reallocating the shader or brush:
+
+
+```kotlin
+// RuntimeShader parsed once and cached in Composable scope
+val shader = remember { RuntimeShader(SHADER_SRC) }
+val brush = remember(shader) { ShaderBrush(shader) }
+
+Box(
+ modifier = modifier
+ .fillMaxSize()
+ .drawWithCache {
+ // Layout/size-dependent uniforms updated in cache block (on resize)
+ shader.setFloatUniform("u_resolution", size.width, size.height)
+
+ onDrawBehind {
+ // Per-frame uniforms updated in draw block without reallocating
+ shader.setFloatUniform("u_time", timeState.value)
+ drawRect(brush)
+ }
+ }
+)
+```
+
+
+
+## 3. Graphics `Path` performance and object reuse
+
+- **Prefer `Path.rewind()` over `Path.reset()` in Hot Draw Loops** :
+ - **`rewind()`** : Clears lines and curves but **retains internal buffers
+ and allocated capacity** in memory. Use `rewind()` when rebuilding a path on every frame with stable complexity (such as waveform monitors or custom charts) to avoid buffer reallocations.
+ - **`reset()`** : Clears the path and **releases internal buffers** . Use `reset()` only when the path will not be reused soon or when its complexity is shrinking sharply.
+
+## 4. Value classes, primitive collections, and packing
+
+Avoid allocating short-lived objects (such as `Pair`, `Pair`, `Point`, coordinate pairs, or dimensions) inside loops, animation
+callbacks, measure blocks, or draw methods.
+
+- **Avoid Generic Tuples (`Pair`, `Triple`)** : Allocating `Pair(row, col)` or `Pair(x, y)` in hot loops creates continuous heap allocations and GC pressure (e.g. `HashSet>` allocates the `Pair` object, boxes both primitive `Int`s, and creates a `HashMap.Node` entry --- totaling 3+ heap allocations per element).
+- **Standard Generic Sets Box Primitives** : `HashSet` or `HashSet` boxes every primitive to `java.lang.Long`/`java.lang.Integer` and allocates a `HashMap.Node`. For true zero-allocation collections, use **`androidx.collection.MutableLongSet`** or **`MutableIntSet`** from `androidx.collection:collection`.
+- **Avoid Iterator Allocations in Loops** : `for (item in items)` allocates an `Iterator` object when `items` is a standard `List`. Use indexed loops (`for
+ (i in items.indices)`) or `items.fastForEach { ... }` from `androidx.compose.ui.util`.
+- **Value Class Boxing Traps** : Kotlin `@JvmInline value class` and primitive types box into heap objects when used as generic type arguments (e.g. `Set`), when declared as nullable (`Point?`), or when cast to `Any`/interface types.
+- **Use Built-in Compose Packing Utilities** : Prefer the built-in primitive packing functions in `androidx.compose.ui.util` (`packInts`, `unpackInt1`, `unpackInt2`, `packFloats`, `unpackFloat1`, `unpackFloat2`) or Compose value classes (`IntOffset`, `Offset`, `IntSize`, `Size`) instead of custom bitwise operators.
+
+### A. Primitive packing with `MutableLongSet` (grid or layout calculations)
+
+- **Bad (Allocates Pair, boxed Ints, iterator, and HashMap.Node per
+ element)**:
+
+
+ ```kotlin
+ val occupied = HashSet>()
+ for (item in items) {
+ occupied.add(Pair(item.row, item.col))
+ }
+ ```
+
+
+
+- **Optimized (0 heap allocations using MutableLongSet, packInts, and
+ fastForEach)**:
+
+
+ ```kotlin
+ // import androidx.collection.MutableLongSet
+ // import androidx.compose.ui.util.fastForEach
+ // import androidx.compose.ui.util.packInts
+
+ val occupied = MutableLongSet()
+ items.fastForEach { item ->
+ occupied.add(packInts(item.row, item.col))
+ }
+ ```
+
+
+
+### B. Value class for coordinates
+
+- **Bad (Creates heap Point objects on every iteration)**:
+
+
+ ```kotlin
+ class Point(val x: Float, val y: Float)
+ var acc = Point(0f, 0f)
+ for (i in 0 until 1000) {
+ acc = Point(acc.x + i, acc.y - i)
+ }
+ ```
+
+
+
+- **Optimized (0 heap allocations, packs 2 floats into a Long primitive value
+ class)**:
+
+
+ ```kotlin
+ // import androidx.compose.ui.util.packFloats
+ // import androidx.compose.ui.util.unpackFloat1
+ // import androidx.compose.ui.util.unpackFloat2
+
+ @JvmInline
+ value class Point private constructor(val packedValue: Long) {
+ constructor(x: Float, y: Float) : this(packFloats(x, y))
+
+ val x: Float get() = unpackFloat1(packedValue)
+ val y: Float get() = unpackFloat2(packedValue)
+ }
+
+ var acc = Point(0f, 0f)
+ for (i in 0 until 1000) {
+ acc = Point(acc.x + i, acc.y - i)
+ }
+ // Alternatively, use Compose's built-in Offset class, which implements
+ // this exact @JvmInline Long packing pattern under the hood.
+ ```
+
+
+
+## 5. Image loading and decoding (`AsyncImage` versus `painterResource`)
+
+Avoid using `Image(painter = painterResource(resId))` to render raster photos or
+large bitmap assets---especially inside scrolling lists (`LazyColumn`, `LazyRow`,
+`LazyGrid`). Reserve `painterResource` strictly for vector drawables (`xml`) and
+small UI icons.
+
+- **Synchronous Main-Thread Decoding** : `painterResource` decodes bitmaps synchronously on the main thread during composition (`Compose:recompose` -\> `ImageDecoder`), including during lazy list prefetch (`lazy:prefetch:compose`). Even a small compressed JPEG in the APK (e.g., 159 KB) can decompress into a large `2760x1840` bitmap, blocking the main thread and causing dropped frames.
+- **No Target-Size Downsampling** : `painterResource` has no awareness of the layout size (such as `Modifier.size(160.dp)`) and decodes the image at full original resolution.
+- **Prefer Async image loading like Coil's `AsyncImage` (or Asynchronous
+ Downsampling)** :
+ - **Offloads Decoding** : `AsyncImage` decodes images asynchronously on a background thread, keeping the main thread free during scroll and prefetch.
+ - **Automatic Downsampling (`ImageDecoder.setTargetSize`)** : `AsyncImage` measures the target layout bounds and configures `ImageDecoder.setTargetSize()` automatically so only the pixels actually displayed are decoded and uploaded to the GPU.
+ - **Ship Appropriately Sized Assets**: If bundling static thumbnails in the APK, ship pre-scaled thumbnail assets matching the display bucket rather than full-resolution camera photos.
+
+### A. Loading bitmap resources or photos in a fixed-size container
+
+- **Bad (Synchronously decodes full `2760x1840` bitmap on the main thread and
+ uploads the full texture to the GPU for a `160.dp` box)**:
+
+
+ ```kotlin
+ Image(
+ painter = painterResource(id = R.drawable.donut),
+ contentScale = ContentScale.Fit,
+ modifier = Modifier.size(160.dp),
+ contentDescription = stringResource(id = R.string.attached_image),
+ )
+ ```
+
+
+
+- **Optimized (Decodes off the main thread, automatically downsamples to the
+ `160.dp` target dimensions via `ImageDecoder.setTargetSize`, and caches the
+ result)**:
+
+
+ ```kotlin
+ // import coil3.compose.AsyncImage
+
+ AsyncImage(
+ model = R.drawable.donut,
+ contentScale = ContentScale.Fit,
+ modifier = Modifier.size(160.dp),
+ contentDescription = stringResource(id = R.string.attached_image),
+ )
+ ```
+
+
\ No newline at end of file
diff --git a/jetpack-compose/jetpack-compose-performance/references/layouts.md b/jetpack-compose/jetpack-compose-performance/references/layouts.md
new file mode 100644
index 0000000..72a4272
--- /dev/null
+++ b/jetpack-compose/jetpack-compose-performance/references/layouts.md
@@ -0,0 +1,176 @@
+## 1. Layout coordinates, rects, and visibility tracking
+
+`Modifier.onGloballyPositioned` executes on every layout pass across the entire
+Compose hierarchy whenever any node is placed or moved, making it a frequent
+source of frame drops, unnecessary callbacks, and layout feedback loops. Use
+targeted modern APIs instead:
+
+### 1. Viewport and dwell tracking: use `Modifier.onVisibilityChanged`
+
+Avoid calculating viewport intersections manually inside `onGloballyPositioned`.
+Use **`Modifier.onVisibilityChanged`** (with `minFractionVisible` and optional
+`minDurationMs` dwell thresholds) for:
+
+- **Impression Logging**: Ensures analytics fire only when an item actually enters the viewport and stays visible for a minimum duration (preventing premature logging on pre-fetched lazy items).
+- **Deferring Lookahead Shared Transitions** : Attaches `Modifier.sharedBounds` or `Modifier.sharedElement` only when an item enters the viewport, eliminating Lookahead passes on offscreen items.
+
+
+```kotlin
+// Optimized: Viewport entry with dwell threshold
+Box(
+ modifier = Modifier.onVisibilityChanged(
+ minFractionVisible = 0.5f,
+ minDurationMs = 500L,
+ ) { isVisible ->
+ if (isVisible) {
+ analytics.logImpression(itemId)
+ }
+ }
+)
+```
+
+
+
+### 2. Local bounds and placement: prefer `onPlaced` or `onLayoutRectChanged`
+
+- **Dimensions Only (`IntSize`)** : If you only need width and height, use `Modifier.onSizeChanged`. It executes only when the node's dimensions change, ignoring position shifts.
+- **Local Coordinates after Placement** : Use `Modifier.onPlaced` or `Modifier.onLayoutRectChanged` when you need local coordinates relative to the parent without subscribing to global window or screen coordinate updates across the entire tree.
+
+### 3. On-demand coordinate queries: avoid `MutableState` position tracking
+
+Avoid continuously listening to coordinate callbacks (`onGloballyPositioned` or
+`onPlaced`) solely to cache layout coordinates into a `MutableState` for later
+use when an event occurs (such as showing a tooltip, menu, or handling a
+gesture). Storing coordinates in state causes redundant callback executions on
+every layout or scroll pass and risks recomposition trashing or backwards
+writes.
+
+#### A. In Custom Modifiers (`Modifier.Node`)
+
+`requireLayoutCoordinates()` is an API on `Modifier.Node` (specifically
+`DelegatableNode`), designed to pull coordinates on demand:
+
+- **Query inside Event Handlers** : In custom modifier nodes (such as `PointerInputModifierNode` or `DrawModifierNode`), call `requireLayoutCoordinates()` directly inside gesture, touch, or draw callbacks at the moment the event occurs.
+- **Eliminates continuous listeners** : You do not need to implement `LayoutAwareModifierNode` or listen to `onGloballyPositioned` continuously just to have coordinates available when an interaction happens.
+
+
+```kotlin
+// In a custom PointerInputModifierNode:
+class InteractiveTooltipNode : Modifier.Node(), PointerInputModifierNode {
+ override fun onPointerEvent(
+ pointerEvent: PointerEvent,
+ pass: PointerEventPass,
+ bounds: IntSize,
+ ) {
+ // Query coordinates on-demand at the exact moment of the interaction
+ if (isAttached) {
+ val coordinates = requireLayoutCoordinates()
+ val bounds = coordinates.boundsInRoot()
+ showTooltip(bounds)
+ }
+ }
+}
+```
+
+
+
+#### B. In high-level composable code (versus `Modifier.clickable`)
+
+`requireLayoutCoordinates()` is strictly a `Modifier.Node` API---it is **not**
+available in high-level composables, and `Modifier.clickable` does not provide
+coordinates to its `onClick: () -> Unit` lambda:
+
+- **Standard clicks (`Modifier.clickable`)** : If your click handler does not require exact screen coordinates, use `Modifier.clickable` directly without attaching any coordinate listeners.
+- **Position-aware gestures (`Modifier.pointerInput`)** : If you need the exact tap position in composable code (for example, to anchor a drop-down menu at the touch point), use `Modifier.pointerInput` with `detectTapGestures` to receive the tap `Offset` directly on the event, rather than storing `onGloballyPositioned` bounds in `MutableState`:
+
+
+```kotlin
+// Optimized: Captures touch offset on event without onGloballyPositioned
+Box(
+ modifier = Modifier.pointerInput(Unit) {
+ detectTapGestures { offset ->
+ showMenuAt(offset)
+ }
+ }
+)
+```
+
+
+
+### 4. Prohibit layout feedback loops (back-writing during Layout)
+
+Never write to `MutableState` inside `onPlaced`, `onSizeChanged`, or
+`onGloballyPositioned` if reading that state invalidates the layout, size, or
+placement of the node being measured.
+
+Writing to state in the Layout phase that is read in Composition creates a
+**backwards write** , which triggers the **first-frame correctness problem**: the
+first frame is rendered with invalid, default, or unsettled dimensions (such as
+zero size or an incorrect offset), followed by a second frame recomposition that
+causes visible visual popping, layout flashing, and dropped frames. If the
+layout sizing continuously writes back a new value to state, it results in an
+infinite recomposition loop.
+
+Similarly, within the Layout phase itself, state changes must flow forward:
+updating state during measurement and reading it in placement is acceptable, but
+writing to state in placement that is read during measurement causes a
+continuous remeasure loop.
+
+If sizes or coordinates are needed for custom drawing or placement, read them
+directly in the Draw or Layout phase using `Modifier.drawWithCache` or
+`Modifier.layout` instead of passing them back to Composition. For more
+information on phase flow and eliminating recomposition loops, see the
+[Backwards Write documentation](https://developer.android.com/develop/ui/compose/performance/backwards-write).
+
+## 2. Phase deferral for layout offsets and constraints
+
+Defer reading animated or scroll-driven state values to the **Layout** phase by
+wrapping reads in lambda-based layout modifiers. This bypasses the Composition
+phase entirely on state changes:
+
+- **Bad (Recomposes on every offset change)**:
+
+
+ ```kotlin
+ val offset = scrollState.value
+ Box(modifier = Modifier.offset(x = offset.dp, y = 0.dp))
+ ```
+
+
+
+- **Optimized (Skips Composition, evaluates in Layout phase)**:
+
+
+ ```kotlin
+ Box(modifier = Modifier.offset { IntOffset(scrollState.value, 0) })
+ ```
+
+
+
+## 3. Minimizing layout tree depth and wrapper nodes
+
+Every layout node in Compose incurs measurement, placement, and snapshot
+tracking costs. Minimize layout hierarchy depth:
+
+- **Avoid Decorative Wrapper Layouts** : Do not wrap a composable in an extra `Box` solely to apply a background, border, padding, or clickable modifier. Attach modifiers directly to the child composable.
+- **Avoid Canvas Nodes for Decorative Backgrounds** : Avoid using the `Canvas` composable solely to add decorative drawings behind content. Use `Modifier.drawBehind` or `Modifier.drawWithCache` on the existing layout node to avoid adding an extra node to the layout tree.
+
+## 4. Subcomposition overhead (`SubcomposeLayout` and `BoxWithConstraints`)
+
+`SubcomposeLayout` (and composables built on top of it, such as
+`BoxWithConstraints`) defers composition until the measurement pass, breaking
+Compose's standard single-pass layout pipeline into multiple subcomposition
+passes:
+
+- **Avoid `BoxWithConstraints` for Simple Responsiveness** : Do not use `BoxWithConstraints` when standard modifiers (`Modifier.fillMaxWidth()`, `Modifier.weight()`, or `Modifier.aspectRatio()`) or flow layouts can achieve the same visual layout.
+- **Avoid `BoxWithConstraints` inside Lazy Items** : Using `BoxWithConstraints` inside `LazyColumn` or `LazyRow` items introduces subcomposition overhead for every recycled and pre-fetched item, increasing scroll jank.
+- **Prefer `Modifier.layout` or `LayoutModifierNode`** : When custom measurement or placement rules depend on incoming constraints, prefer using `Modifier.layout { measurable, constraints -> ... }` or implementing `LayoutModifierNode` rather than spawning a `SubcomposeLayout` or `BoxWithConstraints`.
+
+## 5. Intrinsic measurements
+
+Intrinsic measurements (`IntrinsicSize.Min`, `IntrinsicSize.Max`) query children
+for their preferred size before the final measurement pass, effectively running
+a two-pass layout:
+
+- Use intrinsic measurements judiciously and only when siblings must match dimensions (for example, a `Divider` matching the height of adjacent text).
+- Avoid chaining nested intrinsic measurements across deep hierarchies.
\ No newline at end of file
diff --git a/jetpack-compose/jetpack-compose-performance/references/lazy-layouts.md b/jetpack-compose/jetpack-compose-performance/references/lazy-layouts.md
new file mode 100644
index 0000000..50e8462
--- /dev/null
+++ b/jetpack-compose/jetpack-compose-performance/references/lazy-layouts.md
@@ -0,0 +1,366 @@
+## 1. Lazy layout directives
+
+Implement strict item keying, content-type mapping, and index-lookup
+optimizations to prevent unnecessary item recompositions, layout passes, and
+dropped frames during scrolling.
+> **Scope Guard (Virtualized Containers Only)** : The directives in this section
+> apply **strictly** to lazy, virtualized layouts (`LazyColumn`, `LazyRow`,
+> `LazyVerticalGrid`, `LazyHorizontalGrid`, `HorizontalPager`, `VerticalPager`).
+> Standard non-virtualized containers (`Column`, `Row`, `Box`) iterating over
+> collections using standard Kotlin `for` or `forEach` loops are **not** lazy
+> layouts. Do **not** flag standard `Column`/`Row` loops for missing `key` or
+> `contentType` parameters.
+
+### 1. Require keys and content types
+
+Always provide a stable `key` and a `contentType` for all items.
+
+- **`key`**: Enables Compose to maintain item state across reorderings and list updates without recreating item compositions from scratch.
+- **`contentType`**: Enables Compose to reuse item compositions and node slots when recycling items of the same visual type, significantly reducing layout inflation overhead during fast scrolling.
+
+
+```kotlin
+LazyColumn {
+ items(
+ items = itemList,
+ key = { item -> item.id },
+ contentType = { item -> item.type }
+ ) { item ->
+ ItemRow(item = item)
+ }
+}
+```
+
+
+
+- **Ensure Keys are Bundle-Saveable** : Keys must be saveable in an Android `Bundle` because `LazyColumn` uses `rememberSaveable` internally to persist scroll states and item state across configuration changes and process death.
+ - **Do NOT** use custom data classes as keys unless they implement `Parcelable` or `Serializable`.
+ - **Do** use unique primitive types (like `Int` or `Long`). If needed, combine fields into a unique string: `key = { "${it.id}_${it.timestamp}"
+ }`. Prefer `Int` and `Long` over `String` for minimal memory and hashing overhead.
+- **Avoid Duplicate and Unstable Keys** : Ensure keys are globally unique and deterministic. Never use `hashCode()` or list indexes as keys, as they change when items are reordered, inserted, or filtered.
+
+### 2. Avoid in-item collection searches and observable state queries
+
+Do not perform collection searches, linear index scans, or observable state
+queries inside item layout blocks or outer list item scopes:
+
+- **Linear Index Scans (`indexOf`, `find`)** : Searching the list for an item's
+ index inside the layout block (for example, `list.indexOf(item)`) turns a
+ linear layout pass into an `O(N²)` operation and causes
+ `IndexOutOfBoundsException` during asynchronous mutations. Use
+ `itemsIndexed` instead.
+
+ - **Bad (`O(N²)` linear scan per item)**:
+
+
+ ```kotlin
+ LazyColumn {
+ items(items = itemList, key = { it.id }) { item ->
+ // O(N) lookup inside each item!
+ val index = itemList.indexOf(item)
+ ItemRow(index = index, item = item)
+ }
+ }
+ ```
+
+
+
+ - **Optimized (Direct index passing with `itemsIndexed`)**:
+
+
+ ```kotlin
+ LazyColumn {
+ itemsIndexed(
+ items = itemList,
+ key = { _, item -> item.id },
+ ) { index, item ->
+ ItemRow(index = index, item = item)
+ }
+ }
+ ```
+
+
+
+- **Observable Selection \& State Queries** : Never use `mutableStateListOf`
+ (`SnapshotStateList`) for tracking selected IDs where `.contains()` and
+ `.remove()` perform `O(N)` linear scans; use an immutable `Set` in
+ `MutableState` (`mutableStateOf(emptySet())`) or
+ `mutableStateSetOf` (`SnapshotStateSet` in Compose 1.8+) for `O(1)`
+ lookups. Also, reading an observable collection state (like
+ `selectedIds.contains(item.id)`) inside the outer `items(...) { ... }`
+ block subscribes the entire list or section to changes in that collection.
+ Toggling a single item invalidates the parent scope and forces a full
+ recomposition of all visible items. Defer the state read to the leaf item
+ composable by passing a lambda provider (`isSelected: () -> Boolean`).
+
+ - **Bad (`mutableStateListOf` `O(N)` lookups and reading state in outer
+ `items` scope triggers full list or grid recomposition on toggle)**:
+
+
+ ```kotlin
+ @Composable
+ fun TopicGrid(sections: List) {
+ val selectedTopicIds = remember { mutableStateListOf() }
+ LazyColumn {
+ items(sections, key = { it.id }) { section ->
+ Column {
+ Text(text = section.title)
+ section.topics.forEach { topic ->
+ // Avoid: O(N) List.contains() read in items() scope
+ // invalidates the whole section!
+ val isSelected = selectedTopicIds.contains(topic.id)
+ TopicChip(
+ topic = topic,
+ isSelected = isSelected,
+ onToggle = {
+ if (isSelected) {
+ selectedTopicIds.remove(topic.id)
+ } else {
+ selectedTopicIds.add(topic.id)
+ }
+ }
+ )
+ }
+ }
+ }
+ }
+ }
+ ```
+
+
+
+ - **Optimized (Use `Set` (`mutableStateOf(emptySet())` or
+ `mutableStateSetOf()`) for `O(1)` lookup and pass lambda to leaf
+ composable to isolate recomposition to the clicked item)**:
+
+
+ ```kotlin
+ @Composable
+ fun TopicGrid(sections: List) {
+ var selectedTopicIds by remember {
+ mutableStateOf(emptySet())
+ }
+ LazyColumn {
+ items(
+ items = sections,
+ key = { it.id },
+ contentType = { "section" },
+ ) { section ->
+ Column {
+ Text(text = section.title)
+ section.topics.forEach { topic ->
+ TopicChip(
+ topic = topic,
+ // O(1) Set read deferred to TopicChip's scope
+ isSelectedProvider = {
+ selectedTopicIds.contains(topic.id)
+ },
+ onToggle = {
+ val selected =
+ selectedTopicIds.contains(topic.id)
+ selectedTopicIds = if (selected) {
+ selectedTopicIds - topic.id
+ } else {
+ selectedTopicIds + topic.id
+ }
+ }
+ )
+ }
+ }
+ }
+ }
+ }
+
+ @Composable
+ fun TopicChip(
+ topic: Topic,
+ isSelectedProvider: () -> Boolean,
+ onToggle: () -> Unit
+ ) {
+ // Read occurs inside leaf scope
+ val isSelected = isSelectedProvider()
+ val status = if (isSelected) "Selected" else "Unselected"
+ Box(modifier = Modifier.clickable { onToggle() }) {
+ Text(text = "${topic.title} ($status)")
+ }
+ }
+ ```
+
+
+
+### 3. Avoid `derivedStateOf` for direct collection sizes and counts
+
+Do not wrap direct collection property accesses (such as `list.size` or
+`list.isEmpty()`) in `derivedStateOf`. Accessing a property on an already
+available list or snapshot collection is an `O(1)` read. Wrapping it in
+`derivedStateOf` allocates a `DerivedState` wrapper object, snapshot record
+tracking structures, and observer subscriptions that cost far more than reading
+the integer property directly.
+
+### 4. Avoid lazy layouts for small or bounded collections
+
+**Tip:** For small static or bounded dynamic collections (e.g. 1 to 10 items
+such as tags, buttons, form rows, or chips), prefer using a standard `Row` or
+`Column` with a `for` or `forEach` loop instead of `LazyRow` or `LazyColumn`.
+For small collections, composing all items eagerly reduces layout, prefetching,
+and measurement overhead compared to lazy list virtualization machinery.
+Benchmark this change before applying broad changes to code.
+
+- **Bad (Lazy virtualization overhead for tiny static list)**:
+
+
+ ```kotlin
+ LazyRow {
+ items(items = listOf("Work", "Personal", "Family")) { tag ->
+ FilterChip(tag = tag)
+ }
+ }
+ ```
+
+
+
+- **Optimized (Standard Row with forEach)**:
+
+
+ ```kotlin
+ Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
+ listOf("Work", "Personal", "Family").forEach { tag ->
+ FilterChip(tag = tag)
+ }
+ }
+ ```
+
+
+
+### 5. Hoist shared `Flow` and state collections out of lazy items
+
+**Critical:** Never collect a shared `Flow`, `StateFlow`, or observable timer
+directly inside list item composables using `collectAsState()` or
+`collectAsStateWithLifecycle()`.
+
+When a shared flow is collected inside item composables, every visible item in
+the `LazyColumn` or `LazyRow` creates an independent subscription to that same
+flow. When the flow emits an update (such as an idle timer tick or periodic
+sync), it invalidates and recomposes all visible items simultaneously, creating
+massive CPU spikes and dropped frames during scrolling.
+
+Instead, **hoist the flow collection once** to the parent container above the
+`LazyColumn`, derive the required state, and pass stable or primitive values
+down to the individual item composables.
+
+- **Bad (Each visible item creates an independent subscription to the shared
+ timer)**:
+
+
+ ```kotlin
+ @Composable
+ fun FeedItem(item: Post) {
+ // Avoid: Multiplied subscriptions across all visible items!
+ val secondsSinceLastScroll by MainFeedIdleTracker
+ .secondsSinceLastScrollFlow
+ .collectAsState(0L)
+ val isDwellTriggered = secondsSinceLastScroll >= 5L
+ PostActions(isDwellTriggered = isDwellTriggered)
+ }
+
+ @Composable
+ fun FeedList(posts: List) {
+ LazyColumn {
+ items(posts, key = { it.id }) { post ->
+ FeedItem(post)
+ }
+ }
+ }
+ ```
+
+
+
+- **Optimized (Hoist subscription once above LazyColumn, pass stable value
+ down)**:
+
+
+ ```kotlin
+ @Composable
+ fun FeedList(posts: List) {
+ // Hoist once: Parent owns the single subscription
+ val secondsSinceLastScroll by MainFeedIdleTracker
+ .secondsSinceLastScrollFlow
+ .collectAsStateWithLifecycle(0L)
+ val isDwellTriggered = secondsSinceLastScroll >= 5L
+
+ LazyColumn {
+ items(posts, key = { it.id }) { post ->
+ FeedItem(post = post, isDwellTriggered = isDwellTriggered)
+ }
+ }
+ }
+
+ @Composable
+ fun FeedItem(post: Post, isDwellTriggered: Boolean) {
+ PostActions(isDwellTriggered = isDwellTriggered)
+ }
+ ```
+
+
+
+### 6. Propagate `CompositionLocal`s down instead of reading in lazy items
+
+Avoid querying `CompositionLocal`s (such as `LocalContext.current` or
+`LocalDensity.current`) directly inside individual lazy item composables.
+
+Querying a `CompositionLocal` inside every item adds repetitive map lookup
+overhead during item composition and subscribes each individual item to the
+CompositionLocal's updates. Instead, read the CompositionLocal once in the
+parent screen or list container and pass the resolved values down as
+parameters.
+
+- **Bad (Querying scopes and context per item)**:
+
+
+ ```kotlin
+ @Composable
+ fun SnackItem(snack: Snack) {
+ val sharedScope = LocalSharedTransitionScope.current
+ ?: error("No scope")
+ val context = LocalContext.current
+ // ...
+ }
+ ```
+
+
+
+- **Optimized (Pass resolved dependencies down as parameters)**:
+
+
+ ```kotlin
+ @Composable
+ fun SnackList(
+ snacks: List,
+ sharedScope: SharedTransitionScope,
+ ) {
+ val context = LocalContext.current
+ LazyColumn {
+ items(snacks, key = { it.id }, contentType = { "snack" }) { snack ->
+ SnackItem(
+ snack = snack,
+ sharedScope = sharedScope,
+ context = context,
+ )
+ }
+ }
+ }
+ ```
+
+
+
+### 7. Defer shared transitions behind visibility indicators
+
+Attaching `Modifier.sharedBounds` or `Modifier.sharedElement` to every item in
+a lazy layout adds significant modifier allocation, shared state tracking, and
+snapshot record instantiation overhead---even for offscreen or pre-fetched items
+that are not visible.
+
+In fast-scrolling or large lazy lists, gate shared transitions behind
+visibility indicators (such as `Modifier.onVisibilityChanged` or viewport
+visibility checks) so only rendered, visible items instantiate transition
+scopes and coordinate tracking.
\ No newline at end of file
diff --git a/jetpack-compose/jetpack-compose-performance/references/state-and-stability.md b/jetpack-compose/jetpack-compose-performance/references/state-and-stability.md
new file mode 100644
index 0000000..bc63fdb
--- /dev/null
+++ b/jetpack-compose/jetpack-compose-performance/references/state-and-stability.md
@@ -0,0 +1,665 @@
+## 1. Phase deferral and lambdas (Composition versus Layout versus Draw)
+
+Defer reading animated, frequently changing, or scroll-driven state values until
+the Layout or Draw phase by wrapping reads in lambdas. This bypasses the
+expensive Composition phase entirely and avoids unnecessary recompositions.
+
+### A. Modifier offsets
+
+- **Bad (Recomposes on every pixel change)**:
+
+
+ ```kotlin
+ val offset = scrollState.value
+ Box(modifier = Modifier.offset(x = offset.dp, y = 0.dp))
+ ```
+
+
+
+- **Optimized (Skips Composition, goes straight to Layout)**:
+
+
+ ```kotlin
+ Box(modifier = Modifier.offset { IntOffset(scrollState.value, 0) })
+ ```
+
+
+
+### B. Alpha and rotation animations
+
+- **Bad (Recomposes on every animation frame)**:
+
+
+ ```kotlin
+ val alpha by animateFloatAsState(targetValue)
+ Box(modifier = Modifier.alpha(alpha))
+ ```
+
+
+
+- **Optimized (Skips Composition and Layout, goes straight to Draw)**:
+
+
+ ```kotlin
+ val alpha by animateFloatAsState(targetValue)
+ Box(modifier = Modifier.graphicsLayer { this.alpha = alpha })
+ ```
+
+
+
+### C. Custom composable parameters
+
+Passing a lambda provider (`valueProvider: () -> Float`) is beneficial only when
+the value is read in Layout or Draw. If read in Composition, pass the raw value
+directly to avoid lambda allocation overhead.
+
+- **Read in Composition (Pass value directly)**:
+
+
+ ```kotlin
+ @Composable
+ fun TitleText(text: String) {
+ Text(text = text)
+ }
+ ```
+
+
+
+- **Read in Layout or Draw (Pass lambda provider)**:
+
+
+ ```kotlin
+ @Composable
+ fun FadingBox(alphaProvider: () -> Float) {
+ Box(Modifier.graphicsLayer { alpha = alphaProvider() })
+ }
+ ```
+
+
+
+## 2. Preventing backwards writes across frame phases
+
+A **backwards write** occurs whenever a Compose `State` is modified in a later
+frame phase (or in a downstream composable scope within the same composition
+pass) than where it was read.
+
+Compose executes frames in three strictly forward-flowing phases:
+**Composition \> Layout \> Drawing**. Modifying state in a later phase (or after
+reading it) forces Compose to schedule an earlier phase to re-run, creating an
+unoptimized recomposition loop.
+
+### Consequences
+
+- **Extra Frames \& Dropped Frames**: Forces redundant composition passes across consecutive frames, wasting CPU/GPU resources and dropping frames.
+- **First-Frame Correctness Issues**: Initial frames render with invalid, default, or unsettled data (such as zero size or an incorrect offset), causing visible layout popping or flashing when the second frame renders.
+- **Infinite Recomposition Loops**: Occurs when layout sizing or draw logic continuously writes back to state that drives composition.
+
+### Core rules
+
+1. **Verify Read-Before-Write Order** : A backwards write **strictly requires a
+ state read to precede the state write** (`Read -> Write` in the same composition pass, or `Composition Read -> Layout or Draw Write` across frame phases). State writes belong in event callbacks (`onClick`), coroutines (`LaunchedEffect`), or side effects (`SideEffect`); layout callbacks (`onSizeChanged`, `onPlaced`) do not count as events.
+2. **Do Not Confuse `Write -> Read` with Backwards Writes** : Assigning to a `MutableState` at the top of a composable **before** any read of that state occurs (or writing in Composition and reading later in the `Canvas` Draw phase) is **not** a backwards write, because no read observer has recorded the current composition scope prior to the write (and `Composition Write ->
+ Draw Read` flows forward). Instead, classify `var x by remember {
+ mutableStateOf(...) }; x = getValue()` as **Redundant State Wrapping** (unnecessary `MutableState` and slot table allocation that is also fragile if someone later adds a read preceding the write) and replace it with a plain `val` or read directly in the target phase.
+3. **Never write to state in `onSizeChanged`, `onPlaced`, or `LayoutModifier`
+ if read in Composition** : Read sizes directly in Draw or Layout phase (`Modifier.drawWithCache`, `Modifier.layout`), hoist window-level sizing (`WindowWidthSizeClass`), or use `FlowRow`/`LazyVerticalGrid`.
+4. **Never mutate state after reading it in Composition** : Use `rememberUpdatedState` when you need to capture a changing value in composition without triggering recomposition.
+5. **Defer state reads to the latest possible phase**: Reading state in Layout or Draw skips Composition entirely when the state changes.
+
+### Common patterns: backwards writes versus event-driven updates
+
+#### A. Direct mutation in Composition (`Read -> Write` versus `Write -> Read`)
+
+- **Bad (`Read -> Write`: Mutating state after reading it triggers immediate
+ recomposition loop)**:
+
+
+ ```kotlin
+ @Composable
+ fun BadCounter() {
+ var count by remember { mutableIntStateOf(0) }
+ Text("Count: $count") // 1. State read in Composition
+ Button(onClick = {}) {
+ // 2. State write in Composition AFTER read (Backwards write!)
+ countability.kt
+ ```
+
+
+
+- **Not a Backwards Write, but Redundant/Fragile (`Write -> Read` in
+ Composition)**:
+
+
+ ```kotlin
+ // NOT a backwards write because the write happens BEFORE any read in
+ // Composition (and before Canvas reads it in the Draw phase).
+ // However, wrapping a synchronous value in mutableStateOf is redundant
+ // and error-prone if a read is later added before the write.
+ @Composable
+ fun RedundantStateWrapper(viewModel: MyViewModel) {
+ var path by remember { mutableStateOf(Path()) }
+ // 1. Write occurs first (no prior read in scope)
+ path = viewModel.getPath()
+ Canvas(Modifier.fillMaxSize()) {
+ // 2. Read occurs later in Draw phase (Forward flow)
+ drawPath(path, Color.Red)
+ }
+ }
+
+ // Optimized: Remove redundant mutableStateOf wrapper and read in Draw
+ @Composable
+ fun OptimizedWrapper(viewModel: MyViewModel) {
+ Canvas(Modifier.fillMaxSize()) {
+ val path = viewModel.getPath()
+ drawPath(path, Color.Red)
+ }
+ }
+ ```
+
+
+
+#### B. Reading in Composition, writing in Layout (phase backwards write)
+
+- **Bad (Layout writes to state read in Composition, causing first-frame
+ popping)**:
+
+
+ ```kotlin
+ @Composable
+ fun BadLayout() {
+ var componentHeight by remember { mutableStateOf(0.dp) }
+
+ // Composition reads state before Layout measures it
+ if (componentHeight > 100.dp) {
+ Banner()
+ }
+
+ Box(
+ modifier = Modifier.onSizeChanged { size ->
+ // Backwards write: Layout -> Composition
+ componentHeight = size.height.dp
+ }
+ )
+ }
+ ```
+
+
+
+- **Optimized (Defer sizing to Layout phase or hoist to Window level)**:
+
+
+ ```kotlin
+ @Composable
+ fun OptimizedLayout(modifier: Modifier = Modifier) {
+ // Measurement and placement handled in Layout without recomposition
+ Box(
+ modifier = modifier.layout { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+ layout(placeable.width, placeable.height) {
+ placeable.placeRelative(0, 0)
+ }
+ }
+ )
+ }
+ ```
+
+
+
+For more information, see the [Backwards Write documentation](https://developer.android.com/develop/ui/compose/performance/backwards-write).
+
+## 3. State and snapshot optimizations
+
+### A. Optimize whole-collection updates with `neverEqualPolicy()`
+
+`mutableStateListOf` tracks every individual element mutation through the
+snapshot system. When fine-grained element tracking is not needed, back your
+collection with a standard `MutableList` wrapped in `mutableStateOf(list,
+neverEqualPolicy())` and trigger recomposition using a load-bearing assignment
+(`state.value = list`).
+
+
+```kotlin
+class ActiveQueueManager {
+ private val list = mutableListOf()
+ val items = mutableStateOf(list, neverEqualPolicy())
+
+ fun enqueue(item: Item) {
+ list.add(item)
+ items.value = list // Load-bearing assignment triggers recomposition
+ }
+}
+```
+
+
+
+### B. Use `SnapshotStateList.toList()` for repeated queries and iterations
+
+Iterating or querying a `SnapshotStateList` (or `SnapshotStateMap`) repeatedly
+inside a composable registers snapshot read observations on each accessed index
+or element.
+
+When performing repeated queries (such as checking `.contains()` in a loop),
+filtering, sorting, or multi-step processing on a snapshot collection, take an
+atomic snapshot using `.toList()` (or `.toSet()` / `.toMap()`) once at the start
+of the scope and perform operations on that read-only snapshot:
+
+- **Bad (Repeated contains queries inside loop register fine-grained
+ dependencies on every element)**:
+
+
+ ```kotlin
+ @Composable
+ fun ItemSelector(
+ items: List,
+ selectedIds: SnapshotStateList,
+ ) {
+ Column {
+ items.forEach { item ->
+ // Avoid: Repeatedly querying the snapshot list inside the loop
+ val isSelected = selectedIds.contains(item.id)
+ ItemRow(item = item, isSelected = isSelected)
+ }
+ }
+ }
+ ```
+
+
+
+- **Optimized (Take a read-only snapshot Set once for `O(1)` queries)**:
+
+
+ ```kotlin
+ @Composable
+ fun ItemSelector(
+ items: List,
+ selectedIds: SnapshotStateList,
+ ) {
+ // Convert to a local Set once per recomposition for fast O(1) lookups
+ val selectedSet = selectedIds.toSet()
+ Column {
+ items.forEach { item ->
+ val isSelected = selectedSet.contains(item.id)
+ ItemRow(item = item, isSelected = isSelected)
+ }
+ }
+ }
+ ```
+
+
+
+### C. Extract repeated state reads into local variables
+
+Reading Compose `State` properties (such as `transition.currentState`,
+`scrollState.value`, or `state.value`) multiple times in a function registers
+redundant snapshot lookups. Cache the state read into a local variable at the
+top of the block.
+
+- **Bad (Repeated snapshot state reads)**:
+
+
+ ```kotlin
+ val label = when (transition.currentState) {
+ State.Idle -> "Idle"
+ State>.Running - "Running: ${transition.currentState}"
+ > State.Finished - "Finished: ${tre}"
+ }StateAndStability.kt
+ ```
+
+
+
+- **Optimized (Single read cached in local variable)**:
+
+
+ ```kotlin
+ val currentState = transition.currentState
+ val label = when (currentState) {
+ State.Idle -> "Idle"
+ State>.Running - "Running: $currentState"
+ > State.Finished - "Fte"
+ }StateAndStability.kt
+ ```
+
+
+
+### D. Use `computedStateOf` versus `derivedStateOf` (Compose 1.13+)
+
+When deriving state from frequently changing inputs (such as checking
+`scrollState.value > 0` on every scroll tick), reading the raw state directly in
+composition invalidates the composable on every frame.
+
+In Compose 1.13+, **`computedStateOf`** is introduced alongside
+**`derivedStateOf`**:
+
+- **`computedStateOf`** : Computes its calculation on **every read** without caching the value.
+ - **First read** : Slightly faster than `derivedStateOf` because it avoids cache initialization and snapshot dependency bookkeeping.
+ - **Second read** : Slightly slower than `derivedStateOf` because it re-executes the calculation.
+ - **Read-after-write (invalidation)** : Significantly faster than `derivedStateOf` because it does not have the expensive cache invalidation path, cache write-back, and snapshot apply-listener dispatch.
+- **`derivedStateOf`** : Caches the result of its calculation and returns the cached value as long as dependency states have not changed.
+ - Has a slower invalidation path when its dependency states change frequently, due to cache clearing, re-computation, cache write-back, and advancing snapshots or triggering apply listeners.
+
+#### When to use which
+
+1. **Prefer `computedStateOf` (Compose 1.13+) or `remember { derivedStateOf {
+ ... } }` (Compose \< 1.13)** for simple condition checks, boolean flags, or
+ lightweight derivations driven by fast-changing states (such as scroll
+ offsets or slider positions). Check `gradle/libs.versions.toml` or
+ `build.gradle(.kts)` for the project's Compose version before using
+ `computedStateOf`:
+
+
+ ```kotlin
+ @Composable
+ fun ScrollToTopButton(scrollState: ScrollState) {
+ // Compose 1.13+: Invalidates composition only when > 0 changes,
+ // with minimal invalidation overhead compared to derivedStateOf.
+ // On Compose < 1.13, use remember { derivedStateOf { ... } }.
+ val showButton by remember { computedStateOf { scrollState.value > 0 } }
+
+ if (showButton) {
+ FloatingActionButton(onClick = { /* ... */ }) {
+ Icon(Icons.Default.ArrowUpward, "Scroll to Top")
+ ability.kt
+ ```
+
+
+
+2. **Use `derivedStateOf`** when the calculation itself is computationally
+ heavy and is read multiple times across the composition pass, so that the
+ benefits of multi-read caching outweigh the invalidation cost.
+
+ - *Read frequency nuance for trivial calculations* : `derivedStateOf` can also outperform `computedStateOf` for trivial calculations if read frequently enough per composition pass. Specifically, `derivedStateOf` starts to make up for the invalidation cost for trivial calculations when read about `10 / numberOfStatesReadPerCalculation` times per composition (assuming the calculation never invalidates between reads).
+3. **Prefer plain Kotlin getters (`get()`)** for trivial property lookups or
+ inverted booleans where invalidation dampening is not needed:
+
+
+ ```kotlin
+ var isLocked by mutableStateOf(false)
+ val isReady: Boolean get() = !isLocked
+ ```
+
+
+
+4. **Avoid `produceState` + `snapshotFlow`** for synchronous derivations:
+ Wrapping `snapshotFlow` in `produceState` adds unnecessary coroutine
+ allocation, context switching, and flow dispatch overhead compared to
+ `computedStateOf` or `derivedStateOf`.
+
+ - **Bad (Unnecessary coroutine and flow dispatch)**:
+
+
+ ```kotlin
+ val isScrolledToTop by produceState(initialValue = true, scrollState) {
+ snapshotFlow { scrollState.firstVisibleItemIndex == 0 }
+ .collect { value = it }
+ }
+ ```
+
+
+
+ - **Optimized (Synchronous snapshot derivation)**:
+
+
+ ```kotlin
+ // Compose 1.13+:
+ val isScrolledToTop by remember {
+ computedStateOf { scrollState.firstVisibleItemIndex == 0 }
+ }
+ // Compose < 1.13:
+ val isScrolledToTop by remember {
+ derivedStateOf { scrollState.firstVisibleItemIndex == 0 }
+ }
+ ```
+
+
+
+## 4. Strong skipping mode and stability
+
+Don't annotate everything with `@Stable` or `@Immutable` (or wrap collections
+in `ImmutableList`) in an attempt to force every composable to skip.
+
+**Skipping a composable is not free**. Forcing stability can actually
+degrade runtime performance instead of improving it.
+
+### A. How Compose handles skipping under the hood
+
+When a composable is made skippable, the Compose compiler generates slot table
+storage and comparison logic for every parameter:
+
+ // What you write
+ @Composable
+ fun ContactCell(contact: Contact) {
+ /* Your function body */
+ }
+
+ // What the Compose compiler generates (simplified)
+ @Composable
+ fun ContactCell(
+ contact: Contact,
+ $composer: Composer?,
+ $changed: Int
+ ) {
+ val $dirty = $changed or $composer.changed(contact)
+
+ if ($dirty) {
+ /* Your function body */
+ } else {
+ $composer.skipToGroupEnd()
+ }
+ }
+
+Every parameter passed to a composable is effectively stored in the slot table
+as if it were `remember`ed. On every recomposition pass:
+
+1. Compose reads the previous parameter value from the slot table.
+2. It executes an equality check between the old and new parameter values.
+3. If any check fails, the composable executes its body; if all checks pass, it calls `$composer.skipToGroupEnd()`.
+
+This slot table caching and comparison logic incurs memory and CPU overhead on
+every recomposition attempt.
+
+### B. Instance equality (`===`) versus structural equality (`.equals()`)
+
+Under **Strong Skipping Mode** (enabled by default in modern Compose):
+
+- **Unstable parameters** are compared using **instance equality** (`===` / `$composer.changedInstance(param)`).
+- **Stable parameters** (primitives, `@Stable` / `@Immutable` annotated classes, or types with all stable properties) are compared using **structural equality** (`.equals()` / `$composer.changed(param)`).
+
+#### Why `@Stable`, `@Immutable`, and `ImmutableList` can hurt performance
+
+Unstable parameters are not prevented from skipping: if the same instance is
+passed across recompositions (`param === oldParam`), Compose skips the
+composable quickly with a fast `O(1)` pointer check.
+
+When you annotate a class with `@Stable` or `@Immutable` (or replace
+`kotlin.collections.List` with `ImmutableList`), Compose switches from fast
+`===` pointer comparison to deep `.equals()` structural comparison.
+
+- **Expensive Deep Comparisons** : In complex models or large collections (e.g. hundreds of items), running `equals()` on every recomposition can be far more expensive than executing the composable's body. Method traces often reveal that `Model.equals` consumes the vast majority of frame time.
+- **Composition Counts != Benchmarks** : A composable that skips but spends 3ms evaluating a deep `.equals()` check is significantly slower than a composable that recomposes in 0.2ms.
+- **Never Recommend Replacing `List` with `ImmutableList`** : Never recommend converting `kotlin.collections.List` properties into `kotlinx.collections.immutable.ImmutableList` (or Guava `ImmutableList` via `stability_config.txt`) on `@Stable` or `@Immutable` models. Standard `List` already skips via `O(1)` reference equality (`===`) under Strong Skipping Mode, whereas `ImmutableList` forces `O(N)` element-by-element `.equals()` traversal.
+
+#### Scope guard: when to remove existing `@Stable` or `@Immutable`
+
+When auditing or refactoring an existing codebase, apply a strict scope guard
+before recommending changes to existing stability annotations:
+
+1. **Only Recommend Removing `@Stable` / `@Immutable` for Large Datasets in
+ Lazy Layouts** : Strongly recommend removing existing `@Stable` or `@Immutable` annotations from data models **only** when **both** conditions apply:
+ - The model is used within a **lazy layout** (`LazyColumn`, `LazyRow`, `LazyVerticalGrid`, `LazyHorizontalGrid`, `Pager`, `ScalingLazyColumn`) as the input collection container or item model, **AND**
+ - It holds a **large dataset or deep nested collection tree** (such as a full screen stream, hundreds of items/tags, or nested lists like `Post` containing `List` and `List`), where `O(N)` structural equality checks during scrolling or recomposition are expensive.
+2. **Leave Existing Annotations Untouched Elsewhere** : For all other use cases where a codebase already has `@Stable`, `@Immutable`, or `ImmutableList`--- such as non-lazy screen/component models, standalone UI elements, or models with small/bounded collections (e.g. a card model holding 2--5 badges, thumbnails, or metadata rows) where dataset size is small or hard to estimate statically---**leave the existing annotations untouched**.
+
+### C. Parameter unwrapping and equality propagation pitfall (`ImmutableList`)
+
+The Compose compiler includes an optimization where propagating the exact same
+object reference down the composable hierarchy allows Compose to reuse previous
+equality results and skip redundant comparisons.
+
+When a parent `@Composable` passes the exact same parameter reference to a child
+`@Composable`, the Compose compiler propagates its dirty bitmask so the child
+can skip re-evaluating `.equals()`.
+
+However, when a parent `@Composable` receives a `data class` containing a large
+`ImmutableList` and passes the unwrapped `ImmutableList` to a child
+`@Composable`, **Compose runs `O(N)` structural equality twice**:
+
+
+```kotlin
+// Bad: Both HomeScreen (via data class .equals()) and HomeContent evaluate
+// O(N) structural equality on the same ImmutableList in a single frame
+data class HomeScreenModel(
+ val header: String,
+ val items: ImmutableList
+)
+
+@Composable
+private fun HomeScreen(model: HomeScreenModel) {
+ TopAppBar(title = { Text(model.header) })
+ // Unwrapping model.items to pass to a child @Composable
+ HomeContent(model.items)
+}
+
+// Because contentList is an ImmutableList, HomeContent must re-evaluate O(N)
+// .equals() on the list itself, even though HomeScreen already compared model
+@Composable
+private fun HomeContent(contentList: ImmutableList) {
+ LazyColumn {
+ items(contentList) { /* ... */ }
+ }
+}
+```
+
+
+
+In the preceding example, because `items` is an `ImmutableList` (which the
+compiler treats as stable and compares with `.equals()`) inside a `data class`:
+
+1. `HomeScreen` evaluates `model.equals(oldModel)`, which traverses `model.items.equals(oldModel.items)` in `O(N)` time.
+2. `HomeContent` receives `model.items` rather than `model`, so the compiler cannot propagate `model`'s `$dirty` flag and calls `contentList.equals(oldContentList)` a second time (`O(N)`).
+
+#### Scope guard: when parameter unwrapping is *not* an issue
+
+Do **not** flag parameter unwrapping unless **all** of the following conditions
+are met:
+
+1. **Both caller and callee are skippable `@Composable` functions** : Plain Kotlin functions, node-building helpers, and `LazyListScope` / `LazyGridScope` DSL extensions (for example, `fun verticalGridUi(itemList =
+ content.itemList, ...)`) are not skippable composables and do not generate `$composer.changed(...)` equality checks in the slot table.
+2. **The parent object is a `@Composable` parameter with structural
+ `.equals()`** : If the container object is read from state inside the function body (such as `val content = model.contentState.value`) rather than passed as a `@Composable` parameter, or is a regular `class` without a deep `data class` `.equals()` implementation, no parent equality check occurred in the first place.
+3. **The unwrapped property itself triggers expensive `O(N)` `.equals()`** : The unwrapped property must be an `ImmutableList` (or a collection marked stable in `stability_config.txt`). **Never** flag unwrapping a standard `kotlin.collections.List` (`Set`, `Map`) or primitive/value-type properties (`Int`, `Dp`, `String`, `Boolean`, enums)---under Strong Skipping Mode, standard `List` is compared via `O(1)` reference equality (`===`), so unwrapping `content.itemList: List` has zero deep-comparison overhead.
+
+### D. Don't over-index on recomposition counts
+
+Recomposition counts are not a substitute for benchmarks. Once composition is
+scheduled for a screen, skipping a single intermediate composable saves minimal
+time while still incurring the overhead of remembering parameters and evaluating
+equality checks. Rather than trying to eliminate every individual recomposition,
+focus on **avoiding unnecessary compositions** altogether by deferring rapidly
+changing state reads to the Layout or Draw phases (for example, using lambda
+modifiers and `graphicsLayer`), and
+**keep composables lightweight** so they execute quickly when recomposition
+occurs.
+
+### E. Guidelines for designing fast composables
+
+1. **Prefer fewer, easy-to-compare arguments** : Pass primitives (`String`, `Int`, `Long`, `Dp`) or standard `List` references (which compare via fast `===`) rather than large composite objects whenever practical.
+2. **Avoid double `O(N)` comparisons on `ImmutableList`** : Do not wrap large collections in `ImmutableList` inside `data class` models where both parent and child `@Composable` functions will run `O(N)` `.equals()` on the list; use standard `List` so Compose uses `O(1)` `===` reference equality.
+3. **Merge related effects and state**: Minimize the number of remembered objects and dispatched effects to reduce slot table overhead.
+4. **Be selective with `@Stable` / `@Immutable`** : Only add `@Stable` or `@Immutable` to small, flat UI models from non-Compose modules where instances are frequently recreated and `.equals()` is cheap `O(1)`. Never add `@Stable`, `@Immutable`, or `ImmutableList` to large collections or deep data graphs. When auditing existing code, only remove `@Stable` or `@Immutable` from models that feed lazy layouts (`LazyColumn`, `LazyRow`, `LazyGrid`) with large datasets or deep nested collections; leave existing annotations on non-lazy or small-collection models untouched.
+5. **Always benchmark**: Use Macrobenchmark or Microbenchmark to measure real frame timing and CPU impact. Never rely solely on recomposition counts to judge performance.
+
+- **Optimized (Remove `@Immutable` on large lazy-layout models to rely on fast
+ `===` instance equality, and pass lightweight primitives)**:
+
+
+ ```kotlin
+ // Unstable model skips using === pointer check when instance is unchanged
+ data class FeedState(
+ val feedId: String,
+ val items: List
+ )
+
+ @Composable
+ fun FeedScreen(state: FeedState) {
+ // Pass only the primitive ID needed by the header
+ FeedHeader(feedId = state.feedId)
+ // Pass the state or items directly
+ FeedList(items = state.items)
+ }
+ ```
+
+
+
+For more information, see the [Strong Skipping Mode documentation](https://developer.android.com/develop/ui/compose/performance/stability/strongskipping).
+
+## 5. Using `remember` and calculation keys
+
+While `remember` is essential for preserving state and caching expensive
+operations across recompositions, each call stores its result and keys in the
+slot table and checks key equality on every recomposition. Use `remember` when
+object identity must be preserved or when caching nontrivial work (such as
+formatters, shaders, paths, or list transformations), rather than wrapping
+trivial expressions or cheap allocations whose key comparisons cost more than
+the allocation itself.
+
+### Require explicit calculation keys on `remember`
+
+`remember { ... }` with no keys executes only on initial composition and caches
+the result for the lifetime of the Composable. If the Composable recomposes with
+new parameter values, omitting keys returns the stale cached value. For standard
+Kotlin calculations and object instantiations, always pass every external
+variable or parameter referenced inside the `remember` block as a key.
+
+- **Exception for `derivedStateOf`** : When wrapping `derivedStateOf`, do
+ **not** pass the snapshot state properties read inside the calculation
+ lambda as `remember` keys:
+
+
+ ```kotlin
+ // Bad: passing snapshot state property as a key breaks derivedStateOf
+ val showButton by remember(scrollState.firstVisibleItemIndex) {
+ derivedStateOf { scrollState.firstVisibleItemIndex > 0 }
+ }
+
+ // Optimized: derivedStateOf tracks firstVisibleItemIndex internally
+ val showButton by remember {
+ derivedStateOf { scrollState.firstVisibleItemIndex > 0 }
+ }
+ ```
+
+
+
+ `derivedStateOf` dynamically tracks snapshot state reads internally and
+ invalidates only when the derived result actually changes. Passing state
+ values as `remember` keys forces the `remember` block to recreate on every
+ single state mutation, completely destroying `derivedStateOf`'s ability to
+ skip recompositions. Only pass the `State` holder instance as a key if the
+ instance itself can change.
+- **Bad (Omitting keys for standard calculation returns stale value upon
+ parameter change)**:
+
+
+ ```kotlin
+ @Composable
+ fun FormattedDateLabel(timestamp: Long, locale: Locale) {
+ val formattedDate = remember {
+ SimpleDateFormat("yyyy-MM-dd", locale).format(Date(timestamp))
+ }
+ Text(text = formatability.kt
+ ```
+
+
+
+- **Optimized (Pass referenced calculation parameters as keys)**:
+
+
+ ```kotlin
+ @Composable
+ fun FormattedDateLabel(timestamp: Long, locale: Locale) {
+ val formattedDate = remember(timestamp, locale) {
+ SimpleDateFormat("yyyy-MM-dd", locale).format(Date(timestamp))
+ }
+ Text(text = formatability.kt
+ ```
+
+
\ No newline at end of file
diff --git a/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/SKILL.md b/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/SKILL.md
index ed21187..cc1eccd 100644
--- a/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/SKILL.md
+++ b/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/SKILL.md
@@ -9,6 +9,7 @@ description: Provides a structured workflow for migrating an Android XML View to
license: Complete terms in LICENSE.txt
metadata:
author: Google LLC
+ last-updated: '2026-09-18'
keywords:
- Jetpack Compose
- migration
@@ -120,4 +121,4 @@ verified, write a Compose UI test for the new composable.
### Step 10: XML code removal
Delete the migrated XML file and its associated legacy tests. **Caution:** Only
-remove code and resources that are not referenced by other parts of the project.
\ No newline at end of file
+remove code and resources that are not referenced by other parts of the project.
diff --git a/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/designsystems/migrate-xml-theme-to-compose.md b/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/designsystems/migrate-xml-theme-to-compose.md
index d8230f8..60b7968 100644
--- a/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/designsystems/migrate-xml-theme-to-compose.md
+++ b/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/designsystems/migrate-xml-theme-to-compose.md
@@ -15,9 +15,9 @@ Material 3 Compose theming.
| Term | Definition |
|---|---|
| `MaterialTheme` | The composable function that provides theming (colors, typography, shapes) to Compose UI components. |
-| `Shape` | A Compose object used to define custom component shapes for a `MaterialTheme`. |
+| `Shapes` | A Compose object used to define custom component shapes for a `MaterialTheme`. |
| `Typography` | A Compose object used to define custom text styles (font families, sizes, weights) for a `MaterialTheme`. |
-| `Color` | A Compose object used to define custom color schemes for `MaterialTheme`. |
+| `ColorScheme` | A Compose object used to define custom color schemes for `MaterialTheme`. |
| XML Theme | The Android theming system defined in XML files, used by the View system. |
## Limitations
@@ -79,8 +79,8 @@ Material 3 Compose.
- If your app uses custom shapes:
- 1. In your Compose code, define a `Shape` object to replicate your XML shape definitions.
- 2. Provide this `Shape` object to your `MaterialTheme`.
+ 1. In your Compose code, define a `Shapes` object to replicate your XML shape definitions.
+ 2. Provide this `Shapes` object to your `MaterialTheme`.
For more details, see [shapes](https://developer.android.com/develop/ui/compose/designsystems/material3#shapes).
- If your app uses custom typography:
@@ -111,6 +111,7 @@ Material 3 Compose.
## Step 5: Migrate styles (styles.xml)
XML styles (styles.xml) system defines styles and appearance of:
+
1. Widgets, components, themes for windows and dialogs
2. Typography
3. Themes and overlays
@@ -118,12 +119,14 @@ XML styles (styles.xml) system defines styles and appearance of:
XML Views and components combine multiple attributes to create a style.
They set their styles from styles.xml in two different ways:
+
1. Setting "style="@style/..." directly and explicitly in the XML View
2. Setting the style indirectly and implicitly for a component as part of a larger Theme (theme.xml)
-Styles have no direct equivalent in Compose - instead styles are passed as:
-parameters to composables, defined in the AppTheme, or by creating layered,
-reusable composable variations with the defined style.
+Styles have no **direct** equivalent in Compose - instead styles are passed as:
+parameters or modifiers to composables, using the
+[new, experimental Styles API](https://developer.android.com/develop/ui/compose/styles) defined in the AppTheme, or by creating
+layered, reusable composable variations with the defined style.
Provide separate @Composable functions named according to the style and the
base component, to signify the difference in styling and use cases for those
@@ -139,11 +142,11 @@ components.
| XML | Compose |
|---|---|
-| `Theme.MaterialComponents.*` | `MaterialTheme(colorScheme, typography, shapes) { }` |
+| `Theme.Material3.*` | `MaterialTheme(colorScheme, typography, shapes) { }` |
| `TextAppearance.Material3.BodyMedium` | `TextStyle(...)` defined in `Typography(bodyMedium = ...)` |
| `ShapeAppearance.*.SmallComponent` | `Shapes(small = RoundedCornerShape(X.dp))` |
-| `Widget.MaterialComponents.Button` | `Button(colors = ButtonDefaults.buttonColors(...))` |
-| `Widget.MaterialComponents.CardView` | `Card(shape=..., elevation=..., colors=...)` |
+| `Widget.Material3.Button` | `Button(colors = ButtonDefaults.buttonColors(...))` |
+| `Widget.Material3.CardView` | `Card(shape=..., elevation=..., colors=...)` |
| `Widget.*.TextInputLayout.OutlinedBox` | `OutlinedTextField(colors = OutlinedTextFieldDefaults.colors(...))` |
| `Widget.*.Chip.Filter` | `FilterChip(colors = FilterChipDefaults.filterChipColors(...))` |
| `Widget.*.Toolbar.Primary` | `TopAppBar(colors = TopAppBarDefaults.topAppBarColors(...))` |
@@ -160,7 +163,7 @@ components.
## Step 6: Validate the theme migration
Always use the existing theme values from the original XML theme as the source
-of truth for the new Material Theme in Compose
+of truth for the new Material Theme in Compose.
Never invent new theme values during migration, to maintain brand consistency
and avoid visual regressions.
diff --git a/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/migrate/interoperability-apis/compose-in-views.md b/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/migrate/interoperability-apis/compose-in-views.md
index 0a0f7eb..83c7188 100644
--- a/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/migrate/interoperability-apis/compose-in-views.md
+++ b/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/migrate/interoperability-apis/compose-in-views.md
@@ -1,3 +1,6 @@
+> [!NOTE]
+> **Note:** Interoperability is supported for hybrid apps that use Compose and Views. For apps that are only in Compose, use the recommended Compose-only architecture with a single Activity and latest navigation libraries, like Navigation 3.
+
You can add Compose-based UI into an existing app that uses a View-based design.
To create a new, entirely Compose-based screen, have your
@@ -61,7 +64,10 @@ The table below summarizes the different scenarios you can use
| [`DisposeOnLifecycleDestroyed`](https://developer.android.com/reference/kotlin/androidx/compose/ui/platform/ViewCompositionStrategy.DisposeOnLifecycleDestroyed) | The Composition will be disposed when the provided [`Lifecycle`](https://developer.android.com/reference/androidx/lifecycle/Lifecycle) is destroyed. Interop scenario \* `ComposeView` in a Fragment's View. |
| [`DisposeOnViewTreeLifecycleDestroyed`](https://developer.android.com/reference/kotlin/androidx/compose/ui/platform/ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) | The Composition will be disposed when the `Lifecycle` owned by the `LifecycleOwner` returned by `ViewTreeLifecycleOwner.get` of the next window the View is attached to is destroyed. Interop scenario: \* `ComposeView` in a Fragment's View. \* `ComposeView` in a View wherein the Lifecycle is not known yet. |
-## `ComposeView` in Fragments
+## `ComposeView` in Fragments (Transitionary Step)
+
+> [!NOTE]
+> **Note:** For hybrid apps with Views and Compose, that use Fragments, use `ComposeView` to wrap composables content and add to a Fragment during migration. For Compose-only apps, do not use Fragments and instead use the recommended Compose-only architecture with a single Activity and latest navigation libraries, like Navigation 3.
If you want to incorporate Compose UI content in a fragment or an existing View
layout, use [`ComposeView`](https://developer.android.com/reference/kotlin/androidx/compose/ui/platform/ComposeView)
diff --git a/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/migrate/interoperability-apis/views-in-compose.md b/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/migrate/interoperability-apis/views-in-compose.md
index 9b6add2..dcf5163 100644
--- a/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/migrate/interoperability-apis/views-in-compose.md
+++ b/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/migrate/interoperability-apis/views-in-compose.md
@@ -4,6 +4,11 @@ Compose, like
[`AdView`](https://developers.google.com/android/reference/com/google/android/gms/ads/AdView).
This approach also lets you reuse custom views you may have designed.
+> [!NOTE]
+> **Note:** Use `AndroidView` as a wrapper only for missing SDK components without Compose support. Rewrite your custom Views in Compose wherever possible, starting the migration with the simplest custom Views and scaling to more complex ones.
+
+
+
To include a view element or hierarchy, use the [`AndroidView`](https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/AndroidView.composable#AndroidView(kotlin.Function1,androidx.compose.ui.Modifier,kotlin.Function1))
composable. `AndroidView` is passed a lambda that returns a
[`View`](https://developer.android.com/reference/android/view/View). `AndroidView` also provides an `update`
@@ -63,6 +68,9 @@ To embed an XML layout, use the
API, which is provided by the `androidx.compose.ui:ui-viewbinding` library. To
do this, your project must enable [view binding](https://developer.android.com/topic/libraries/view-binding#setup).
+> [!NOTE]
+> **Note:** For Compose-only apps, don't use `AndroidViewBinding` to inflate full screen-level XML layouts, and instead use it only for smaller, legacy XML layouts during the incremental migration process.
+
```kotlin
@Composable
@@ -113,12 +121,15 @@ fun AndroidViewInLazyList() {
-## Fragments in Compose
+## Fragments in Compose (Transitionary Step)
Use the `AndroidFragment` composable to add a `Fragment` in Compose.
`AndroidFragment` has fragment-specific handling such as removing the
fragment when the composable leaves the composition.
+> [!NOTE]
+> **Note:** Wrap existing Fragments in Compose only during incremental migration. For Compose-only apps, do not use Fragments and instead use the recommended Compose-only architecture with a single Activity and latest navigation libraries, like Navigation 3.
+
To include a fragment, use the [`AndroidFragment`](https://developer.android.com/reference/kotlin/androidx/fragment/compose/package-summary#AndroidFragment)
composable. You pass a `Fragment` class to `AndroidFragment`, which then adds
an instance of that class directly into the composition. `AndroidFragment` also
@@ -141,6 +152,39 @@ fun FragmentInComposeExample() {
+### Cap Fragment lifecycle state
+
+In Fragment [1.9.0](https://developer.android.com/jetpack/androidx/releases/fragment#1.9.0) and higher, you can use the `maxLifecycle`
+parameter to cap the lifecycle state of the embedded `Fragment`.
+
+For example, in a [`HorizontalPager`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/pager/HorizontalPager.composable), you can dynamically set `maxLifecycle`
+to [`RESUMED`](https://developer.android.com/reference/kotlin/androidx/lifecycle/Lifecycle.State#RESUMED) only when the corresponding page is active, keeping offscreen
+pages capped at [`STARTED`](https://developer.android.com/reference/kotlin/androidx/lifecycle/Lifecycle.State#STARTED). When the user navigates to another page, the
+previous page's fragment cap becomes `STARTED`. This pauses or cancels workloads
+scoped to `RESUMED` (such as coroutines running in
+[`repeatOnLifecycle(RESUMED)`](https://developer.android.com/reference/kotlin/androidx/lifecycle/package-summary#(androidx.lifecycle.LifecycleOwner).repeatOnLifecycle(androidx.lifecycle.Lifecycle.State,kotlin.coroutines.SuspendFunction1))), preventing offscreen pages from performing
+foreground-only work while keeping their UI initialized at `STARTED`:
+
+
+```kotlin
+HorizontalPager(state = pagerState) { page ->
+ // Dynamically cap the lifecycle state based on whether the page is selected
+ val maxLifecycle = if (pagerState.settledPage == page) {
+ Lifecycle.State.RESUMED
+ } else {
+ Lifecycle.State.STARTED
+ }
+
+ when (page) {
+ 0 -> AndroidFragment(maxLifecycle = maxLifecycle)
+ 1 -> AndroidFragment(maxLifecycle = maxLifecycle)
+ /* Other pages and corresponding fragments */
+ }
+}
+```
+
+
+
## Calling the Android framework from Compose
Compose operates within the Android framework classes. For example, it's hosted
diff --git a/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/setup-compose-dependencies-and-compiler.md b/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/setup-compose-dependencies-and-compiler.md
index 22c39c5..4ec0a1c 100644
--- a/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/setup-compose-dependencies-and-compiler.md
+++ b/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/setup-compose-dependencies-and-compiler.md
@@ -1,64 +1,59 @@
-## Set up the Compose Compiler Gradle plugin
-
-For Gradle users, you can use the Compose Compiler Gradle plugin to make setting
-up and configuring Compose easier.
+For Gradle, use the Compose Compiler Gradle plugin to set up and configure
+Compose.
> [!NOTE]
> **Note:** The Compose Compiler Gradle Plugin is only available from Kotlin 2.0+. For migration instructions, see ["Jetpack Compose compiler moving to the Kotlin
-> repository"](https://android-developers.googleblog.com/2024/04/jetpack-compose-compiler-moving-to-kotlin-repository.html). For an example migration, see the [Compose Samples
-> PR](https://github.com/android/compose-samples/pull/1354) in the Compose samples.
+> repository"](https://android-developers.googleblog.com/2024/04/jetpack-compose-compiler-moving-to-kotlin-repository.html).
### Set up with Gradle version catalogs
-The following instructions outline how you can set up the Compose Compiler
-Gradle plugin:
+Set up the Compose Compiler Gradle plugin:
-1. In your `libs.versions.toml` file, remove any reference to the Compose Compiler.
-2. In the `versions` and `plugins` section, add the following new dependency:
+1. In the `libs.versions.toml` file, remove any reference to the Compose Compiler.
+2. In the `versions` and `plugins` sections, add the new dependency:
- [versions]
- kotlin = "2.3.10"
+ [versions]
+ kotlin = "2.4.10"
- [plugins]
- org-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+ [plugins]
+ org-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
- // Add this line
- compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+ // Add this line
+ compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
-1. In your project's root `build.gradle.kts` file, add the following snippet to the `plugins` section.
+3. In the project's root `build.gradle.kts` file, add the following to the
+ `plugins` section.
- plugins {
+ plugins {
// Existing plugins
alias(libs.plugins.compose.compiler) apply false
- }
+ }
-1. In each module that uses Compose, apply the plugin as follows.
+4. In each module that uses Compose, apply the plugin:
- plugins {
+ plugins {
// Existing plugins
alias(libs.plugins.compose.compiler)
- }
+ }
-Your app should now build and compile if you are using the default set up. If
-you had configured custom options on the Compose compiler, see the following
+The project should now build and compile if it was using the default set up. If
+it had configured custom options on the Compose compiler, follow the next
section.
### Set up the Compose Compiler without Gradle version catalogs
-Add the
-following plugin to `build.gradle.kts` files associated with modules you use
-Compose:
+Add the plugin to `build.gradle.kts` files associated with modules where Compose
+is used:
plugins {
- id("org.jetbrains.kotlin.plugin.compose") version "2.3.10" // this version matches your Kotlin version
+ id("org.jetbrains.kotlin.plugin.compose") version "2.4.10" // this version matches your Kotlin version
}
-You might also need to add this classpath to your top-level project
-`build.gradle.kts` file:
+Add the classpath to your top-level project `build.gradle.kts` file:
buildscript {
dependencies {
- classpath("org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin:2.3.10")
+ classpath("org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin:2.4.10")
}
}
@@ -66,7 +61,7 @@ You might also need to add this classpath to your top-level project
To configure the Compose compiler using the Gradle plugin, add the
`composeCompiler` block to the module's `build.gradle.kts` file at the top
-level.
+level:
android { ... }
@@ -79,6 +74,11 @@ For the full list of available options, see the [documentation](https://www.jetb
## Set up Compose dependencies
+Always use the latest Compose BOM version: `2026.09.00`.
+
+Set the `compose` flag to `true` inside the Android [`BuildFeatures`](https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/dsl/BuildFeatures) to
+enable [Compose functionality](https://developer.android.com/develop/ui/compose/tooling) in Android Studio.
+
Add the following definition to your app's `build.gradle` file:
### Groovy
@@ -97,17 +97,13 @@ Add the following definition to your app's `build.gradle` file:
}
}
-Setting the `compose` flag to `true` inside the Android [`BuildFeatures`](https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/dsl/BuildFeatures)
-block enables [Compose functionality](https://developer.android.com/develop/ui/compose/tooling) in Android Studio.
-
-Finally, add the Compose BOM and the subset of Compose library dependencies
-you need to your dependencies from the following block:
+Add the Compose BOM and the subset of Compose library dependencies:
### Groovy
dependencies {
- def composeBom = platform('androidx.compose:compose-bom:2026.03.00')
+ def composeBom = platform('androidx.compose:compose-bom:2026.09.00')
implementation composeBom
androidTestImplementation composeBom
@@ -134,7 +130,7 @@ you need to your dependencies from the following block:
// Optional - Integration with activities
implementation 'androidx.activity:activity-compose:1.13.0'
// Optional - Integration with ViewModels
- implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.11.0'
// Optional - Integration with LiveData
implementation 'androidx.compose.runtime:runtime-livedata'
// Optional - Integration with RxJava
@@ -146,7 +142,7 @@ you need to your dependencies from the following block:
dependencies {
- val composeBom = platform("androidx.compose:compose-bom:2026.03.00")
+ val composeBom = platform("androidx.compose:compose-bom:2026.09.00")
implementation(composeBom)
androidTestImplementation(composeBom)
@@ -173,7 +169,7 @@ you need to your dependencies from the following block:
// Optional - Integration with activities
implementation("androidx.activity:activity-compose:1.13.0")
// Optional - Integration with ViewModels
- implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0")
+ implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.11.0")
// Optional - Integration with LiveData
implementation("androidx.compose.runtime:runtime-livedata")
// Optional - Integration with RxJava
@@ -183,4 +179,19 @@ you need to your dependencies from the following block:
> [!NOTE]
> **Note:** Jetpack Compose is shipped using a Bill of Materials (BOM), to keep the versions of all library groups in sync. Read more about it in the [Bill of
-> Materials page](https://developer.android.com/develop/ui/compose/bom/bom).
\ No newline at end of file
+> Materials page](https://developer.android.com/develop/ui/compose/bom/bom).
+
+## `compileSdk` and Android Gradle Plugin compatibility
+
+Compose library releases continually adopt the latest `compileSdk` versions to
+provide access to the latest Android features. Newer `compileSdk` versions
+require newer versions of Android Gradle Plugin, so adopting new Compose
+releases also requires projects to adopt new versions of the Android Gradle
+Plugin. We recommend keeping your project's `compileSdk` up to date with the
+latest released versions. `compileSdk` is unrelated from `targetSdk`.
+
+For example, starting with Compose 1.12.0, projects are required to use
+`compileSdk 37` and Android Gradle Plugin (AGP) 9.
+
+To check which version of AGP is supported for different API levels, see the
+[Android Gradle plugin API level support](https://developer.android.com/build/releases/about-agp#api-level-support) documentation.
\ No newline at end of file
diff --git a/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/references/xml-layout-migration.md b/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/references/xml-layout-migration.md
index 8e93794..5aeb6b3 100644
--- a/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/references/xml-layout-migration.md
+++ b/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/references/xml-layout-migration.md
@@ -1,72 +1,84 @@
## 1. Structural analysis \& mapping
-**Identify the precise mapping** between XML elements and Compose equivalents. You must determine:
-\* The exact `@Composable` functions (e.g., `ConstraintLayout`, `Column`, `LazyColumn`) that replace the XML tag hierarchy.
-\* The specific parameters and `Modifier` extensions required to replicate XML attributes (e.g., `layout_width`, `padding`, `elevation`).
-\* The appropriate state management strategy for interactive elements.
+**Identify the precise mapping** between XML elements and Compose equivalents.
+You must determine:
+
+- The exact `@Composable` functions (e.g., `ConstraintLayout`, `Column`, `LazyColumn`) that replace the XML tag hierarchy.
+- The specific parameters and `Modifier` extensions required to replicate XML attributes (e.g., `layout_width`, `padding`, `elevation`).
+- The appropriate state management strategy for interactive elements.
## 2. Migration execution
-**Convert the XML layout code to Jetpack Compose**, ensuring the visual hierarchy and layout logic are preserved while leveraging Compose's declarative nature.
+**Convert the XML layout code to Jetpack Compose**, ensuring the visual
+hierarchy and layout logic are preserved while leveraging Compose's declarative
+nature.
## 3. Theming \& design system integrity
**Do not use hard-coded values.** Follow these rules for styling:
-\* **Token Alignment:** Cross-reference XML dimension, color, and style attributes with the existing Compose `Theme` (e.g., `MaterialTheme.colorScheme` or custom design system tokens).
-\* **Reuse over Creation:** If matching values exist in the current Compose theme, reuse them. If a value is missing but required for the design, define it within the theme structure rather than hard-coding it in the Composable.
-\* **Project Consistency:** You **MUST** strictly adhere to existing code conventions, naming standards, and implementation patterns found in the project. **Prioritize** project-specific reusable components over generic Material defaults.
+
+- **Token Alignment:** Cross-reference XML dimensions, colors, and style attributes with the existing Compose `Theme` (e.g., `MaterialTheme.colorScheme` or custom design system tokens).
+- **Reuse over Creation:** If matching values exist in the current Compose theme, reuse them. If a value is missing but required for the design, define it within the theme structure rather than hard-coding it in the Composable.
+- **Project Consistency:** You **MUST** strictly adhere to existing code conventions, naming standards, and implementation patterns found in the project. **Prioritize** project-specific reusable components over generic Material defaults.
## 4. Component layering \& reusability
-Evaluate if the XML layout serves as a foundation-level design system component (reused across the app with a distinct role). If it is:
-\* **Create a reusable composable:** Do not just inline the code. Define a new standalone `@Composable`.
-\* **Parameterization:** Expose specific parameters for variable data (text, colors, styles) and use `Modifier` for layout-specific customizations.
-\* **Feature parity \& restriction:** Ensure the new composable enforces the same UI constraints as the original XML component, preventing unauthorized style overrides while maintaining the intended flexibility.
+Evaluate if the XML layout serves as a foundation-level design system component
+(reused across the app with a distinct role). If it is:
+
+- **Create a reusable composable:** Do not just inline the code. Define a new standalone `@Composable`.
+- **Parameterization:** Expose specific parameters for variable data (text, colors, styles) and use `Modifier` for layout-specific customizations.
+- **Feature parity \& restriction:** Ensure the new composable enforces the same UI constraints as the original XML component, preventing unauthorized style overrides while maintaining the intended flexibility.
Example before migration:
-`xml
-`
+
+
Example after migration:
-## ```kotlin
+
+```kotlin
@Composable
fun RoundedBorderlessButton(
-text: String,
-onClick: () -> Unit,
-modifier: Modifier = Modifier,
-enabled: Boolean = true
+ text: String,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true
) {
-TextButton(
-onClick, modifier
-.defaultMinSize(minWidth = dimensionResource(R.dimen.min_width))
-.padding(
-start = dimensionResource(R.dimen.padding_2),
-end = dimensionResource(R.dimen.padding_2)
-), enabled, shape = RoundedCornerShape(8.dp),
-colors = ButtonDefaults.textButtonColors(
-contentColor = MaterialTheme.colorScheme.primary
-)
-) {
-Text(
-text = text,
-style = MaterialTheme.typography.bodyMedium.copy(
-fontFamily = FontFamily.SansSerif,
-fontWeight = FontWeight.Medium
-)
-)
-}
+ TextButton(
+ onClick, modifier
+ .defaultMinSize(minWidth = dimensionResource(R.dimen.min_width))
+ .padding(
+ start = dimensionResource(R.dimen.padding_2),
+ end = dimensionResource(R.dimen.padding_2)
+ ), enabled, shape = RoundedCornerShape(8.dp),
+ colors = ButtonDefaults.textButtonColors(
+ contentColor = MaterialTheme.colorScheme.primary
+ )
+ ) {
+ Text(
+ text = text,
+ style = MaterialTheme.typography.bodyMedium.copy(
+ fontFamily = FontFamily.SansSerif,
+ fontWeight = FontWeight.Medium
+ )
+ )
+ }
}
```
+
+
+*** ** * ** ***
+
## 5. Output requirements
- Provide the full Kotlin file content.
diff --git a/jetpack-compose/theming/styles/SKILL.md b/jetpack-compose/theming/styles/SKILL.md
new file mode 100644
index 0000000..2af7e49
--- /dev/null
+++ b/jetpack-compose/theming/styles/SKILL.md
@@ -0,0 +1,226 @@
+---
+name: styles
+description: Use this skill to integrate the Jetpack Compose Styles API into an Android
+ project. This skill guides you through upgrading dependencies, setting up component
+ themes, making custom components styleable, and migrating existing layout properties
+ to use unified styles. Migrate custom design system components, replace hard coded
+ parameters with Style attributes, and use Modifier.styleable for interaction states.
+license: Complete terms in LICENSE.txt
+metadata:
+ author: Google LLC
+ last-updated: '2026-09-08'
+ keywords:
+ - Jetpack Compose
+ - Styles
+ - Theming with Styles
+ - Migrate to Styles
+ - Modifier.styleable
+---
+
+## Limitations
+
+- Warn the user that this skill is EXPERIMENTAL and requires updating to alpha version of Compose and opting in to the Experimental APIs.
+- This skill only supports custom UI components and custom themes.
+- This skill does not support Material Design component Styles.
+
+## Prerequisites
+
+### 1. Upgrade dependencies
+
+- The project must use `compileSdk` version 37 or higher.
+- The project must use `androidx.compose.foundation:foundation` version `1.12.0-alpha01` or higher.
+- Alternatively, the project must use Compose BOM version `2026.04.01` or higher.
+- The API requires this exact package: `import
+ androidx.compose.foundation.style.Style`
+
+### 2. Configure compiler options to enable experimental API
+
+You must opt-in to the experimental API at the project level. Add the following
+block to your module's `build.gradle.kts`:
+
+ kotlin {
+ compilerOptions {
+ jvmTarget = JvmTarget.fromTarget("17")
+ freeCompilerArgs.add("-opt-in=androidx.compose.foundation.style.ExperimentalFoundationStyleApi")
+ }
+ }
+
+## Core workflows and guides
+
+Refer to the official documentation to complete specific development tasks:
+
+- Basic Style Usage: To set backgrounds, sizes, and alignments on a component, follow the [Compose Styles Fundamentals
+ Guide](references/android/develop/ui/compose/styles/fundamentals.md).
+- State and Transitions: To configure property changes for state shifts (like pressed or hovered), follow the [Animations and State-Based Styling
+ Guide](references/android/develop/ui/compose/styles/state-animations.md).
+- Architecture Trade offs: To decide when to use a Style versus a standard Modifier, follow the [Styles versus Modifiers
+ Comparison](references/android/develop/ui/compose/styles/styles-vs-modifiers.md).
+- Theme Level Integration: To connect style definitions with custom themes, follow [Theming with Styles](references/android/develop/ui/compose/styles/theming.md) and [Custom Themes in Compose](references/android/develop/ui/compose/designsystems/custom.md).
+
+## Step-by-Step Migration Workflow
+
+### Step 1: Analyze theme structure
+
+1. Locate your central theme file (such as `Theme.kt`).
+2. Identify design tokens. Note references for colors, typography, and shapes (for example, `LocalColorScheme`, `LocalTypography`, or `LocalShapes`).
+3. If the project lacks Jetpack Compose dependencies, stop. Instruct the user to migrate to Jetpack Compose first.
+4. If the project imports `androidx.compose.material.MaterialTheme`, recommend migrating to Material 3 before proceeding.
+
+### Step 2: Establish `ComponentStyles`
+
+1. Create a new file named `ComponentStyles.kt` in your theme directory.
+2. Define a top-level data class to hold your component styles, for example, the Jetsnack one is called `JetsnackStyles`:
+
+
+ ```kotlin
+ object ExampleComponentStyles {
+ val customButtonStyle: Style = {
+
+ }
+ val customTextFieldStyle: Style = {
+
+ }
+ }
+ ```
+
+
+
+3. Expose this class through your custom theme with a static reference, don't
+ use `CompositionLocals` here as it's not required.
+
+
+ ```kotlin
+ @Immutable
+ class JetsnackTheme(
+ // other Design system properties
+ ) {
+ companion object {
+ val colors: CustomThemingWithStyles.JetsnackColors
+ @Composable @ReadOnlyComposable
+ get() = LocalJetsnackTheme.current.colors
+ // ...
+
+ // add helper static reference
+ val styles: ComponentStyles = ComponentStyles
+ }
+ }
+ ```
+
+
+
+4. Provide extensions on `StyleScope` to reference theme tokens directly if
+ they are exposed using `CompositionLocals`. For example:
+
+
+ ```kotlin
+ val StyleScope.colors: JetsnackColors
+ get() = LocalJetsnackTheme.currentValue.colors
+
+ val StyleScope.typography: androidx.compose.material3.Typography
+ get() = LocalJetsnackTheme.currentValue.typography
+
+ val StyleScope.shapes: Shapes
+ get() = LocalJetsnackTheme.currentValue.shapes
+ ```
+
+
+
+### Step 3: Migrate a component to Styles API
+
+For each custom component (for example, `CustomButton`), complete the following
+sequence:
+
+1. **Establish a visual baseline (If an emulator is available):**
+ - **If you CANNOT run an Android emulator:** Skip this step entirely and proceed to Step 2.
+ - **If you CAN run an Android emulator:** Perform the following to capture a baseline screenshot:
+ - **Option A:** Locate and run an existing screenshot test for the component.
+ - **Option B (If no test exists):** Create a test using the project's existing testing framework, then run it.
+ - **Option C (If no framework exists):** Create a minimal screenshot test using UI Automator or Espresso, then run it.
+2. **Remove individual styling parameters** : Remove styling parameters such as `backgroundColor`, `shape`, `textStyle`, and `contentPadding` from the signature - anything that `StyleScope` supports.
+3. **Add the style parameter** : Add `style: Style = Style` to the function signature. Always ensure the default value is exactly `Style` (e.g., `style:
+ Style = Style`) and not a specific style default like `ChipStyleDefault` or any other value.
+4. **Declare state tracking** : If the component is interactable, create a `MutableStyleState` using the interaction source. Update state fields (such as `isEnabled`) inside the Composable to track the state correctly.
+5. **Apply styleable modifier** : Replace specific layout modifiers on the root element with `Modifier.styleable()`.
+6. **Move defaults to ComponentStyles** : Move hardcoded values from the component definition to a dedicated `Style` instance in `ComponentStyles.kt`.
+7. **Validate component:** Compare the baseline screenshot image taken at the start with the rendered Compose Preview of the new composable. Ignore string content; focus on layout and styling. Iterate on the Compose code until visual parity is achieved. Once verified, write a Compose UI test for the new composable.
+
+#### Migration example
+
+Before Migration:
+
+
+```kotlin
+@Composable
+fun CustomButton(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ backgroundColor: Color = JetsnackTheme.colors.brandLight,
+ disabledBackgroundColor: Color = JetsnackTheme.colors.brandSecondary,
+ shape: Shape = JetsnackTheme.shapes.extraLarge,
+ textStyle: TextStyle = JetsnackTheme.typography.labelLarge,
+ enabled: Boolean = true,
+ content: @Composable RowScope.() -> Unit,
+) {
+ val interactionSource = remember { MutableInteractionSource() }
+ Row(
+ modifier
+ .clickable(onClick = onClick, indication = null, interactionSource = interactionSource)
+ .background(if (enabled) backgroundColor else disabledBackgroundColor, shape)
+ .defaultMinSize(58.dp, 40.dp),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically,
+ content = content,
+ )
+}
+```
+
+
+
+After Migration:
+
+
+```kotlin
+// Exposed via ComponentStyles.kt
+object ComponentStyles {
+ val buttonStyle = Style {
+ background(colors.brandLight)
+ shape(shapes.extraLarge)
+ minWidth(58.dp)
+ minHeight(40.dp)
+ textStyle(typography.labelLarge)
+ disabled {
+ background(colors.brandSecondary)
+ }
+ }
+}
+
+@Composable
+fun CustomButton(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ style: Style = Style,
+ enabled: Boolean = true,
+ content: @Composable RowScope.() -> Unit,
+) {
+ val interactionSource = remember { MutableInteractionSource() }
+ val styleState = rememberUpdatedStyleState(interactionSource) {
+ it.isEnabled = enabled
+ }
+ Row(
+ modifier
+ .clickable(onClick = onClick, indication = null, interactionSource = interactionSource)
+ .styleable(styleState, JetsnackTheme.styles.buttonStyle, style),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically,
+ content = content,
+ )
+}
+```
+
+
+
+### Step 4: Validate Changes
+
+1. Build the project. Verify that there are no compilation errors.
+2. Run your module's screenshot tests.
+3. Compare visual outputs of the whole app between the previous and updated components. Verify that no visual layout regressions occur.
diff --git a/jetpack-compose/theming/styles/references/android/develop/ui/compose/designsystems/custom.md b/jetpack-compose/theming/styles/references/android/develop/ui/compose/designsystems/custom.md
new file mode 100644
index 0000000..59dbb25
--- /dev/null
+++ b/jetpack-compose/theming/styles/references/android/develop/ui/compose/designsystems/custom.md
@@ -0,0 +1,459 @@
+While Material is our recommended design system and Jetpack Compose ships an
+implementation of Material, you are not forced to use it. Material is built
+entirely on public APIs, so it's possible to create your own design system in
+the same manner.
+
+There are several approaches you might take:
+
+- [Extend `MaterialTheme`](https://developer.android.com/develop/ui/compose/designsystems/custom#extending-material) with additional theming values.
+- [Replace one or more Material systems](https://developer.android.com/develop/ui/compose/designsystems/custom#replacing-systems) --- `Colors`, `Typography`, or `Shapes` --- with custom implementations while keeping the others.
+- [Implement a fully custom design system](https://developer.android.com/develop/ui/compose/designsystems/custom#implementing-fully-custom) to replace `MaterialTheme`.
+
+You may also want to continue using Material components with a custom design
+system. It's possible to do this but there are things to keep in mind to suit
+the approach you've taken.
+
+To learn more about the lower-level constructs and APIs used by `MaterialTheme`
+and custom design systems, check out the [Anatomy of a theme in Compose](https://developer.android.com/develop/ui/compose/designsystems/anatomy) guide.
+
+## Extend Material Theming
+
+Compose Material closely models
+[Material Theming](https://m3.material.io/)
+to make it straightforward and type-safe to follow the Material guidelines.
+However, it's possible to extend the color, typography, and shape sets with
+additional values. The simplest approach is to add extension properties:
+
+
+```kotlin
+// Use with MaterialTheme.colorScheme.snackbarAction
+val ColorScheme.snackbarAction: Color
+ @Composable
+ get() = if (isSystemInDarkTheme()) Red300 else Red700
+
+// Use with MaterialTheme.typography.textFieldInput
+val Typography.textFieldInput: TextStyle
+ get() = TextStyle(/* ... */)
+
+// Use with MaterialTheme.shapes.card
+val Shapes.card: Shape
+ get() = RoundedCornerShape(size = 20.dp)
+```
+
+
+
+This provides consistency with `MaterialTheme` usage APIs. An example of this
+defined by Compose itself is
+[`surfaceColorAtElevation`](https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#(androidx.compose.material3.ColorScheme).surfaceColorAtElevation(androidx.compose.ui.unit.Dp)),
+which determines the surface color that should be used depending on the
+elevation.
+
+> [!NOTE]
+> **Note:** This approach is only recommended for straightforward theming value additions, or for values that are the same in different themes. If you have multiple themes, it's better to define a class with new properties instead.
+
+Another approach is to define an extended theme that "wraps" `MaterialTheme` and
+its values.
+
+Suppose you want to add two additional colors --- `caution` and `onCaution`, a
+yellow color used for actions that are semi-dangerous --- whilst keeping the
+existing Material colors:
+
+
+```kotlin
+@Immutable
+data class ExtendedColors(
+ val caution: Color,
+ val onCaution: Color
+)
+
+val LocalExtendedColors = staticCompositionLocalOf {
+ ExtendedColors(
+ caution = Color.Unspecified,
+ onCaution = Color.Unspecified
+ )
+}
+
+@Composable
+fun ExtendedTheme(
+ /* ... */
+ content: @Composable () -> Unit
+) {
+ val extendedColors = ExtendedColors(
+ caution = Color(0xFFFFCC02),
+ onCaution = Color(0xFF2C2D30)
+ )
+ CompositionLocalProvider(LocalExtendedColors provides extendedColors) {
+ MaterialTheme(
+ /* colors = ..., typography = ..., shapes = ... */
+ content = content
+ )
+ }
+}
+
+// Use with eg. ExtendedTheme.colors.caution
+object ExtendedTheme {
+ val colors: ExtendedColors
+ @Composable
+ get() = LocalExtendedColors.current
+}
+```
+
+
+
+This is similar to `MaterialTheme` usage APIs. It also supports multiple themes
+as you can nest `ExtendedTheme`s in the same way as `MaterialTheme`.
+
+### Use Material components
+
+When extending Material Theming, existing `MaterialTheme` values are maintained
+and Material components still have reasonable defaults.
+
+If you want to use extended values in components, wrap them in your own
+composable functions, directly setting the values you want to alter, and
+exposing others as parameters to the containing composable:
+
+
+```kotlin
+@Composable
+fun ExtendedButton(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ content: @Composable RowScope.() -> Unit
+) {
+ Button(
+ colors = ButtonDefaults.buttonColors(
+ containerColor = ExtendedTheme.colors.caution,
+ contentColor = ExtendedTheme.colors.onCaution
+ /* Other colors use values from MaterialTheme */
+ ),
+ onClick = onClick,
+ modifier = modifier,
+ content = content
+ )
+}
+```
+
+
+
+You would then replace usages of `Button` with `ExtendedButton` where
+appropriate.
+
+
+```kotlin
+@Composable
+fun ExtendedApp() {
+ ExtendedTheme {
+ /*...*/
+ ExtendedButton(onClick = { /* ... */ }) {
+ /* ... */
+ }
+ }
+}
+```
+
+
+
+## Replace Material subsystems
+
+Instead of extending Material Theming, you may want to replace one or more
+systems --- `Colors`, `Typography`, or `Shapes` --- with a custom implementation,
+while maintaining the others.
+
+Suppose you want to replace the type and shape systems while keeping the color
+system:
+
+
+```kotlin
+@Immutable
+data class ReplacementTypography(
+ val body: TextStyle,
+ val title: TextStyle
+)
+
+@Immutable
+data class ReplacementShapes(
+ val component: Shape,
+ val surface: Shape
+)
+
+val LocalReplacementTypography = staticCompositionLocalOf {
+ ReplacementTypography(
+ body = TextStyle.Default,
+ title = TextStyle.Default
+ )
+}
+val LocalReplacementShapes = staticCompositionLocalOf {
+ ReplacementShapes(
+ component = RoundedCornerShape(ZeroCornerSize),
+ surface = RoundedCornerShape(ZeroCornerSize)
+ )
+}
+
+@Composable
+fun ReplacementTheme(
+ /* ... */
+ content: @Composable () -> Unit
+) {
+ val replacementTypography = ReplacementTypography(
+ body = TextStyle(fontSize = 16.sp),
+ title = TextStyle(fontSize = 32.sp)
+ )
+ val replacementShapes = ReplacementShapes(
+ component = RoundedCornerShape(percent = 50),
+ surface = RoundedCornerShape(size = 40.dp)
+ )
+ CompositionLocalProvider(
+ LocalReplacementTypography provides replacementTypography,
+ LocalReplacementShapes provides replacementShapes
+ ) {
+ MaterialTheme(
+ /* colors = ... */
+ content = content
+ )
+ }
+}
+
+// Use with eg. ReplacementTheme.typography.body
+object ReplacementTheme {
+ val typography: ReplacementTypography
+ @Composable
+ get() = LocalReplacementTypography.current
+ val shapes: ReplacementShapes
+ @Composable
+ get() = LocalReplacementShapes.current
+}
+```
+
+
+
+### Use Material components
+
+When one or more systems of `MaterialTheme` have been replaced, using Material
+components as-is may result in unwanted Material color, type, or shape values.
+
+If you want to use replacement values in components, wrap them in your own
+composable functions, directly setting the values for the relevant system, and
+exposing others as parameters to the containing composable.
+
+> [!NOTE]
+> **Note:** Not all values may be exposed as parameters in Material composables, in particular with `CompositionLocal` composables (such as `LocalTextStyle`). In such cases you may need to wrap `content` lambdas in provider functions (like `ProvideTextStyle`).
+
+
+```kotlin
+@Composable
+fun ReplacementButton(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ content: @Composable RowScope.() -> Unit
+) {
+ Button(
+ shape = ReplacementTheme.shapes.component,
+ onClick = onClick,
+ modifier = modifier,
+ content = {
+ ProvideTextStyle(
+ value = ReplacementTheme.typography.body
+ ) {
+ content()
+ }
+ }
+ )
+}
+```
+
+
+
+You would then replace usages of `Button` with `ReplacementButton` where
+appropriate.
+
+
+```kotlin
+@Composable
+fun ReplacementApp() {
+ ReplacementTheme {
+ /*...*/
+ ReplacementButton(onClick = { /* ... */ }) {
+ /* ... */
+ }
+ }
+}
+```
+
+
+
+## Implement a fully custom design system
+
+You may want to replace Material Theming with a fully custom design system.
+Consider that `MaterialTheme` provides the following systems:
+
+- `Colors`, `Typography`, and `Shapes`: Material Theming systems
+- `TextSelectionColors`: Colors used for text selection by `Text` and `TextField`
+- `Ripple` and `RippleTheme`: Material implementation of `Indication`
+
+If you want to continue using Material components, you must replace some of
+these systems in your custom themes or handle the systems in your
+components to avoid unwanted behavior.
+
+However, design systems are not limited to the concepts Material relies on. You
+can modify existing systems and introduce entirely new ones --- with new classes
+and types --- to make other concepts compatible with themes.
+
+In the following code, we model a custom color system that includes gradients
+(`List`), include a type system, introduce a new elevation system,
+and exclude other systems provided by `MaterialTheme`:
+
+
+
+
+```kotlin
+@Immutable
+data class CustomColors(
+ val content: Color,
+ val component: Color,
+ val background: List
+)
+
+@Immutable
+data class CustomTypography(
+ val body: TextStyle,
+ val title: TextStyle
+)
+
+@Immutable
+data class CustomElevation(
+ val default: Dp,
+ val pressed: Dp
+)
+
+val LocalCustomColors = staticCompositionLocalOf {
+ CustomColors(
+ content = Color.Unspecified,
+ component = Color.Unspecified,
+ background = emptyList()
+ )
+}
+val LocalCustomTypography = staticCompositionLocalOf {
+ CustomTypography(
+ body = TextStyle.Default,
+ title = TextStyle.Default
+ )
+}
+val LocalCustomElevation = staticCompositionLocalOf {
+ CustomElevation(
+ default = Dp.Unspecified,
+ pressed = Dp.Unspecified
+ )
+}
+
+@Composable
+fun CustomTheme(
+ /* ... */
+ content: @Composable () -> Unit
+) {
+ val customColors = CustomColors(
+ content = Color(0xFFDD0D3C),
+ component = Color(0xFFC20029),
+ background = listOf(Color.White, Color(0xFFF8BBD0))
+ )
+ val customTypography = CustomTypography(
+ body = TextStyle(fontSize = 16.sp),
+ title = TextStyle(fontSize = 32.sp)
+ )
+ val customElevation = CustomElevation(
+ default = 4.dp,
+ pressed = 8.dp
+ )
+ CompositionLocalProvider(
+ LocalCustomColors provides customColors,
+ LocalCustomTypography provides customTypography,
+ LocalCustomElevation provides customElevation,
+ content = content
+ )
+}
+
+// Use with eg. CustomTheme.elevation.small
+object CustomTheme {
+ val colors: CustomColors
+ @Composable
+ get() = LocalCustomColors.current
+ val typography: CustomTypography
+ @Composable
+ get() = LocalCustomTypography.current
+ val elevation: CustomElevation
+ @Composable
+ get() = LocalCustomElevation.current
+}
+```
+
+
+
+### Use Material components
+
+When no `MaterialTheme` is present, using Material components as-is will result
+in unwanted Material color, type, and shape values and indication behavior.
+
+If you want to use custom values in components, wrap them in your own composable
+functions, directly setting the values for the relevant system, and exposing
+others as parameters to the containing composable.
+
+We recommend that you access values you set from your custom theme.
+Alternatively, if your theme doesn't provide `Color`, `TextStyle`, `Shape`, or
+other systems, you can hardcode them.
+
+
+```kotlin
+@Composable
+fun CustomButton(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ content: @Composable RowScope.() -> Unit
+) {
+ Button(
+ colors = ButtonDefaults.buttonColors(
+ containerColor = CustomTheme.colors.component,
+ contentColor = CustomTheme.colors.content,
+ disabledContainerColor = CustomTheme.colors.content
+ .copy(alpha = 0.12f)
+ .compositeOver(CustomTheme.colors.component),
+ disabledContentColor = CustomTheme.colors.content
+ .copy(alpha = 0.38f)
+
+ ),
+ shape = ButtonShape,
+ elevation = ButtonDefaults.elevatedButtonElevation(
+ defaultElevation = CustomTheme.elevation.default,
+ pressedElevation = CustomTheme.elevation.pressed
+ /* disabledElevation = 0.dp */
+ ),
+ onClick = onClick,
+ modifier = modifier,
+ content = {
+ ProvideTextStyle(
+ value = CustomTheme.typography.body
+ ) {
+ content()
+ }
+ }
+ )
+}
+
+val ButtonShape = RoundedCornerShape(percent = 50)
+```
+
+
+
+> [!NOTE]
+> **Note:** `Button` uses `rememberRipple()` internally to provide a `Ripple` `Indication`. It's a good idea to check the source code when implementing other custom components that wrap existing components.
+
+If you've introduced new class types --- such as `List` to represent
+gradients --- then it may be better to implement components from scratch instead
+of wrapping them. For an example, take a look at
+[`JetsnackButton`](https://github.com/android/compose-samples/blob/main/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Button.kt)
+from the Jetsnack sample.
+
+## Recommended for you
+
+- Note: link text is displayed when JavaScript is off
+- [Material Design 3 in Compose](https://developer.android.com/develop/ui/compose/designsystems/material3)
+- [Migrate from Material 2 to Material 3 in Compose](https://developer.android.com/develop/ui/compose/designsystems/material2-material3)
+- [Anatomy of a theme in Compose](https://developer.android.com/develop/ui/compose/designsystems/anatomy)
\ No newline at end of file
diff --git a/jetpack-compose/theming/styles/references/android/develop/ui/compose/styles/fundamentals.md b/jetpack-compose/theming/styles/references/android/develop/ui/compose/styles/fundamentals.md
new file mode 100644
index 0000000..ab0d26d
--- /dev/null
+++ b/jetpack-compose/theming/styles/references/android/develop/ui/compose/styles/fundamentals.md
@@ -0,0 +1,421 @@
+There are three ways you can adopt Styles throughout your app:
+
+1. Use directly on existing components that expose a [`Style`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/style/Style) parameter.
+2. Apply a style with [`Modifier.styleable`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/style/styleable.modifier#(androidx.compose.ui.Modifier).styleable(androidx.compose.foundation.style.StyleState,androidx.compose.foundation.style.Style)) on layout composables that don't accept a `Style` parameter.
+3. In your own custom design system, use `Modifier.styleable{}` and expose a style parameter on your own components.
+
+## Available properties on Styles
+
+Styles support many of the same properties that modifiers support; however, not
+everything that is a modifier can be replicated with a Style. You still need
+modifiers for certain behaviors, like interactions, custom drawing, or stacking
+of properties.
+
+| Grouping | Properties | Inherited by children |
+|---|---|---|
+| **Layout and sizing** | | |
+| Content Padding (inner) | - `contentPadding(all: Dp)` - `contentPadding(horizontal: Dp, vertical: Dp)` - `contentPadding(start: Dp, top: Dp, end: Dp, bottom: Dp)` - `contentPaddingHorizontal(value: Dp)` / `contentPaddingVertical(value: Dp)` - `contentPaddingStart(value: Dp)` / `contentPaddingTop(value: Dp)` / `contentPaddingEnd(value: Dp)` / `contentPaddingBottom(value: Dp)` | No |
+| External Padding (outer) | - `externalPadding(all: Dp)` - `externalPadding(horizontal: Dp, vertical: Dp)` - `externalPadding(start: Dp, top: Dp, end: Dp, bottom: Dp)` - `externalPaddingHorizontal(value: Dp)` / `externalPaddingVertical(value: Dp)` - `externalPaddingStart(value: Dp)` / `externalPaddingTop(value: Dp)` / `externalPaddingEnd(value: Dp)` / `externalPaddingBottom(value: Dp)` | No |
+| Dimensions | `fillWidth()/fillHeight()/fillSize()` and `width`, `height`, and `size` (supports `Dp`, `DpSize`, or `Float` fractions). | No |
+| Positioning | `left/top/right/bottom` offsets. | No |
+| **Visual Appearance** | | |
+| Fills | `background` and `foreground` (supports `Color` or `Brush`). | No |
+| Borders | `borderWidth`, `borderColor`, and `borderBrush`. | No |
+| Shape | `shape` | No - but used in conjunction with other properties. `clip` and `border` use this defined shape. |
+| Shadows | `dropShadow`, `innerShadow` | No |
+| **Transformations** | | |
+| Graphics layer spatial movement | `translationX`, `translationY`, `scaleX/scaleY`, `rotationX/rotationY/rotationZ` | No |
+| Control | `alpha`, `zIndex` (stacking order), and `transformOrigin` (pivot point) | No |
+| **Typography** | | |
+| Styling | `textStyle`, `fontSize`, `fontWeight`, `fontStyle`, and `fontFamily` | Yes |
+| Coloration | `contentColor` and `contentBrush`. This is also used for Icons styling. | Yes |
+| Paragraph | `lineHeight`, `letterSpacing`, `textAlign`, `textDirection`, `lineBreak`, and `hyphens`. | Yes |
+| Decoration | `textDecoration`, `textIndent`, and `baselineShift`. | Yes |
+
+## Use Styles directly on components with Style parameters
+
+Components that expose a `Style` parameter allow you to set their styling:
+
+
+```kotlin
+BaseButton(
+ onClick = { },
+ style = { }
+) {
+ BaseText("Click me")
+}
+```
+
+
+
+Within the style lambda, you can set various properties, such as `externalPadding`
+or `background`:
+
+
+```kotlin
+BaseButton(
+ onClick = { },
+ style = { background(Color.Blue) }
+) {
+ BaseText("Click me")
+}
+```
+
+
+
+For the full list of supported properties, see [Available properties on
+Styles](https://developer.android.com/develop/ui/compose/styles/fundamentals#properties-styles).
+
+## Apply Styles using modifiers for components with no existing parameter
+
+For components that lack a built-in style parameter, you can still apply styles
+with the `styleable` modifier. This approach is also useful when developing your
+own custom components.
+
+
+```kotlin
+Row(
+ modifier = Modifier.styleable { }
+) {
+ BaseText("Content")
+}
+```
+
+
+
+Similar to the `style` parameter, you can include properties like `background`,
+`contentPadding`, or `externalPadding` inside the lambda.
+
+
+```kotlin
+Row(
+ modifier = Modifier.styleable {
+ background(Color.Blue)
+ }
+) {
+ BaseText("Content")
+}
+```
+
+
+
+> [!NOTE]
+> **Note:** When using `Modifier.styleable`, the child composables won't have those properties applied to them, unless they are inherited properties. Only the container with the `styleable` modifier has those properties applied.
+
+Multiple chained `Modifier.styleable` modifiers are additive with non-inherited
+properties on the applied composable, behaving similarly to multiple modifiers
+defining the same properties. For inherited properties, these are overridden,
+and the last `styleable` modifier in the chain sets the values.
+
+When using `Modifier.styleable`, you may also want to create and supply a
+`StyleState` to be used with the modifier to apply state-based styling. For more
+details, see [State and animations with
+Styles](https://developer.android.com/develop/ui/compose/styles/state-animations).
+
+## Define a standalone Style
+
+You can define a standalone Style for reusability purposes:
+
+
+```kotlin
+val style = Style { background(Color.Blue) }
+```
+
+
+
+You can then pass that defined style into a composable's style parameter or with
+`Modifier.styleable`. When using `Modifier.styleable`, you also need to create a
+`StyleState` object. `StyleState` is covered in detail in the [State and
+animations with Styles](https://developer.android.com/develop/ui/compose/styles/state-animations) documentation.
+
+The following example shows how you can apply a Style either directly through a
+component's built-in parameters, or through a `Modifier.styleable`:
+
+
+```kotlin
+val style = Style { background(Color.Blue) }
+
+// built in parameter
+BaseButton(onClick = { }, style = style) {
+ BaseText("Button")
+}
+
+// modifier styleable
+val styleState = remember { MutableStyleState(null) }
+Column(
+ Modifier.styleable(styleState, style)
+) {
+ BaseText("Column content")
+}
+```
+
+
+
+You can also pass that Style into multiple components:
+
+
+```kotlin
+val style = Style { background(Color.Blue) }
+
+// built in parameter
+BaseButton(onClick = { }, style = style) {
+ BaseText("Button")
+}
+BaseText("Different text that uses the same style parameter", style = style)
+
+// modifier styleable
+val columnStyleState = remember { MutableStyleState(null) }
+Column(
+ Modifier.styleable(columnStyleState, style)
+) {
+ BaseText("Column")
+}
+val rowStyleState = remember { MutableStyleState(null) }
+Row(
+ Modifier.styleable(rowStyleState, style)
+) {
+ BaseText("Row")
+}
+```
+
+
+
+## Add multiple Style properties
+
+You can add multiple Style properties by setting different properties on each
+line:
+
+
+```kotlin
+BaseButton(
+ onClick = { },
+ style = {
+ background(Color.Blue)
+ contentPaddingStart(16.dp)
+ }
+) {
+ BaseText("Button")
+}
+```
+
+
+
+> [!IMPORTANT]
+> **Important:** Unlike modifier-based styling, properties in Styles override one another; the last property defined takes precedence.
+
+Properties in Styles are not additive, unlike modifier-based styling. Styles
+take the last set value in the list of properties within one style block. In the
+following example, with the background set twice, the `TealColor` is the applied
+background. For padding, `contentPaddingTop` overrides the top
+padding set by `contentPadding` and does not combine the values.
+
+
+```kotlin
+BaseButton(
+ style = {
+ background(Color.Red)
+ // Background of Red is now overridden with TealColor instead
+ background(TealColor)
+ // All directions of padding are set to 64.dp (top, start, end, bottom)
+ contentPadding(64.dp)
+ // Top padding is now set to 16.dp, all other paddings remain at 64.dp
+ contentPaddingTop(16.dp)
+ },
+ onClick = {
+ //
+ }
+) {
+ BaseText("Click me!")
+}
+```
+
+
+
+ **Figure 1.** Button with two background colors set and two `contentPadding` overrides.
+
+## Merge multiple style objects
+
+You can create multiple Style objects and pass them into the style parameter of
+your composable.
+
+
+```kotlin
+val style1 = Style { background(TealColor) }
+val style2 = Style { contentPaddingTop(16.dp) }
+
+BaseButton(
+ style = style1 then style2,
+ onClick = {
+
+ },
+) {
+ BaseText("Click me!")
+}
+```
+
+
+
+ **Figure 2.** Button with background color and `contentPaddingTop` set.
+
+When multiple Styles specify the same property, the last set
+property is chosen. Because properties are not additive in Styles, the last
+padding passed in overrides the `contentPaddingHorizontal` set by the initial
+`contentPadding`. Additionally, the last background color overrides the
+background color set by the initial style passed in.
+
+
+```kotlin
+val style1 = Style {
+ background(Color.Red)
+ contentPadding(32.dp)
+}
+
+val style2 = Style {
+ contentPaddingHorizontal(8.dp)
+ background(Color.LightGray)
+}
+
+BaseButton(
+ style = style1 then style2,
+ onClick = {
+
+ },
+) {
+ BaseText("Click me!")
+}
+```
+
+
+
+In this case, the styling applied has a light gray background and `32.dp` padding,
+except for the left and right padding, which has a value of `8.dp`.
+ **Figure 3.** Button with `contentPadding` that's overridden by different Styles.
+
+## Style inheritance
+
+> [!NOTE]
+> **Note:** While the Style APIs are experimental, you need to opt-in to enable Style inheritance by setting the flag `ComposeFoundationFlags.isInheritedTextStyleEnabled = true`.
+
+Certain style properties, such as `contentColor` and text style-related
+properties, propagate to the child composables. A style set on a child
+composable overrides the inherited parent styling for that specific child.
+ **Figure 4.** Style propagation with `Style`, `styleable`, and direct parameters.
+
+| Priority | Method | Effect |
+|---|---|---|
+| 1 (Highest) | Direct arguments on a composable | Overrides everything; for example, `Text(color = Color.Red)` |
+| 2 | Style parameter | Local style overrides `Text(style = Style { contentColor(Color.Red)}` |
+| 3 | Modifier chain | `Modifier.styleable{ contentColor(Color.Red)` on the component itself. |
+| 4 (Lowest) | Parent styles | For properties that can be inherited (Typography/Color) passed down from the parent. |
+
+> [!NOTE]
+> **Note:** Multiple chained `Modifier.styleable` modifiers are additive with non-inherited properties on the applied composable, similar to having multiple modifiers defining the same properties. For inherited properties, these are overridden; the last `styleable` modifier in the chain sets the values.
+
+### Parent styling
+
+You can set text properties (such as `contentColor`) from the parent composable,
+and they propagate to all child `Text` composables.
+
+
+```kotlin
+val styleState = remember { MutableStyleState(null) }
+Column(
+ modifier = Modifier.styleable(styleState) {
+ background(Color.LightGray)
+ val blue = Color(0xFF4285F4)
+ val purple = Color(0xFFA250EA)
+ val colors = listOf(blue, purple)
+ contentBrush(Brush.linearGradient(colors))
+ },
+) {
+ BaseText("Children inherit", style = { width(60.dp) })
+ BaseText("certain properties")
+ BaseText("from their parents")
+}
+```
+
+
+
+ **Figure 5.** Child composables' property inheritance.
+
+### Child override of properties
+
+You can also set styling on a specific `Text` composable. If the parent composable
+has styling set, the styling set on the child composable overrides the
+parent composable's styling.
+
+
+```kotlin
+val styleState = remember { MutableStyleState(null) }
+Column(
+ modifier = Modifier.styleable(styleState) {
+ background(Color.LightGray)
+ val blue = Color(0xFF4285F4)
+ val purple = Color(0xFFA250EA)
+ val colors = listOf(blue, purple)
+ contentBrush(Brush.linearGradient(colors))
+ },
+) {
+ BaseText("Children can ", style = {
+ contentBrush(Brush.linearGradient(listOf(Color.Red, Color.Blue)))
+ })
+ BaseText("override properties")
+ BaseText("set by their parents")
+}
+```
+
+
+
+ **Figure 6.** Child composables override parent properties.
+
+## Implement custom Style properties
+
+You can create custom properties that map to existing Style definitions by using
+extension functions on the `StyleScope`, as shown in the following example:
+
+
+```kotlin
+fun StyleScope.outlinedBackground(color: Color) {
+ border(1.dp, color)
+ background(color)
+}
+```
+
+
+
+Apply this new property within a Style definition:
+
+
+```kotlin
+val customExtensionStyle = Style {
+ outlinedBackground(Color.Blue)
+}
+```
+
+
+
+Creating new styleable properties is unsupported. If your use case
+requires such support, submit a [feature request](https://issuetracker.google.com/issues/new?component=612128).
+
+## Read `CompositionLocal` values
+
+It's a common pattern to store design system tokens within a `CompositionLocal`,
+to access the variables without needing to pass them as parameters. Styles
+can access `CompositionLocal`s to retrieve system-wide values within a style:
+
+
+```kotlin
+val buttonStyle = Style {
+ contentPadding(12.dp)
+ shape(RoundedCornerShape(50))
+ background(Brush.verticalGradient(LocalCustomColors.currentValue.background))
+}
+```
+
+
\ No newline at end of file
diff --git a/jetpack-compose/theming/styles/references/android/develop/ui/compose/styles/state-animations.md b/jetpack-compose/theming/styles/references/android/develop/ui/compose/styles/state-animations.md
new file mode 100644
index 0000000..01683bd
--- /dev/null
+++ b/jetpack-compose/theming/styles/references/android/develop/ui/compose/styles/state-animations.md
@@ -0,0 +1,461 @@
+
+
+The Styles API offers a declarative and streamlined approach to managing UI
+changes during interaction states like `hovered`, `focused`, and `pressed`. With
+this API, you can significantly decrease the boilerplate code typically required
+when using modifiers.
+
+To facilitate reactive styling, `StyleState` acts as a stable, read-only
+interface that tracks the active state of an element (such as its enabled,
+pressed, or focused status). Within a `StyleScope`, you can access this through
+the `state` property to implement conditional logic directly in your Style
+definitions.
+
+## State-based interaction: Hovered, focused, pressed, selected, enabled, toggled
+
+Styles come with built-in support for common interactions:
+
+- Pressed
+- Hovered
+- Selected
+- Enabled
+- Toggled
+
+It's also possible to support custom states. See the [Custom State Styling with
+StyleState](https://developer.android.com/develop/ui/compose/styles/state-animations#custom-state) section for more information.
+
+### Handle interaction states with Style parameters
+
+The following example demonstrates modifying the `background` and `borderColor`
+in response to interaction states, specifically switching to purple when hovered
+and blue when focused:
+
+
+```kotlin
+@Preview
+@Composable
+private fun OpenButton() {
+ BaseButton(
+ style = outlinedButtonStyle then {
+ background(Color.White)
+ hovered {
+ background(lightPurple)
+ border(2.dp, lightPurple)
+ }
+ focused {
+ background(lightBlue)
+ }
+ },
+ onClick = { },
+ content = {
+ BaseText("Open in Studio", style = {
+ contentColor(Color.Black)
+ fontSize(26.sp)
+ textAlign(TextAlign.Center)
+ })
+ }
+ )
+}
+```
+
+
+
+**Figure 1.** Changing background color based on hovered and focused states.
+
+You can also create nested state definitions. For example, you can define a
+specific style for when a button is being both pressed and hovered
+simultaneously:
+
+
+```kotlin
+@Composable
+private fun OpenButton_CombinedStates() {
+ BaseButton(
+ style = outlinedButtonStyle then {
+ background(Color.White)
+ hovered {
+ // light purple
+ background(lightPurple)
+ pressed {
+ // When running on a device that can hover, whilst hovering and then pressing the button this would be invoked
+ background(lightOrange)
+ }
+ }
+ pressed {
+ // when running on a device without a mouse attached, this would be invoked as you wouldn't be in a hovered state only
+ background(lightRed)
+ }
+ focused {
+ background(lightBlue)
+ }
+ },
+ onClick = { },
+ content = {
+ BaseText("Open in Studio", style = {
+ contentColor(Color.Black)
+ fontSize(26.sp)
+ textAlign(TextAlign.Center)
+ })
+ }
+ )
+}
+```
+
+
+
+**Figure 2.** Hovered and pressed state together on a button.
+
+### Custom composables with Modifier.styleable
+
+When creating your own `styleable` components, you must connect an
+`interactionSource` to a `styleState`. Then, pass this state into
+`Modifier.styleable` to utilize it.
+
+Consider a scenario where your design system includes a `GradientButton`. You
+may want to create a `LoginButton` that inherits from `GradientButton`, but
+alters its colors during interactions, like being pressed.
+
+- To enable `interactionSource` style updates, include an `interactionSource` as a parameter within your composable. Use the provided parameter or, if one is not supplied, initialize a new `MutableInteractionSource`.
+- Initialize the `styleState` by providing the `interactionSource`. Make sure the `styleState`'s enabled status reflects the value of the provided enabled parameter.
+- Assign the `interactionSource` to the `focusable` and `clickable` modifiers. Finally, apply the `styleState` to the modifier's `styleable` parameter.
+
+
+```kotlin
+@Composable
+private fun GradientButton(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ style: Style = Style,
+ enabled: Boolean = true,
+ interactionSource: MutableInteractionSource? = null,
+ content: @Composable RowScope.() -> Unit,
+) {
+ val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
+ val styleState = rememberUpdatedStyleState(interactionSource) {
+ it.isEnabled = enabled
+ }
+ Row(
+ modifier =
+ modifier
+ .clickable(
+ onClick = onClick,
+ enabled = enabled,
+ interactionSource = interactionSource,
+ indication = null,
+ )
+ .styleable(styleState, baseGradientButtonStyle then style),
+ content = content,
+ )
+}
+```
+
+
+
+You can now use the `interactionSource` state to drive style modifications with
+the pressed, focused, and hovered options inside the style block:
+
+
+```kotlin
+@Preview
+@Composable
+fun LoginButton() {
+ val loginButtonStyle = Style {
+ pressed {
+ background(
+ Brush.linearGradient(
+ listOf(Color.Magenta, Color.Red)
+ )
+ )
+ }
+ }
+ GradientButton(onClick = {
+ // Login logic
+ }, style = loginButtonStyle) {
+ BaseText("Login")
+ }
+}
+```
+
+
+
+**Figure 3.** Changing a custom composable state based on `interactionSource`.
+
+## Animate style changes
+
+Styles state changes come with built-in animation support. You can wrap the new
+property within any state change block with `animate` to automatically add
+animations between different states. This is similar to the `animate*AsState`
+APIs. The following example animates the `borderColor` from black to blue when
+the state changes to focused:
+
+
+```kotlin
+val animatingStyle = Style {
+ externalPadding(48.dp)
+ border(3.dp, Color.Black)
+ background(Color.White)
+ size(100.dp)
+
+ pressed {
+ animate {
+ borderColor(Color.Magenta)
+ background(Color(0xFFB39DDB))
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun AnimatingStyleChanges() {
+ val interactionSource = remember { MutableInteractionSource() }
+ val styleState = remember(interactionSource) { MutableStyleState(interactionSource) }
+ Box(modifier = Modifier
+ .clickable(
+ interactionSource,
+ enabled = true,
+ indication = null,
+ onClick = {
+
+ }
+ )
+ .styleable(styleState, animatingStyle)) {
+
+ }
+}
+```
+
+
+
+**Figure 4.** Animating color changes on press.
+
+The `animate` API accepts an `animationSpec` to change the duration or shape of
+the animation curve. The following example animates the size of the box with a
+`spring` spec:
+
+
+```kotlin
+val animatingStyleSpec = Style {
+ externalPadding(48.dp)
+ border(3.dp, Color.Black)
+ background(Color.White)
+ size(100.dp)
+ transformOrigin(TransformOrigin.Center)
+ pressed {
+ animate {
+ borderColor(Color.Magenta)
+ background(Color(0xFFB39DDB))
+ }
+ animate(spring(dampingRatio = Spring.DampingRatioMediumBouncy)) {
+ scale(1.2f)
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun AnimatingStyleChangesSpec() {
+ val interactionSource = remember { MutableInteractionSource() }
+ val styleState = remember(interactionSource) { MutableStyleState(interactionSource) }
+ Box(modifier = Modifier
+ .clickable(
+ interactionSource,
+ enabled = true,
+ indication = null,
+ onClick = {
+
+ }
+ )
+ .styleable(styleState, animatingStyleSpec))
+}
+```
+
+
+
+**Figure 5.** Animating size and color changes on press.
+
+## Custom state styling with StyleState
+
+Depending on your composable use case, you may have different styles that are
+backed by custom states. For example, if you have a media app, you may want to
+have different styling for the buttons in your `MediaPlayer` composable
+depending on the playback state of the player. Follow these steps to create and
+use your own custom state:
+
+1. Define custom key
+2. Create `StyleState` extension
+3. Link to custom state
+
+### Define custom key
+
+To create a custom state-based style, first create a
+[`StyleStateKey`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/style/StyleStateKey) and pass in the default state value. When the
+app launches, the media player is in the `Stopped` state, so it's initialized in
+this way:
+
+
+```kotlin
+enum class PlayerState {
+ Stopped,
+ Playing,
+ Paused
+}
+
+val playerStateKey = StyleStateKey(PlayerState.Stopped)
+```
+
+
+
+### Create StyleState extension functions
+
+Define an extension function on `StyleState` to query the current `playState`.
+Then, create extension functions on `StyleScope` with your custom states passing
+in the `playStateKey`, a lambda with the specific state, and the style.
+
+
+```kotlin
+// Extension Function on MutableStyleState to query and set the current playState
+var MutableStyleState.playerState
+ get() = this[playerStateKey]
+ set(value) { this[playerStateKey] = value }
+
+fun StyleScope.playerPlaying(block: () -> Unit) {
+ state(playerStateKey, block, { key, state -> state[key] == PlayerState.Playing })
+}
+fun StyleScope.playerPaused(block: () -> Unit) {
+ state(playerStateKey, block, { key, state -> state[key] == PlayerState.Paused })
+}
+```
+
+
+
+### Link to custom state
+
+Define the `styleState` in your composable and set the `styleState.playState`
+equal to incoming state. Pass `styleState` into the `styleable` function on the
+modifier.
+
+
+```kotlin
+@Composable
+fun MediaPlayer(
+ url: String,
+ modifier: Modifier = Modifier,
+ style: Style = Style,
+ state: PlayerState = remember { PlayerState.Paused }
+) {
+ // Hoist style state, set playstate as a parameter,
+ val styleState = remember { MutableStyleState(null) }
+ // Set equal to incoming state to link the two together
+ styleState.playerState = state
+ Box(
+ modifier = modifier.styleable(styleState, style)) {
+ ///..
+ }
+}
+```
+
+
+
+Within the `style` lambda, you can apply state-based styling for custom states,
+using the previously defined extension functions.
+
+
+```kotlin
+@Composable
+fun StyleStateKeySample() {
+ // Using the extension function to change the border color to green while playing
+ val style = Style {
+ borderColor(Color.Gray)
+ playerPlaying {
+ animate {
+ borderColor(Color.Green)
+ }
+ }
+ playerPaused {
+ animate {
+ borderColor(Color.Blue)
+ }
+ }
+ }
+ val styleState = remember { MutableStyleState(null) }
+ styleState[playerStateKey] = PlayerState.Playing
+
+ // Using the style in a composable that sets the state -> notice if you change the state parameter, the style changes. You can link this up to an ViewModel and change the state from there too.
+ MediaPlayer(url = "https://example.com/media/video",
+ style = style,
+ state = PlayerState.Stopped)
+}
+```
+
+
+
+The following code is the full snippet for this example:
+
+
+```kotlin
+enum class PlayerState {
+ Stopped,
+ Playing,
+ Paused
+}
+val playerStateKey = StyleStateKey(PlayerState.Stopped)
+var MutableStyleState.playerState
+ get() = this[playerStateKey]
+ set(value) { this[playerStateKey] = value }
+
+fun StyleScope.playerPlaying(block: () -> Unit) {
+ state(playerStateKey, block, { key, state -> state[key] == PlayerState.Playing })
+}
+fun StyleScope.playerPaused(block: () -> Unit) {
+ state(playerStateKey, block, { key, state -> state[key] == PlayerState.Paused })
+
+}
+
+@Composable
+fun MediaPlayer(
+ url: String,
+ modifier: Modifier = Modifier,
+ style: Style = Style,
+ state: PlayerState = remember { PlayerState.Paused }
+) {
+ // Hoist style state, set playstate as a parameter,
+ val styleState = remember { MutableStyleState(null) }
+ // Set equal to incoming state to link the two together
+ styleState.playerState = state
+ Box(
+ modifier = modifier.styleable(styleState, Style {
+ size(100.dp)
+ border(2.dp, Color.Red)
+
+ }, style, )) {
+
+ ///..
+ }
+}
+@Composable
+fun StyleStateKeySample() {
+ // Using the extension function to change the border color to green while playing
+ val style = Style {
+ borderColor(Color.Gray)
+ playerPlaying {
+ animate {
+ borderColor(Color.Green)
+ }
+ }
+ playerPaused {
+ animate {
+ borderColor(Color.Blue)
+ }
+ }
+ }
+ val styleState = remember { MutableStyleState(null) }
+ styleState[playerStateKey] = PlayerState.Playing
+
+ // Using the style in a composable that sets the state -> notice if you change the state parameter, the style changes. You can link this up to an ViewModel and change the state from there too.
+ MediaPlayer(url = "https://example.com/media/video",
+ style = style,
+ state = PlayerState.Stopped)
+}
+```
+
+
\ No newline at end of file
diff --git a/jetpack-compose/theming/styles/references/android/develop/ui/compose/styles/styles-vs-modifiers.md b/jetpack-compose/theming/styles/references/android/develop/ui/compose/styles/styles-vs-modifiers.md
new file mode 100644
index 0000000..cd9bc50
--- /dev/null
+++ b/jetpack-compose/theming/styles/references/android/develop/ui/compose/styles/styles-vs-modifiers.md
@@ -0,0 +1,48 @@
+Styles differ from modifiers by design. Styles don't replace modifiers; instead,
+the two systems coexist with different goals. Internally, a Style is a modifier.
+You can do everything Styles can do with modifiers, but not all functionality in
+modifiers is available in Styles.
+**Important:**
+
+- **Choose Styles if:** You need to override a default of an existing component, perform high-performance animations, or define a theme-wide set of properties for a component.
+- **Choose Modifiers if:** You need to add behavior (for example, clickable, gestures), define unique one-off layouts, or need additive properties.
+
+The following is a comparison between Styles versus modifiers:
+
+| Feature | Modifiers | Styles |
+|---|---|---|
+| **Primary Goal** | Define behaviors, semantics, and complex layouts. Modifiers manipulate individual elements on the fly for a particular composable and don't trickle down from the theme. | Define visual appearance, individual item sizing and themeable properties. Styles operate at a theme level and are over-writeable at a component level. They trickle down and apply styling across different composables. |
+| **Logic** | Additive - the modifiers combine together to form a new result. | Over-writable - the last property set in the Style wins. Styles act as a single layer of properties that override each other based on a defined precedence hierarchy. |
+| **Theming** | Challenging to lift into a theme, normally used individually. | By design, Styles are themeable (they can access `CompositionLocal`s) and can be defined once and used across components. |
+| **Performance** | Updates often require all three phases of Compose: composition, layout and draw. Achieving good animation performance of modifiers often requires writing lambda-based versions. | Skips composition phase, only active in layout and draw phase, reducing recompositions. Requires less object allocation. |
+| **Animations** | Requires using separate animation primitives like `animate*AsState` | Features built-in `animate { }` API that handles some animations for you. |
+
+## Limitations of modifiers
+
+Modifiers have many benefits in the current Compose landscape. However, Styles
+address some limitations of modifiers, which the following list describes:
+
+- Modifiers are typically created in the Composition phase. Updates can force a full rerun of Composition, Layout, and Draw, even for small visual changes like color, unless you create lambda-based modifiers.
+- Conditional modifiers require disruptive if-else logic within fluent chains. Animating them requires manual state boilerplate and lacks a high-performance "auto-animate" mechanism.
+- Modifiers stack rather than replace. You can't override a component's default border; you can only draw a second one on top.
+- Modifiers are difficult to abstract into global themes. Consequently, themes usually store raw values instead of reusable modifier configurations.
+
+## Limitations of Styles
+
+While Styles can fill in some of the gaps that modifiers have, they also have
+some limitations, which show how they cannot entirely replace modifiers:
+
+- Styles are specialized Modifiers. While a modifier can do anything a Style does, the reverse is not true. Consequently, Styles can supplement, but cannot replace, modifiers.
+- Styles are limited to visual configuration (backgrounds, padding, borders). They cannot handle behaviors like click logic, gesture detection, or accessibility semantics.
+- Resolving a Style into its final state is *more expensive than applying a
+ single modifier*. The system must generate a data structure containing all possible property values, and the lookup of inherited properties further complicates this.
+
+## When to use Styles over modifiers
+
+While the choice to use Styles is largely dependent on your app and use cases,
+the following guidance helps determine when to prefer a style over a modifier:
+
+- **To achieve theme-wide consistency:** Styles are designed to be "lifted" into a global theme. Instead of passing repetitive Modifiers to every component, you can define a single Style in your theme to create a unified look across the entire app.
+- **When performing frequent animations:** Styles evaluate during the Layout and Draw phases, allowing properties like color or scale to animate while bypassing the Composition phase entirely. This significantly reduces performance overhead. Use a Style instead of a modifier when doing visual property animations.
+- **Overriding vs. stacking:** Use Styles when you need to replace a default property. Modifiers are additive (adding a border stacks a second one), whereas Styles use "last-write-wins" logic, making it easier to swap out backgrounds or padding without visual clutter.
+- **Customizing Material components:** If a Material component provides a Style parameter, it is the suggested approach for customization. These styles allow you to access and modify specific properties within the composable's internal structure that might otherwise be inaccessible.
\ No newline at end of file
diff --git a/jetpack-compose/theming/styles/references/android/develop/ui/compose/styles/theming.md b/jetpack-compose/theming/styles/references/android/develop/ui/compose/styles/theming.md
new file mode 100644
index 0000000..6b551d0
--- /dev/null
+++ b/jetpack-compose/theming/styles/references/android/develop/ui/compose/styles/theming.md
@@ -0,0 +1,269 @@
+> [!NOTE]
+> **Note:** Styles are `@Experimental` and likely to change in upcoming releases, with Material support for Styles added in future releases. If you have any feedback, [file Styles issues](https://issuetracker.google.com/issues/new?component=612128).
+
+There are several ways you can build out your apps using Styles. What you choose
+depends on where your app sits in relation to its adoption of Material Design:
+
+1. Fully custom design system, not using Material Design
+ - **Recommendation**: Define component styles that consume values from the theme, and expose style parameters on design system components.
+2. Using Material Design
+ - **Recommendation**: Await Material adoption to integrate with Styles. Use styles on your own components where possible.
+
+
+## Android skills
+
+[View on GitHub](https://github.com/android/skills/blob/main/jetpack-compose/theming/styles)
+
+### Use the Jetpack Compose Styles API
+
+Use the `styles` [Android skill](https://developer.android.com/tools/agents/android-skills) to create and customize components with the Styles API. To install the skill from the [Android CLI](https://developer.android.com/tools/agents/android-cli), run:
+
+ android skills add styles
+
+
+
+## The Style layer
+
+In the traditional Compose model, customization often relies heavily on
+overriding global tokens (colors and typography) provided by `MaterialTheme`, or
+wrapping and overriding properties of a design system composable where possible.
+Sometimes, there are properties within the Material layer that are not exposed
+through the subsystems or parameters, but are hardcoded defaults on the
+component itself.
+
+With the Styles API, there's a new layer of abstraction that's a bridge between
+subsystems and components: **Styles**.
+
+| Layer | Responsibility | Example |
+|---|---|---|
+| **Subsystem values** | Named values | `val Primary = Color(0xFF34A85E)` |
+| **Atomic Styles** | Style that does exactly one property change | `val largeSizeAtomic = Style { size(100.dp, 40.dp) }` |
+| **Component Styles** | Component-specific configurations | A Button with Primary background and 16dp padding. `val buttonStyle = Style { contentPadding(16.dp) shape(RoundedCornerShape(8.dp)) background(Color.Blue) }` |
+| **Components** | The functional UI element that consumes a Style. | `Button(style = buttonStyle) { ... }` |
+
+ **Figure 1.** An example of a component and how it accesses styles from a theme.
+
+### Atomic versus monolithic Styles
+
+With the Styles API, you can break down a Style into separate atomic styles.
+Instead of defining complex, component-specific styles like `baseButtonStyle`,
+you can also create small, single-purpose utility styles. These act as your
+"atoms".
+
+
+```kotlin
+// Define single-purpose "atomic" styles
+val paddingAtomic = Style {
+ contentPadding(16.dp)
+}
+val roundedCornerShapeAtomic = Style {
+ shape(RoundedCornerShape(8.dp))
+}
+val primaryBackgroundAtomic = Style {
+ background(Color.Blue)
+}
+val largeSizeAtomic = Style {
+ size(100.dp, 40.dp)
+}
+val interactiveShadowAtomic = Style {
+ hovered {
+ animate {
+ dropShadow(
+ Shadow(
+ offset = DpOffset(
+ 0.dp,
+ 0.dp
+ ),
+ radius = 2.dp,
+ spread = 0.dp,
+ color = Color.Blue,
+ )
+ )
+ }
+ }
+}
+```
+
+
+
+#### Composition using "then"
+
+The `then` operator in the Styles API lets you merge multiple `Style` objects.
+This lets you build a component using atomic utility classes.
+
+**Traditional (non-atomic)**:
+
+
+```kotlin
+// One large monolithic style
+val buttonStyle = Style {
+ contentPadding(16.dp)
+ shape(RoundedCornerShape(8.dp))
+ background(Color.Blue)
+}
+```
+
+
+
+**Atomic refactor**:
+
+
+```kotlin
+// Combine atoms to create the final appearance
+val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then interactiveShadowAtomic
+```
+
+
+
+## Adopt Styles in your design system
+
+Consider the following options when adopting Styles within your design system,
+depending on where in the spectrum your design system lies.
+
+### Custom design system with Styles
+
+***Consider when**: You've been handed an extensive brand guide that is not
+based on Material Design, and you are not planning to use Material Design*.
+
+***Strategy**: Implement a fully custom design system, and expose styles as part
+of the theme*.
+
+This option is the custom path if you don't use Material as your main design
+system language. You bypass `MaterialTheme` entirely for visual definitions and
+have created your [own custom theme already](https://developer.android.com/develop/ui/compose/designsystems/custom#implementing-fully-custom). You build a `CompanyTheme` that
+acts as a container for your Styles.
+
+- **How it works** : Create a `CompanyTheme` object that holds `Style` objects for every component in your system. Your components (either wrappers around Material logic or custom `Box` or `Layout` implementations) consume these styles directly, and expose a `Style` parameter for consumers of your design system.
+- **The Style layer**: Styles are the primary definition of your design system. Tokens are named variables fed into these styles. This allows for deep customization, such as defining unique animations for state changes (for example, animating scale and color on press).
+
+If you are building out your own [custom theme](https://developer.android.com/develop/ui/compose/designsystems/custom) without using Material, and
+want to adopt styles, add your list of styles to your Theme. This lets you
+access your base styles from anywhere in your project.
+
+1. Create a `Styles` class that stores the various styles in your application
+ and create the defaults. For example, in the Jetsnack app - the class is
+ named `JetsnackStyles`:
+
+
+ ```kotlin
+ object JetsnackStyles{
+ val buttonStyle: Style = Style {
+ shape(shapes.medium)
+ background(colors.brand)
+ contentColor(colors.textPrimary)
+ contentPaddingVertical(8.dp)
+ contentPaddingHorizontal(24.dp)
+ textStyle(typography.labelLarge)
+ disabled {
+ animate {
+ background(colors.brandSecondary)
+ }
+ }
+ }
+ val cardStyle: Style = Style {
+ shape(shapes.medium)
+ background(colors.uiBackground)
+ contentColor(colors.textPrimary)
+ }
+ }
+ ```
+
+
+
+2. Provide `Styles` as part of your overall theme, and expose helper extension
+ functions on `StyleScope` to access the subsystems:
+
+
+ ```kotlin
+ @Immutable
+ class JetsnackTheme(
+ val colors: JetsnackColors = LightJetsnackColors,
+ val typography: androidx.compose.material3.Typography = androidx.compose.material3.Typography(),
+ val shapes: Shapes = Shapes()
+ ) {
+ companion object {
+ val colors: JetsnackColors
+ @Composable @ReadOnlyComposable
+ get() = LocalJetsnackTheme.current.colors
+
+ val typography: androidx.compose.material3.Typography
+ @Composable @ReadOnlyComposable
+ get() = LocalJetsnackTheme.current.typography
+
+ val shapes: Shapes
+ @Composable @ReadOnlyComposable
+ get() = LocalJetsnackTheme.current.shapes
+
+ val styles: JetsnackStyles = JetsnackStyles
+
+ val LocalJetsnackTheme: ProvidableCompositionLocal
+ get() = LocalJetsnackThemeInstance
+ }
+ }
+
+ val StyleScope.colors: JetsnackColors
+ get() = LocalJetsnackTheme.currentValue.colors
+
+ val StyleScope.typography: androidx.compose.material3.Typography
+ get() = LocalJetsnackTheme.currentValue.typography
+
+ val StyleScope.shapes: Shapes
+ get() = LocalJetsnackTheme.currentValue.shapes
+
+ internal val LocalJetsnackThemeInstance = staticCompositionLocalOf { JetsnackTheme() }
+
+ @Composable
+ fun JetsnackTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
+ val colors = if (darkTheme) DarkJetsnackColors else LightJetsnackColors
+ val theme = JetsnackTheme(colors = colors)
+
+ CompositionLocalProvider(
+ LocalJetsnackTheme provides theme,
+ ) {
+ MaterialTheme(
+ typography = LocalJetsnackTheme.current.typography,
+ shapes = LocalJetsnackTheme.current.shapes,
+ content = content,
+ )
+ }
+ }
+ ```
+
+
+
+3. Access `JetsnackStyles` within your composable:
+
+
+ ```kotlin
+ @Composable
+ fun CustomButton(modifier: Modifier,
+ style: Style = Style,
+ text: String) {
+ val interactionSource = remember { MutableInteractionSource() }
+ val styleState = remember(interactionSource) { MutableStyleState(interactionSource) }
+
+ // Apply style to top level container in combination with incoming style from parameter.
+ Box(modifier = modifier
+ .clickable(
+ interactionSource = interactionSource,
+ indication = null,
+ enabled = true,
+ role = Role.Button,
+ onClick = {
+
+ },
+ )
+ .styleable(styleState, JetsnackTheme.styles.buttonStyle, style)) {
+ Text(text)
+ }
+ }
+ ```
+
+
+
+Beyond global theme adoption, there are alternative strategies for incorporating
+`Styles` into your apps. You can use `Styles` inline for specific call sites or
+use static definitions when full theming capabilities aren't necessary.
+`Styles` shouldn't be swapped conditionally unless the whole style is
+fundamentally different. Prefer accessing dynamic tokens inside a visual
+definition rather than switching between distinct style objects.
\ No newline at end of file
diff --git a/media/media3-cast-integration/SKILL.md b/media/media3-cast-integration/SKILL.md
new file mode 100644
index 0000000..d4ce98a
--- /dev/null
+++ b/media/media3-cast-integration/SKILL.md
@@ -0,0 +1,233 @@
+---
+name: media3-cast-integration
+description: Implements Google Cast support in Android apps using Jetpack Media3.
+ Handles adding build dependencies, updating manifest, configuring OptionsProvider,
+ and managing CastPlayer or RemoteCastPlayer for playback in both Compose and View-based
+ UIs. Use when adding Cast functionality or migrating from legacy Cast SDK to Media3
+ Cast.
+license: Complete terms in LICENSE.txt
+metadata:
+ author: Google LLC
+ last-updated: '2026-09-16'
+ keywords:
+ - Android
+ - Media3
+ - Cast
+ - Integration
+ - Migration
+ - ExoPlayer
+ - MediaSession
+ - Jetpack Media3
+---
+
+## Prerequisites
+
+- Jetpack Media3 version must be `>= 1.9.0`. Cast isn't available in lower versions.
+
+## Glossary
+
+- **`CastPlayer`** : Media3 `Player` that controls playback on both local and remote Cast devices.
+- **`RemoteCastPlayer`** : Media3 `Player` that communicates with a Cast receiver, only used for remote playback.
+- **Google Cast SDK**: Legacy casting SDK in maintenance mode, superseded by Jetpack Media3.
+- **`OptionsProvider`** : Interface providing configuration options to initialize GMS `CastContext`.
+
+## Common guidelines
+
+- Legacy [Google Cast SDK](https://developers.google.com/cast) is in maintenance mode.
+- For new Cast setups:
+ - You must use [Jetpack Media3 Cast](references/android/media/media3/cast/index.md).
+ - You mustn't use legacy Cast SDK unless explicitly requested.
+
+## Step 1: Set up dependencies
+
+To complete this step, you **MUST** ensure the following:
+
+- In the app-level build file, declare the `media3-cast` dependency version
+ 1.9.0 or higher.
+
+ implementation("androidx.media3:media3-cast:1.11.1")
+
+- Ensure required Media3 dependencies are present:
+
+ - `androidx.media3:media3-exoplayer`
+ - `androidx.media3:media3-session`
+ - `androidx.media3:media3-ui-compose`
+- If the application uses legacy Views, add `media3-ui`.
+
+- Enforce the same versions across all Media3 dependencies.
+
+- Use configurations in "Add build dependencies" section of [Getting started
+ with CastPlayer](references/android/media/media3/cast/create-castplayer.md) as the source of
+ truth.
+
+- **For apps without an existing Cast integration:**
+
+ - Verify legacy Cast SDK (`libs.play.services.cast.framework`) is absent.
+- **If Migrating from Legacy Cast SDK:**
+
+ - Add Media3 Cast dependencies first.
+ - Keep existing legacy dependencies untouched at this stage to prevent compilation errors.
+
+## Step 2: Update the manifest
+
+To complete this step, you **MUST** ensure the following:
+
+- Inside the manifest's `` tag, declare the Cast options provider.
+- Use `DefaultCastOptionsProvider` by default. See the "OptionsProvider" section in [Getting started with
+ CastPlayer](references/android/media/media3/cast/create-castplayer.md).
+- Declare a custom `OptionsProvider` only if explicitly requested. See [Customize CastOptions](references/android/media/media3/cast/customize-castoptions.md).
+- Ensure `INTERNET` permission is present. Don't add any unnecessary permissions.
+- **If Migrating from Legacy Cast SDK:**
+ - Don't delete existing custom options provider files or manifest entries.
+
+## Step 3: Implement the player and service
+
+### Architecture baseline
+
+Before integrating Media3 Cast, an existing app follows one of two setups:
+
+- **Local-only playback:** Uses Media3 `ExoPlayer` only to support local playback.
+- **Legacy Cast setup:** Uses `ExoPlayer` for local playback, alongside a `Player` wrapper over the legacy `RemoteMediaClient` for remote playback. The UI interfaces with a `MediaSession` interacting with a `ForwardingPlayer`, which finally routes controls to either local or remote playback.
+
+To complete this step, you **MUST** ensure the following:
+
+- Inside the application's `MediaSessionService` (or `MediaLibraryService`) `onCreate()` method, initialize `ExoPlayer` and `CastPlayer`.
+- Use `CastPlayer` by default unless `RemoteCastPlayer` is explicitly requested. See the "Build a CastPlayer" section in [Getting started with
+ CastPlayer](references/android/media/media3/cast/create-castplayer.md).
+- For `CastPlayer`, pass the instance directly to `MediaSession.Builder`.
+- Replace all legacy forwarding player wrappers.
+- Don't delete legacy class files yet to prevent compilation errors during migration.
+
+### Advanced: `RemoteCastPlayer`
+
+- Use `RemoteCastPlayer` only if explicitly requested by user.
+- Initialize `MediaSession` with `localPlayer` and set a `SessionAvailabilityListener` on `RemoteCastPlayer` to transfer playback state on Cast session availability changes:
+
+ class PlaybackService : MediaSessionService() {
+ private var mediaSession: MediaSession? = null
+ private lateinit var localPlayer: ExoPlayer
+ private lateinit var remotePlayer: RemoteCastPlayer
+
+ override fun onCreate() {
+ super.onCreate()
+
+ localPlayer = ExoPlayer.Builder(this).build()
+ remotePlayer = RemoteCastPlayer.Builder(this).build()
+ mediaSession = MediaSession.Builder(this, localPlayer).build()
+
+ remotePlayer.setSessionAvailabilityListener(
+ object : SessionAvailabilityListener {
+ override fun onCastSessionAvailable() {
+ transferPlaybackState(localPlayer, remotePlayer)
+ }
+
+ override fun onCastSessionUnavailable() {
+ transferPlaybackState(remotePlayer, localPlayer)
+ }
+ }
+ )
+ }
+
+ private fun transferPlaybackState(previousPlayer: Player, newPlayer: Player) {
+ if (previousPlayer.mediaItemCount > 0) {
+ val transferStateBuilder = PlayerTransferState.builderFromPlayer(previousPlayer)
+ if (previousPlayer.playbackState == Player.STATE_ENDED ||
+ previousPlayer.currentPosition == C.TIME_END_OF_SOURCE) {
+ transferStateBuilder.setCurrentMediaItemIndex(0)
+ transferStateBuilder.setCurrentPosition(0)
+ }
+ transferStateBuilder.build().setToPlayer(newPlayer)
+ }
+
+ previousPlayer.stop()
+ previousPlayer.clearMediaItems()
+ newPlayer.prepare()
+ mediaSession?.setPlayer(newPlayer)
+ }
+ }
+
+## Step 4: Set up the UI
+
+### Compose-based UI
+
+To complete this step, you **MUST** ensure the following:
+
+- See the "Add a MediaRouteButton Composable to the Player" section in [Getting started with CastPlayer](references/android/media/media3/cast/create-castplayer.md) for Compose integration guidelines.
+- Use the [`MediaRouteButton` composable](https://developer.android.com/reference/kotlin/androidx/media3/cast/MediaRouteButton.composable) from `androidx.media3.cast` package.
+- Don't use `AndroidView` in the Compose UI hierarchy.
+- Place `MediaRouteButton` in an area next to playback controls. Don't hide it behind system UI.
+- Don't use `PlayerSurface` for custom player UI. Use the Material3 [`Player`
+ composable](https://developer.android.com/reference/kotlin/androidx/media3/ui/compose/material3/Player.composable).
+- Force recomposition on playback location shifts to ensure UI sync. Use key
+ constraints on `DeviceInfo` changes:
+
+ @OptIn(UnstableApi::class)
+ @Composable
+ fun MainScreen() {
+ val player = rememberMediaController()
+ val deviceInfo = rememberDeviceInfo(player)
+ player?.let { activePlayer -> key(deviceInfo) { PlayerScreen(player = activePlayer) } }
+ }
+
+ @Composable
+ private fun rememberMediaController(): Player? {
+ // Logic to connect MediaController to MediaSession and release it
+ }
+
+ @Composable
+ private fun rememberDeviceInfo(player: Player?): DeviceInfo? {
+ var deviceInfo by remember(player) { mutableStateOf(player?.deviceInfo) }
+ DisposableEffect(player) {
+ val activePlayer = player ?: return@DisposableEffect onDispose {}
+ deviceInfo = activePlayer.deviceInfo
+ val listener = object : Player.Listener {
+ override fun onDeviceInfoChanged(info: DeviceInfo) {
+ deviceInfo = info
+ }
+ }
+ activePlayer.addListener(listener)
+ onDispose { activePlayer.removeListener(listener) }
+ }
+ return deviceInfo
+ }
+
+### View-based UI
+
+To complete this step, you **MUST** ensure the following:
+
+- For View-based UI setups, see the "Add UI elements" section in [Getting
+ started with CastPlayer](references/android/media/media3/cast/create-castplayer.md).
+- Casting Activities must extend `AppCompatActivity` or `FragmentActivity` and use a `Theme.AppCompat` descendant.
+- Ensure the `AppCompat` theme has a visible `ActionBar` if adding `MediaRouteButton` to the options menu.
+- Replace all instances and imports of `CastButtonFactory` with `MediaRouteButtonFactory`.
+- Rebind `PlayerView.player` references upon `onDeviceInfoChanged` events to
+ prevent black screens or UI freezes:
+
+ private val playerListener: Player.Listener =
+ object : Player.Listener {
+ override fun onDeviceInfoChanged(deviceInfo: DeviceInfo) {
+ // Resetting to null bypasses PlayerView.setPlayer()'s instance equality check
+ // (this.player == player), forcing it to re-bind the video surface to the controller.
+ playerView.player = null
+ playerView.player = controller
+ }
+ }
+
+- **Migration to Compose:**
+
+ - Don't use `AndroidView` to wrap the legacy `PlayerView`.
+ - Implement Material3 [`Player` composable](https://developer.android.com/reference/kotlin/androidx/media3/ui/compose/material3/Player.composable) and [`MediaRouteButton` composable](https://developer.android.com/reference/kotlin/androidx/media3/cast/MediaRouteButton.composable) as per [Getting started with CastPlayer](references/android/media/media3/cast/create-castplayer.md).
+ - Remove legacy XML layout declarations, menu files, and View component references.
+
+## Step 5: Clean up legacy Cast SDK code
+
+> [!WARNING]
+> **Warning:** Don't perform cleanup directly. Remove legacy files and dependencies only when explicitly requested by the user.
+
+To complete this step, you **MUST** ensure the following:
+
+- Remove legacy GMS Cast SDK (`libs.play.services.cast.framework`) and MediaRouter (`libs.androidx.mediarouter`) dependencies.
+- Delete custom `OptionsProvider` classes and manifest entries if `DefaultCastOptionsProvider` is adopted.
+- Remove legacy `MediaTransferReceiver` manifest declarations if present.
+- Remove all references to legacy Cast SDK components such as legacy helper wrappers, forwarding players, and `RemoteMediaClient` interfaces.
+- Delete legacy View XML layouts, menu files, and references to `PlayerView` if the migration to Compose is complete.
diff --git a/media/media3-cast-integration/references/android/media/media3/cast/create-castplayer.md b/media/media3-cast-integration/references/android/media/media3/cast/create-castplayer.md
new file mode 100644
index 0000000..0099bc5
--- /dev/null
+++ b/media/media3-cast-integration/references/android/media/media3/cast/create-castplayer.md
@@ -0,0 +1,390 @@
+The [`CastPlayer`](https://developer.android.com/reference/androidx/media3/cast/CastPlayer) is a Jetpack Media3 [Player](https://developer.android.com/reference/kotlin/androidx/media3/common/Player) implementation that supports
+both local playback and casting to a remote Cast-enabled device. [`CastPlayer`](https://developer.android.com/reference/androidx/media3/cast/CastPlayer)
+simplifies adding cast functionality to your app and provides rich features to
+seamlessly switch between local and remote playback. This guide shows you how to
+integrate [`CastPlayer`](https://developer.android.com/reference/androidx/media3/cast/CastPlayer) into your media app.
+
+To integrate Cast with other platforms, see the [Cast SDK](https://developers.google.com/cast/docs/developers).
+
+## Get a Cast-enabled device
+
+To test `CastPlayer`, you need a [Cast-enabled device](https://store.google.com/gb/category/connected_home?hl=en-GB). Choices include Android
+TV, Chromecast, smart speakers, and smart displays. Verify that your device is
+set up and connected to the same Wi-Fi network as your development mobile for
+discovery.
+
+## Add build dependencies
+
+To start using `CastPlayer`, add the AndroidX Media3 and `CastPlayer`
+dependencies to the `build.gradle` file of your app module.
+
+### Kotlin
+
+ implementation("androidx.media3:media3-exoplayer:1.11.1")
+ implementation("androidx.media3:media3-ui:1.11.1")
+ implementation("androidx.media3:media3-session:1.11.1")
+ implementation("androidx.media3:media3-cast:1.11.1")
+
+### Groovy
+
+ implementation "androidx.media3:media3-exoplayer:1.11.1"
+ implementation "androidx.media3:media3-ui:1.11.1"
+ implementation "androidx.media3:media3-session:1.11.1"
+ implementation "androidx.media3:media3-cast:1.11.1"
+
+## Configure your CastPlayer
+
+To configure the [`CastPlayer`](https://developer.android.com/reference/androidx/media3/cast/CastPlayer), update your `AndroidManifest.xml` file with an
+options provider.
+
+### Options provider
+
+The [`CastPlayer`](https://developer.android.com/reference/androidx/media3/cast/CastPlayer) requires an options provider to configure its behavior. For a
+basic setup, you can use the [`DefaultCastOptionsProvider`](https://developer.android.com/reference/androidx/media3/cast/DefaultCastOptionsProvider) by adding it to your
+`AndroidManifest.xml` file. This uses default settings, including the default
+receiver application.
+
+
+ ...
+
+ ...
+
+
+To customize the configuration, implement your own custom `OptionsProvider`. See
+the [CastOptions](https://developer.android.com/media/media3/cast/customize-castoptions) guide to learn how.
+
+### Add a receiver for media transfers
+
+Adding a `MediaTransferReceiver` to your manifest enables the System UI to
+discover Cast-enabled devices on the network and reroute media without opening
+the app activity. For example, a user can change the device playing your app's
+media from the [media notification](https://developer.android.com/media/implement/surfaces/mobile).
+
+
+ ...
+
+ ...
+
+
+## Build a CastPlayer
+
+For remote playback with Cast, your app should be able to manage playback even
+when the user isn't interacting with an Activity from your app, such as through
+the system media notification. For this reason, you should create your
+`ExoPlayer` (for local playback) and `CastPlayer` (for remote playback)
+instances in a service, such as [MediaSessionService](https://developer.android.com/media/media3/session/background-playback#service-lifecycle) or [MediaLibraryService](https://developer.android.com/guide/topics/media/session/medialibraryservice).
+First, create your `ExoPlayer` instance and then when building your
+[`CastPlayer`](https://developer.android.com/reference/androidx/media3/cast/CastPlayer) instance, set `ExoPlayer` as the local player instance. You can
+then switch media playback between your mobile and the Cast-enabled device from
+the media notification or the lock screen notification. Media3 uses the **Output
+Switcher** feature to handle player transfers when the output route changes from
+local to remote or from remote to local.
+ Figure 1: (a) Device chip on Media notification (b) Cast-enabled devices shown on tapping the device chip (c) Device chip on Lock screen notification
+
+
+### Kotlin
+
+```kotlin
+override fun onCreate() {
+ super.onCreate()
+
+ val exoPlayer = ExoPlayer.Builder(context).build()
+ val castPlayer = CastPlayer.Builder(context).setLocalPlayer(exoPlayer).build()
+
+ mediaSession = MediaSession.Builder(context, castPlayer).build()
+}
+```
+
+### Java
+
+```java
+@Override
+public void onCreate() {
+ super.onCreate();
+
+ ExoPlayer exoPlayer = new ExoPlayer.Builder(context).build();
+ CastPlayer castPlayer = new CastPlayer.Builder(context).setLocalPlayer(exoPlayer).build();
+
+ mediaSession =
+ new MediaSession.Builder(/* context= */ context, /* player= */ castPlayer).build();
+}
+```
+
+
+
+> [!IMPORTANT]
+> **Important:** The preceding code snippet shows the `onCreate` method of a `MediaSessionService`. In an `Activity`, players should be created in `onStart` or `onResume`.
+
+## Add UI elements
+
+Add a [`MediaRouteButton`](https://developer.android.com/reference/androidx/mediarouter/app/MediaRouteButton) to your app's UI. Tapping the [`MediaRouteButton`](https://developer.android.com/reference/androidx/mediarouter/app/MediaRouteButton)
+opens a dialog displaying a list of available Cast-enabled devices on the
+network. When the user selects a device, the media playback is transferred from
+the mobile to the selected receiver device. This section shows you how to add
+the button and listen for events to update your UI when playback switches
+between local and remote devices.
+
+### Set the MediaRouteButton
+
+There are four ways to add the [`MediaRouteButton`](https://developer.android.com/reference/androidx/mediarouter/app/MediaRouteButton) to your activity's UI. The
+best choice depends on your app's design and requirements.
+
+- **Compose UI**: Add a button composable.
+- **Views UI** :
+ - Add the button to the app bar menu.
+ - Add the button inside `PlayerView`.
+ - Add the button as a standard `View`.
+
+ Figure 2: (a) MediaRouteButton in menu bar, (b) as a View, (c) in PlayerView, and (d) Dialog of Cast-enabled devices.
+
+> [!IMPORTANT]
+> **Important:** To use the [`MediaRouteButton`](https://developer.android.com/reference/androidx/mediarouter/app/MediaRouteButton) in Views UI, the containing activity must be a subclass of `FragmentActivity`.
+
+> [!TIP]
+> **Tip:** These screenshots are from the [demo-session](https://github.com/androidx/media/tree/release/demos/session) apps in Media3. You can checkout the app to see an example of `CastPlayer` implementation.
+
+#### Add a Composable `MediaRouteButton` to the Player
+
+You can add the [`MediaRouteButton`](https://developer.android.com/reference/androidx/mediarouter/app/MediaRouteButton) Composable to your player's UI. For more
+information, see the [Compose](https://developer.android.com/media/media3/ui/compose) guide.
+
+
+```kotlin
+@Composable
+fun PlayerComposeView(player: Player, modifier: Modifier = Modifier) {
+ var controlsVisible by remember { mutableStateOf(false) }
+
+ Box(
+ modifier = modifier.clickable { controlsVisible = true },
+ contentAlignment = Alignment.Center,
+ ) {
+ PlayerSurface(player = player, modifier = modifier)
+ AnimatedVisibility(visible = controlsVisible, enter = fadeIn(), exit = fadeOut()) {
+ Box(modifier = Modifier.fillMaxSize()) {
+ MediaRouteButton(modifier = Modifier.align(Alignment.TopEnd))
+ PrimaryControls(player = player, modifier = Modifier.align(Alignment.Center))
+ }
+ }
+ }
+}
+
+@Composable
+fun PrimaryControls(player: Player, modifier: Modifier = Modifier) {
+ // ...
+}
+```
+
+
+
+#### Add the `MediaRouteButton` to the PlayerView
+
+You can add the [`MediaRouteButton`](https://developer.android.com/reference/androidx/mediarouter/app/MediaRouteButton) directly within the [PlayerView](https://developer.android.com/guide/topics/media/ui/playerview)'s UI
+controls. After setting the [MediaController](https://developer.android.com/guide/topics/media/session/mediacontroller) as the player for your
+`PlayerView`, provide a `MediaRouteButtonViewProvider` to display the Cast
+button on the Player.
+
+
+### Kotlin
+
+```kotlin
+override fun onStart() {
+ super.onStart()
+
+ playerView.player = mediaController
+ playerView.setMediaRouteButtonViewProvider(MediaRouteButtonViewProvider())
+}
+```
+
+### Java
+
+```java
+@Override
+public void onStart() {
+ super.onStart();
+
+ playerView.setPlayer(mediaController);
+ playerView.setMediaRouteButtonViewProvider(new MediaRouteButtonViewProvider());
+}
+```
+
+
+
+#### Add the `MediaRouteButton` to the app bar menu
+
+To set up a [`MediaRouteButton`](https://developer.android.com/reference/androidx/mediarouter/app/MediaRouteButton) in the app bar menu, create an XML menu and
+override `onCreateOptionsMenu` in your `Activity`.
+
+
+
+
+### Kotlin
+
+```kotlin
+override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ // ...
+ menuInflater.inflate(R.menu.sample_media_route_button_menu, menu)
+ val menuItemFuture: ListenableFuture