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..15e0b2d
--- /dev/null
+++ b/.claude-plugin/plugin.json
@@ -0,0 +1,46 @@
+{
+ "name": "android-skills",
+ "version": "1.0.14",
+ "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",
+ "./device-ai/appfunctions",
+ "./device-ai/ml-kit-genai-prompt-api",
+ "./devtools/android-cli",
+ "./identity/restore-credentials",
+ "./identity/verified-email",
+ "./jetpack-compose/adaptive",
+ "./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",
+ "./testing/testing-setup",
+ "./tv/leanback-to-compose-tv-migration",
+ "./wear/wear-compose-m3",
+ "./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..5b67b40
--- /dev/null
+++ b/.codex-plugin/plugin.json
@@ -0,0 +1,159 @@
+{
+ "name": "android-skills",
+ "version": "1.0.14",
+ "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": "./device-ai/appfunctions"
+ }
+ },
+ {
+ "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/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": "./testing/testing-setup"
+ }
+ },
+ {
+ "source": {
+ "path": "./tv/leanback-to-compose-tv-migration"
+ }
+ },
+ {
+ "source": {
+ "path": "./wear/wear-compose-m3"
+ }
+ },
+ {
+ "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..3963a12
--- /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-09-24'
+ 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/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/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..0f8eb7c
--- /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-09-30'
+ 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..2f54571
--- /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-alpha03"
+
+ [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..dc5bd59
--- /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-alpha03"
+
+ [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/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