diff --git a/jetpack-compose/adaptive/SKILL.md b/jetpack-compose/adaptive/SKILL.md index 02f9394..0f8eb7c 100644 --- a/jetpack-compose/adaptive/SKILL.md +++ b/jetpack-compose/adaptive/SKILL.md @@ -10,7 +10,7 @@ description: Instructions to make or update an app's UI so that it adapts to dif license: Complete terms in LICENSE.txt metadata: author: Google LLC - last-updated: '2026-09-22' + last-updated: '2026-09-30' keywords: - android - ui @@ -43,7 +43,7 @@ task. 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/develop/ui/compose/tooling/debug.md). Use the following annotation to create previews for all the major form +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: 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 index ddda0de..64bd05f 100644 --- 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 @@ -1,6 +1,3 @@ -> [!NOTE] -> **Note:** FlexBox is an experimental API and is likely to change in the future. To use it, annotate your code with `@ExperimentalFlexBoxApi`. Please file any issues or feedback on the [issue tracker](https://issuetracker.google.com/issues/new?component=1876021&title=%5BFlexBox%5D). - [`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 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 index 316b046..0789e46 100644 --- 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 @@ -1,6 +1,3 @@ -> [!NOTE] -> **Note:** `Grid` is an experimental API and is subject to change. File any issues on the [issue tracker](https://issuetracker.google.com/issues/new?component=1876021&template=1424126). - [`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 diff --git a/jetpack-compose/adaptive/references/android/develop/ui/compose/tooling/debug.md b/jetpack-compose/adaptive/references/android/develop/ui/compose/tooling/debug.md deleted file mode 100644 index 023836f..0000000 --- a/jetpack-compose/adaptive/references/android/develop/ui/compose/tooling/debug.md +++ /dev/null @@ -1,114 +0,0 @@ -Tools for debugging your Compose UI are available in Android Studio. - -## Layout Inspector - -Layout Inspector lets you inspect a Compose layout inside a running app in an -emulator or physical device. You can use the Layout Inspector to check how often -a composable is recomposed or skipped, which can help identify issues with your -app. For example, some coding errors might force your UI to recompose -excessively, which can cause [poor performance](https://developer.android.com/develop/ui/compose/performance). -Some coding errors can prevent your UI from recomposing and, therefore, -prevent your UI changes from showing up on the screen. If you're new to -Layout inspector, check the [guidance](https://developer.android.com/studio/debug/layout-inspector) on how to -run it. - -> [!NOTE] -> **Note:** If you're not seeing Compose components in layout inspector, make sure you are not removing `META-INF/androidx.compose.*.version` files from the APK. These are required for layout inspector to work. - -### Get recomposition counts - -When debugging your Compose layouts, knowing when composables -[recompose](https://developer.android.com/develop/ui/compose/mental-model#recomposition) is important in -understanding whether your UI is implemented properly. For example, if it's -recomposing too many times, your app might be doing more work than is necessary. -On the other hand, components that don't recompose when you anticipate them to -can lead to unexpected behaviors. - -The Layout Inspector shows you when discrete composables in your layout -hierarchy have either recomposed or skipped, as you interact with your app. In -Android Studio, your recompositions are highlighted to help you determine -where in the UI your composables are recomposing. - -**Figure 1.** Recompositions are highlighted in Layout Inspector. - -The highlighted portion shows a gradient overlay of the composable in the image -section of the Layout Inspector, and gradually disappears so that you can get an -idea of where in the UI the composable with the highest recompositions can be -found. If one composable is recomposing at a higher rate than another -composable, then the first composable receives a stronger gradient overlay -color. If you double-click a composable in the layout inspector, you're taken to -the corresponding code for analysis. - -> [!NOTE] -> **Note:** To view recomposition counts, make sure your app is using an API level of 29 or higher, and `Compose 1.2.0` or higher. Then, deploy your app as you normally would. - -![](https://developer.android.com/static/develop/ui/compose/images/li-recomposition-counts.png) **Figure 2.**The composition and skip counter in Layout Inspector. - -Open the **Layout Inspector** window and connect to your app process. In the -**Component Tree** , there are two columns that appear next to the layout -hierarchy. The first column shows the number of compositions for each node and -the second column displays the number of skips for each node. Selecting a -composable node shows the dimensions and parameters of the composable, unless -it's an inline function, in which case the parameters can't be shown. You can -also see similar information in the **Attributes** pane when you select a -composable from the **Component Tree** or the **Layout Display**. - -Resetting the count can help you understand recompositions or skips during a -specific interaction with your app. If you want to reset the count, click -**Reset** near the top of the **Component Tree** pane. - -> [!NOTE] -> **Note:** If you don't see the new columns in the **Component Tree** pane, you can view them by selecting **Show Recomposition Counts** from the **View Options** menu ![Layout Inspector View Options -> icon](https://developer.android.com/static/studio/images/buttons/live-layout-inspector-view-options-icon.png) near the top of the **Component Tree** pane, as shown in the following image. - -![Enable the composition and skip counter in Layout -Inspector](https://developer.android.com/static/develop/ui/compose/images/li-show-recomposition-counts.png) - -**Figure 3**. Enable the composition and skip counter in Layout Inspector. - -### Compose semantics - -In Compose, [Semantics](https://developer.android.com/develop/ui/compose/accessibility/semantics) describe your UI in an -alternative manner that is understandable for -[Accessibility](https://developer.android.com/develop/ui/compose/accessibility) services and for the -[Testing](https://developer.android.com/develop/ui/compose/testing) framework. You can use the Layout Inspector -to inspect semantic information in your Compose layouts. -![Semantic information displayed using the Layout Inspector.](https://developer.android.com/static/develop/ui/compose/images/layout_inspector_semantics_new.png) **Figure 4.** Semantic information displayed using the Layout Inspector. - -When selecting a Compose node, use the **Attributes** pane to check whether it -declares semantic information directly, merges semantics from its children, or -both. To quickly identify which nodes include semantics, either declared or -merged, use select the **View options** drop-down in the **Component Tree** pane -and select **Highlight Semantics Layers**. This highlights only the nodes in the -tree that include semantics, and you can use your keyboard to quickly navigate -between them. - -## Compose UI Check - -To help you build more adaptive and accessible UIs in Jetpack Compose, Android -Studio provides a UI Check mode in Compose Preview. This feature is similar -to [Accessibility Scanner](https://developer.android.com/guide/topics/ui/accessibility/testing#accessibility-scanner) -for views. - -When you activate Compose UI check mode on a Compose Preview, Android Studio -automatically audits your Compose UI and suggests improvements to make your UI -more accessible and adaptive. Android Studio checks that your UI works across -different screen sizes. In the **Problems** panel, the tool shows the issues -that it detects, such as text stretched on large screens or low color contrast. - -To access this feature, click the UI Check icon on Compose Preview: -![](https://developer.android.com/static/studio/images/design/compose-ui-check-entry.png) **Figure 5.** Entry point to UI check mode. - -UI check automatically previews your UI in different configurations and -highlights issues found in different configurations. In the **Problems** panel, -when you click an issue, you can see the details of the issue, suggested fixes, -and the renderings that highlight the area of the issue. -![](https://developer.android.com/static/studio/images/design/compose-ui-check.png) **Figure 6.** UI check mode in action. - -### Fix with AI - -For issues detected in UI Check mode, you can use the AI agent to propose and -apply code fixes. Click the **Fix with AI** button on an issue in the -**Problems** panel. The agent analyzes the problem and your code to suggest -changes that resolve the accessibility or adaptive issue. -![](https://developer.android.com/static/studio/preview/features/images/ui-check-mode-single-fix.png) **Figure 7.** The agent fixes UI issues in UI Check mode. \ 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/play/engage-sdk-integration/SKILL.md b/play/engage-sdk-integration/SKILL.md index d3bc957..a04a605 100644 --- a/play/engage-sdk-integration/SKILL.md +++ b/play/engage-sdk-integration/SKILL.md @@ -6,7 +6,7 @@ description: Helps developers integrate, debug, and resolve Play Engage SDK impl license: Complete terms in LICENSE.txt metadata: author: Google LLC - last-updated: '2026-09-22' + last-updated: '2026-10-03' keywords: - android - engage diff --git a/play/engage-sdk-integration/references/android/guide/playcore/engage/food.md b/play/engage-sdk-integration/references/android/guide/playcore/engage/food.md index 5c6f54c..6c86ff9 100644 --- a/play/engage-sdk-integration/references/android/guide/playcore/engage/food.md +++ b/play/engage-sdk-integration/references/android/guide/playcore/engage/food.md @@ -11,6 +11,29 @@ This guide contains instructions for developer partners to deliver food content (food ordering, food or restaurant reviews \& discovery, meal subscriptions, recipes) to Engage content surfaces. +## Category support and surfaces + +Engage SDK surface eligibility is based on your app's content category. Use the +following table to determine your eligibility for specific surfaces: + +| Status | Content category or use case | Supported surfaces | +|---|---|---| +| **Supported** (Eligible on all surfaces) | - Food / coffee / drink ordering or delivery - Restaurant / Food discovery, reviews, \& reservations - Meal subscriptions - Recipes \& Cooking | - Collections - Play Store Apps tab - Play Store You tab - Play Store store listing page | +| **Not supported** | - Driver / deliverer app - Cooking tools / controls | | + + +## Android skills + +[View on GitHub](https://github.com/android/skills/tree/main/play/engage-sdk-integration) + +### Integrate Engage SDK + +Use the Engage SDK [Android skill](https://developer.android.com/tools/agents/android-skills) to integrate, debug, and resolve implementation issues. To install the skill from the [Android CLI](https://developer.android.com/tools/agents/android-cli), run: + + android skills add engage-sdk-integration + +
+ ## Integration detail ### Terminology diff --git a/play/engage-sdk-integration/references/android/guide/playcore/engage/healthandfitness.md b/play/engage-sdk-integration/references/android/guide/playcore/engage/healthandfitness.md index 417d333..2b0bf02 100644 --- a/play/engage-sdk-integration/references/android/guide/playcore/engage/healthandfitness.md +++ b/play/engage-sdk-integration/references/android/guide/playcore/engage/healthandfitness.md @@ -10,6 +10,29 @@ site](http://play.google.com/console/about/programs/EngageSDK)**. This guide contains instructions for developer partners to deliver health and fitness content to Engage content surfaces. +## Category support and surfaces + +Engage SDK surface eligibility is based on your app's content category. Use the +following table to determine your eligibility for specific surfaces: + +| Status | Content category or use case | Supported surfaces | +|---|---|---| +| **Supported** (Eligible on all surfaces) | - Fitness classes - Fitness routines - Relaxation \& meditation - Routes / trails discovery - Meal planning | - Collections - Play Store Apps tab - Play Store You tab - Play Store store listing page | +| **Not supported** | - BMI calculator - Physical activity tracker / Step counter - Calorie counter - Sleep tracker - Tools / Remotes - Medical apps | | + + +## Android skills + +[View on GitHub](https://github.com/android/skills/tree/main/play/engage-sdk-integration) + +### Integrate Engage SDK + +Use the Engage SDK [Android skill](https://developer.android.com/tools/agents/android-skills) to integrate, debug, and resolve implementation issues. To install the skill from the [Android CLI](https://developer.android.com/tools/agents/android-cli), run: + + android skills add engage-sdk-integration + +
+ ## Integration detail ### Terminology diff --git a/play/engage-sdk-integration/references/android/guide/playcore/engage/listen.md b/play/engage-sdk-integration/references/android/guide/playcore/engage/listen.md index 7aeb887..4a1240f 100644 --- a/play/engage-sdk-integration/references/android/guide/playcore/engage/listen.md +++ b/play/engage-sdk-integration/references/android/guide/playcore/engage/listen.md @@ -10,6 +10,30 @@ site](http://play.google.com/console/about/programs/EngageSDK)**. This guide contains instructions for developer partners to deliver audio content (music, podcasts, audiobooks, live radio) to Engage content surfaces. +## Category support and surfaces + +Engage SDK surface eligibility is based on your app's content category. Use the +following table to determine your eligibility for specific surfaces: + +| Status | Content category or use case | Supported surfaces | +|---|---|---| +| **Supported** (Eligible on all surfaces) | - Music streaming - Audiobooks - Podcasts - Live radio | - Collections - Entertainment Space - Play Store Apps tab - Play Store You tab - Play Store store listing page | +| **Partially supported** (Eligible on some surfaces) | - Live audio streams | - Collections - Entertainment Space | +| **Not supported** | - MP3 player / library - Music maker - Audio recorder / converter / tool - Music recognition - Music education (supported in education) | | + + +## Android skills + +[View on GitHub](https://github.com/android/skills/tree/main/play/engage-sdk-integration) + +### Integrate Engage SDK + +Use the Engage SDK [Android skill](https://developer.android.com/tools/agents/android-skills) to integrate, debug, and resolve implementation issues. To install the skill from the [Android CLI](https://developer.android.com/tools/agents/android-cli), run: + + android skills add engage-sdk-integration + +
+ ## Integration detail ### Terminology diff --git a/play/engage-sdk-integration/references/android/guide/playcore/engage/otherverticals.md b/play/engage-sdk-integration/references/android/guide/playcore/engage/otherverticals.md index 75195ab..6dec631 100644 --- a/play/engage-sdk-integration/references/android/guide/playcore/engage/otherverticals.md +++ b/play/engage-sdk-integration/references/android/guide/playcore/engage/otherverticals.md @@ -11,6 +11,19 @@ This document contains instructions for developer partners to integrate new content like reservations, events, lodging, places of interest, people and other content that might not belong to any of these categories. + +## Android skills + +[View on GitHub](https://github.com/android/skills/tree/main/play/engage-sdk-integration) + +### Integrate Engage SDK + +Use the Engage SDK [Android skill](https://developer.android.com/tools/agents/android-skills) to integrate, debug, and resolve implementation issues. To install the skill from the [Android CLI](https://developer.android.com/tools/agents/android-cli), run: + + android skills add engage-sdk-integration + +
+ ## Integration detail ### Terminology diff --git a/play/engage-sdk-integration/references/android/guide/playcore/engage/read.md b/play/engage-sdk-integration/references/android/guide/playcore/engage/read.md index 70b0701..9c34f6f 100644 --- a/play/engage-sdk-integration/references/android/guide/playcore/engage/read.md +++ b/play/engage-sdk-integration/references/android/guide/playcore/engage/read.md @@ -11,7 +11,51 @@ week of developer time. Learn more at our **[business site](http://play.google.com/console/about/programs/EngageSDK)**. This guide contains instructions for developer partners to deliver reading -content (eBooks, Audiobooks, Comics/Manga) to Engage content surfaces. +content (eBooks, Audiobooks, Comics/Manga, Blogs, News and Magazines) to Engage +content surfaces. + +## Category support and surfaces + +Engage SDK surface eligibility is based on your app's content category. Use the +following table to determine your eligibility for specific surfaces: + +| Status | Content category or use case | Supported surfaces | +|---|---|---| +| **Supported** (Eligible on all surfaces) | - eBooks - Comics \& Manga - Audiobooks - UGC long-form reading services - Blogs | - Collections - Entertainment Space - Play Store Apps tab - Play Store You tab (Personalized recommendations \& continuation only) - Play Store store listing page | +| **Partially supported** (Eligible on some surfaces) | - News \& Magazines\* | - Collections - Entertainment Space | +| **Not supported** | - eBook or PDF reader - Book reviews - Encyclopedia / Dictionary - Translator - Religious text | | + +### News apps + +News apps provide diverse content, each subject to unique eligibility rules. +Because Engage SDK content on the Play Store aligns with Play Premium Growth +Tools, news articles and video clips from news apps are ineligible for the Play +Store, though they are eligible for Collections and Entertainment Space. +Conversely, non-traditional news content, like podcasts and recipes, may be +eligible for the Play Store: + +| Entity type | Eligible for Collections | Eligible for Entertainment Space | Eligible for Play Store | +|---|---|---|---| +| News article | *Yes (Read tab)* | *Yes (Read tab)* | No | +| Video clip | *Yes (Watch tab)* | *Yes (Watch tab)* | No | +| Podcast | *Yes (Listen tab)* | *Yes (Listen tab)* | *Yes* | +| Recipe | *Yes (Food tab)* | No | *Yes* | + +> [!NOTE] +> **Note:** Play Store eligibility aligns with the supported categories for [Play Premium Growth Tools](https://play.google.com/console/about/guides/premium-growth-tools/). If your news app has additional forms of content not listed in the preceding table, contact [`engage-developers@google.com`](mailto:engage-developers@google.com) to clarify its eligibility for the Play Store. + + +## Android skills + +[View on GitHub](https://github.com/android/skills/tree/main/play/engage-sdk-integration) + +### Integrate Engage SDK + +Use the Engage SDK [Android skill](https://developer.android.com/tools/agents/android-skills) to integrate, debug, and resolve implementation issues. To install the skill from the [Android CLI](https://developer.android.com/tools/agents/android-cli), run: + + android skills add engage-sdk-integration + +
## Integration detail diff --git a/play/engage-sdk-integration/references/android/guide/playcore/engage/shopping.md b/play/engage-sdk-integration/references/android/guide/playcore/engage/shopping.md index de0a90f..9dc53d3 100644 --- a/play/engage-sdk-integration/references/android/guide/playcore/engage/shopping.md +++ b/play/engage-sdk-integration/references/android/guide/playcore/engage/shopping.md @@ -10,6 +10,29 @@ site](http://play.google.com/console/about/programs/EngageSDK)**. This guide contains instructions for developer partners to deliver shopping content to Engage content surfaces. +## Category support and surfaces + +Engage SDK surface eligibility is based on your app's content category. Use the +following table to determine your eligibility for specific surfaces: + +| Status | Content category or use case | Supported surfaces | +|---|---|---| +| **Supported** (Eligible on all surfaces) | - Clothes shopping - Online marketplace - Retailer - Food \& drink shopping / grocery - Discounts \& coupons - Pet / supplies - House \& home / Real estate | - Collections - Play Store Apps tab - Play Store You tab - Play Store store listing page | +| **Not supported** | - Shopper app - B2B Shopping - Seller / merchant app - Receipts scanner rewards - Telecom service - Buy now pay later | | + + +## Android skills + +[View on GitHub](https://github.com/android/skills/tree/main/play/engage-sdk-integration) + +### Integrate Engage SDK + +Use the Engage SDK [Android skill](https://developer.android.com/tools/agents/android-skills) to integrate, debug, and resolve implementation issues. To install the skill from the [Android CLI](https://developer.android.com/tools/agents/android-cli), run: + + android skills add engage-sdk-integration + +
+ ## Integration detail ### Terminology diff --git a/play/engage-sdk-integration/references/android/guide/playcore/engage/social.md b/play/engage-sdk-integration/references/android/guide/playcore/engage/social.md index 0b2d1c4..778b040 100644 --- a/play/engage-sdk-integration/references/android/guide/playcore/engage/social.md +++ b/play/engage-sdk-integration/references/android/guide/playcore/engage/social.md @@ -10,6 +10,29 @@ site](http://play.google.com/console/about/programs/EngageSDK)**. This guide contains instructions for developer partners to deliver social media content to Engage content surfaces. +## Category support and surfaces + +Engage SDK surface eligibility is based on your app's content category. Use the +following table to determine your eligibility for specific surfaces: + +| Status | Content category or use case | Supported surfaces | +|---|---|---| +| **Supported** (Eligible on all surfaces) | - Social networks - Image / memes - Video clips / short-form video - IRL meetups - Cloud photo storage | - Collections - Play Store Apps tab - Play Store You tab - Play Store store listing page | +| **Not supported** | - Live Video Chat - Instant messaging, sharing online presence, or live data - File-sharing \& downloaders - Dating - Avatar generators | | + + +## Android skills + +[View on GitHub](https://github.com/android/skills/tree/main/play/engage-sdk-integration) + +### Integrate Engage SDK + +Use the Engage SDK [Android skill](https://developer.android.com/tools/agents/android-skills) to integrate, debug, and resolve implementation issues. To install the skill from the [Android CLI](https://developer.android.com/tools/agents/android-cli), run: + + android skills add engage-sdk-integration + +
+ ## Integration detail The following section captures the integration detail. diff --git a/play/engage-sdk-integration/references/android/guide/playcore/engage/travel.md b/play/engage-sdk-integration/references/android/guide/playcore/engage/travel.md index 55bb090..84baa26 100644 --- a/play/engage-sdk-integration/references/android/guide/playcore/engage/travel.md +++ b/play/engage-sdk-integration/references/android/guide/playcore/engage/travel.md @@ -10,6 +10,29 @@ site](http://play.google.com/console/about/programs/EngageSDK)**. This guide contains instructions for developer partners to deliver travel and events content to Engage content surfaces. +## Category support and surfaces + +Engage SDK surface eligibility is based on your app's content category. Use the +following table to determine your eligibility for specific surfaces: + +| Status | Content category or use case | Supported surfaces | +|---|---|---| +| **Supported** (Eligible on all surfaces) | - All Travel \& local (except unsupported subcategories listed in this table) - Events (all subcategories) - Pet activity, monitoring, sitting, walking | - Collections - Play Store Apps tab - Play Store You tab - Play Store store listing page | +| **Not supported** | - Flight trackers | | + + +## Android skills + +[View on GitHub](https://github.com/android/skills/tree/main/play/engage-sdk-integration) + +### Integrate Engage SDK + +Use the Engage SDK [Android skill](https://developer.android.com/tools/agents/android-skills) to integrate, debug, and resolve implementation issues. To install the skill from the [Android CLI](https://developer.android.com/tools/agents/android-cli), run: + + android skills add engage-sdk-integration + +
+ ## Integration detail ### Terminology diff --git a/play/engage-sdk-integration/references/android/guide/playcore/engage/watch.md b/play/engage-sdk-integration/references/android/guide/playcore/engage/watch.md index 2770f93..21c0938 100644 --- a/play/engage-sdk-integration/references/android/guide/playcore/engage/watch.md +++ b/play/engage-sdk-integration/references/android/guide/playcore/engage/watch.md @@ -17,6 +17,30 @@ existing Google surfaces. > [!NOTE] > **Note:** Check the [TV integration guide](https://developer.android.com/guide/playcore/engage/tv) to learn more about the Continue Watching experience on Google TV. +## Category support and surfaces + +Engage SDK surface eligibility is based on your app's content category. Use the +following table to determine your eligibility for specific surfaces: + +| Status | Content category or use case | Supported surfaces | +|---|---|---| +| **Supported** (Eligible on all surfaces) | - Movies \& TV streaming - Live TV / Sports - On demand video clips - Sports videos - Drama Shorts - Video podcasts | - Collections - Entertainment Space - Play Store Apps tab - Play Store You tab - Play Store store listing page | +| **Partially supported** (Eligible on some surfaces) | - Video clips from news apps | - Collections - Entertainment Space | +| **Not supported** | - Remote controls - Video downloaders, players, and editors - Movie \& TV reviews - Movie tickets - TV guides - Video calls \& chatrooms | | + + +## Android skills + +[View on GitHub](https://github.com/android/skills/tree/main/play/engage-sdk-integration) + +### Integrate Engage SDK + +Use the Engage SDK [Android skill](https://developer.android.com/tools/agents/android-skills) to integrate, debug, and resolve implementation issues. To install the skill from the [Android CLI](https://developer.android.com/tools/agents/android-cli), run: + + android skills add engage-sdk-integration + +
+ ## Integration detail ### Terminology diff --git a/play/play-billing-library-version-upgrade/SKILL.md b/play/play-billing-library-version-upgrade/SKILL.md index 0795e6a..efe3611 100644 --- a/play/play-billing-library-version-upgrade/SKILL.md +++ b/play/play-billing-library-version-upgrade/SKILL.md @@ -6,7 +6,7 @@ description: Use this skill when upgrading or migrating an Android project from license: Complete terms in LICENSE.txt metadata: author: Google LLC - last-updated: '2026-09-01' + last-updated: '2026-10-06' keywords: - android - play billing diff --git a/play/play-billing-library-version-upgrade/references/android/google/play/billing/release-notes.md b/play/play-billing-library-version-upgrade/references/android/google/play/billing/release-notes.md index 9fc90a8..02c4114 100644 --- a/play/play-billing-library-version-upgrade/references/android/google/play/billing/release-notes.md +++ b/play/play-billing-library-version-upgrade/references/android/google/play/billing/release-notes.md @@ -28,7 +28,7 @@ Version 9.1.0 of the Google Play Billing Library and Kotlin extensions are now a - Added [`BillingChoiceInfo`](https://developer.android.com/reference/com/android/billingclient/api/BillingChoiceInfo) class which contains the response details, including image URLs and loyalty information. - Added [`GetBillingChoiceInfoParams`](https://developer.android.com/reference/com/android/billingclient/api/GetBillingChoiceInfoParams) to configure the request. - Added [`BillingChoiceInfoResponseListener`](https://developer.android.com/reference/com/android/billingclient/api/BillingChoiceInfoResponseListener) to receive the callback. - - Added Kotlin extension [`BillingClient.getBillingChoiceInfo()`](https://developer.android.com/reference/kotlin/com/android/billingclient/api/package-summary#(com.android.billingclient.api.BillingClient).getBillingChoiceInfo(com.android.billingclient.api.GetBillingChoiceInfoParams)) suspend function. + - Added Kotlin extension `BillingClient.getBillingChoiceInfo()` suspend function. - Added [`BillingClient.showBillingProgramInformationDialog()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#showBillingProgramInformationDialog(android.app.Activity,com.android.billingclient.api.BillingProgramInformationDialogParams,com.android.billingclient.api.BillingProgramInformationDialogListener)) to show an information dialog for a billing program. - Added [`BillingProgramInformationDialogParams`](https://developer.android.com/reference/com/android/billingclient/api/BillingProgramInformationDialogParams) to configure the dialog. - Added [`BillingProgramInformationDialogListener`](https://developer.android.com/reference/com/android/billingclient/api/BillingProgramInformationDialogListener) to receive the callback. @@ -52,7 +52,7 @@ the previous versions of PBL. debug message. > [!NOTE] - > **Note:** For this feature to work, you need [AndroidX.core library](https://developer.android.com/jetpack/androidx/releases/core#core_and_core-ktx_version_190_2) version 1.9 or later. + > **Note:** For this feature to work, you need [AndroidX.core library](https://developer.android.com/jetpack/androidx/releases/core#1.9.0) version 1.9 or later. - **Nullability update for developer-provided billing** : The [`DeveloperProvidedBillingDetails.getLinkUri()`](https://developer.android.com/reference/com/android/billingclient/api/DeveloperProvidedBillingDetails#getLinkUri()) method has @@ -78,13 +78,16 @@ Version 8.3.0 of the Google Play Billing Library and Kotlin extensions are now a - New APIs for [external payments](https://developer.android.com/google/play/billing/externalpaymentlinks): - Added classes to support external payments flow: - - [`BillingProgram.EXTERNAL_PAYMENTS`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingProgram#EXTERNAL_PAYMENTS) + - [`BillingProgram.EXTERNAL_PAYMENTS`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingProgram#EXTERNAL_PAYMENTS()) - [`EnableBillingProgramParams`](https://developer.android.com/reference/com/android/billingclient/api/EnableBillingProgramParams) - [`DeveloperBillingOptionParams`](https://developer.android.com/reference/com/android/billingclient/api/DeveloperBillingOptionParams) - [`DeveloperProvidedBillingDetails`](https://developer.android.com/reference/com/android/billingclient/api/DeveloperProvidedBillingDetails) - [`DeveloperProvidedBillingListener`](https://developer.android.com/reference/com/android/billingclient/api/DeveloperProvidedBillingListener) - Added [`enableBillingProgram(EnableBillingProgramParams)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enableBillingProgram(com.android.billingclient.api.EnableBillingProgramParams)) to enable external payments. - Added [`BillingFlowParams.Builder.enableDeveloperBillingOption`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#enableDeveloperBillingOption(com.android.billingclient.api.DeveloperBillingOptionParams)) to launch the external payments flow. +- Improved error categorization for external payment flow drop-offs: + + - If a user navigates away from the purchase flow to complete payment on an external app (such as an e-wallet) and abandons the transaction without returning, the response code returned to the client is now `BillingResponseCode.USER_CANCELED` (1) with debug message `BILLING_ACTIVITY_PAUSED` (previously defaulted to `BillingResponseCode.ERROR` (6)). ## Google Play Billing Library 8.2.1 Release (2025-12-15) @@ -181,7 +184,7 @@ available. 8.0.0, unfetched products are returned with a new product-level status code that provides information about unfetched products. Note that there is a change in the signature of the - [`ProductDetailsResponseListener.onProductDetailsResponse()`](https://developer.android.com/google/play/billing/integrate#automatic-service-reconnection) which + [`ProductDetailsResponseListener.onProductDetailsResponse()`](https://developer.android.com/reference/com/android/billingclient/api/ProductDetailsResponseListener#onProductDetailsResponse(com.android.billingclient.api.BillingResult,com.android.billingclient.api.QueryProductDetailsResult)) which requires changes in your app. For more information, see [process the result](https://developer.android.com/google/play/billing/integrate#process-the-result). - Automatic service reconnection. @@ -257,7 +260,7 @@ available. - Added [`ProductDetails.InstallmentPlanDetails`](https://developer.android.com/reference/com/android/billingclient/api/ProductDetails.InstallmentPlanDetails) for installment base plans that users are eligible to purchase. This API helps your app identify the installment plan and its commitment setup to provide related information to the user. To learn more, see our [subscription installments guide](https://developer.android.com/google/play/billing/subscriptions#installments). - Added [`PendingPurchasesParams`](https://developer.android.com/reference/com/android/billingclient/api/PendingPurchasesParams) and - [`BillingClient.Builder.enablePendingPurchases(PendingPurchaseParams)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enablePendingPurchases(PendingPurchaseParams)) + [`BillingClient.Builder.enablePendingPurchases(PendingPurchaseParams)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enablePendingPurchases(com.android.billingclient.api.PendingPurchasesParams)) to replace [`BillingClient.Builder.enablePendingPurchases()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enablePendingPurchases()), which has been deprecated in this release. @@ -265,9 +268,9 @@ available. - The deprecated `enablePendingPurchases()` is functionally equivalent to `enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build())`. - Added APIs to support pending transactions for subscription prepaid plans: - - Use [`PendingPurchasesParams.Builder.enablePrepaidPlans()`](https://developer.android.com/reference/com/android/billingclient/api/PendingPurchasesParams.Builder#enablePrepaidPlans()) along with [`BillingClient.Builder.enablePendingPurchases(PendingPurchaseParams)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enablePendingPurchases(PendingPurchaseParams)) to enable pending transactions for subscription prepaid plans. When adding support, be sure that your app also correctly manages subscription lifecycles. To learn more see our [pending purchases + - Use [`PendingPurchasesParams.Builder.enablePrepaidPlans()`](https://developer.android.com/reference/com/android/billingclient/api/PendingPurchasesParams.Builder#enablePrepaidPlans()) along with [`BillingClient.Builder.enablePendingPurchases(PendingPurchaseParams)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enablePendingPurchases(com.android.billingclient.api.PendingPurchasesParams)) to enable pending transactions for subscription prepaid plans. When adding support, be sure that your app also correctly manages subscription lifecycles. To learn more see our [pending purchases guide](https://developer.android.com/google/play/billing/subscriptions#pending). - - Added [`Purchase.PendingPurchaseUpdate`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PendingPurchaseUpdate) and [`Purchase.getPendingPurchaseUpdate()`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.getPendingPurchaseUpdate()) for retrieving the pending top-up or upgrade or downgrade to an existing subscription. + - Added [`Purchase.PendingPurchaseUpdate`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PendingPurchaseUpdate) and [`Purchase.getPendingPurchaseUpdate()`](https://developer.android.com/reference/com/android/billingclient/api/Purchase#getPendingPurchaseUpdate()) for retrieving the pending top-up or upgrade or downgrade to an existing subscription. - Removed [`BillingClient.Builder.enableAlternativeBilling()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enableAlternativeBilling(com.android.billingclient.api.AlternativeBillingListener)), [`AlternativeBillingListener`](https://developer.android.com/reference/com/android/billingclient/api/AlternativeBillingListener), and @@ -359,14 +362,14 @@ available. Note that `ProrationMode` is still available for backward compatibility reasons. -- Removed order ID for [`PENDING`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState#PENDING) +- Removed order ID for [`PENDING`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState#PENDING()) purchases. Previously, the order ID would always be created even if the purchase was pending. Starting with version 6.0.0, an order ID will not be created for pending purchases, and for these purchases, the order ID will be populated after the purchase is moved to the - [`PURCHASED`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState#PURCHASED) + [`PURCHASED`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState#PURCHASED()) state. - Removed `queryPurchases` and `launchPriceConfirmationFlow` methods. @@ -379,20 +382,20 @@ available. - Added new network error response code. A new network error response code, - [`NETWORK_ERROR`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#NETWORK_ERROR), + [`NETWORK_ERROR`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#NETWORK_ERROR()), has been added starting with PBL version 6.0.0. This code is returned when an error occurs due to a network connection issue. These network connection errors were previously reported as `SERVICE_UNAVAILABLE`. -- Updated [`SERVICE_UNAVAILABLE`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#SERVICE_UNAVAILABLE) +- Updated [`SERVICE_UNAVAILABLE`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#SERVICE_UNAVAILABLE()) and - [`SERVICE_TIMEOUT`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#SERVICE_TIMEOUT). + [`SERVICE_TIMEOUT`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#SERVICE_TIMEOUT()). Starting with PBL version 6.0.0, errors due to timeout in processing will be returned as `SERVICE_UNAVAILABLE` instead of the current `SERVICE_TIMEOUT`. The behavior does not change in earlier versions of PBL. - Removed - [`SERVICE_TIMEOUT`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#SERVICE_TIMEOUT). + [`SERVICE_TIMEOUT`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#SERVICE_TIMEOUT()). Starting with PBL version 6.0.0, `SERVICE_TIMEOUT` will no longer be returned. Previous versions of PBL will still return this code. @@ -454,7 +457,7 @@ This version contains the following changes. price](https://developer.android.com/google/play/billing/integrate#personalized-price). - Removed `queryPurchases()`, which was previously deprecated and replaced by queryPurchasesAsync introduced in Google Play Billing Library 4.0.0. - `launchPriceChangeFlow` has been deprecated and will be removed in a future release. To learn more about alternatives, see [Launch a price change - confirmation flow](https://developer.android.com/google/play/billing/subscriptions#price-change-launch). + confirmation flow](https://developer.android.com/google/play/billing/price-changes). - Removed [`setVrPurchaseFlow()`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#setVrPurchaseFlow(boolean)), which was previously used when instantiating a purchase flow. In previous versions, this method redirected the user to complete the purchase on their Android-powered device. Once you remove this method, users will complete the purchase through the standard purchase flow. ## Google Play Billing Library 4.1 release (2022-02-23) @@ -483,7 +486,7 @@ available. which will be removed in a future release. - Added new subscription replacement mode - [`IMMEDIATE_AND_CHARGE_FULL_PRICE`](https://developer.android.com/google/play/billing/subscriptions#change). + [`IMMEDIATE_AND_CHARGE_FULL_PRICE`](https://developer.android.com/google/play/billing/subscriptions#replacement-modes). - Added [`BillingClient.getConnectionState()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#getConnectionState()) @@ -604,9 +607,7 @@ Unity](https://developer.android.com/google/play/billing/unity). - In `BillingFlowParams`, added [`setObfuscatedProfileId()`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#setobfuscatedprofileid) which works similarly to [`setObfuscatedAccountId()`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#setObfuscatedAccountId(java.lang.String)). For more information, see [Developer payload updates and alternatives](https://developer.android.com/google/play/billing/developer-payload). - In [`Purchase`](https://developer.android.com/reference/com/android/billingclient/api/Purchase), added the [`getAccountIdentifiers()`](https://developer.android.com/reference/com/android/billingclient/api/Purchase#getAccountIdentifiers()) method to return the obfuscated account identifiers set in `BillingFlowParams`. - - In [`BillingClient`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient), the [`loadRewardedSku()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#loadRewardedSku(com.android.billingclient.api.RewardLoadParams,%20com.android.billingclient.api.RewardResponseListener)) method has been marked deprecated as part of deprecating rewarded SKUs. You can find more information about the deprecation in the [Play Console - Help - Center](https://support.google.com/googleplay/android-developer/answer/9155268). + - In [`BillingClient`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient), the [`loadRewardedSku()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#loadRewardedSku(com.android.billingclient.api.RewardLoadParams,%20com.android.billingclient.api.RewardResponseListener)) method has been marked deprecated as part of deprecating rewarded SKUs. ## Google Play Billing Library 2.1.0 Release and Kotlin Extension 2.1.0 Release (2019-12-10) @@ -917,14 +918,14 @@ billingClient.launchPriceChangeConfirmationFlow(activity, The price change confirmation flow displays a dialog containing the new pricing information, asking users to accept the new price. This flow returns a response code of type -[`BillingClient.BillingResponse`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponse). +[`BillingClient.BillingResponse`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode). #### New proration mode When upgrading or downgrading a user's subscription, you can use a new proration mode, `DEFERRED`. This mode updates the user's subscription when it next renews. To learn more about how to set this proration mode, see [Set proration -mode](https://developer.android.com/google/play/billing/billing_subscriptions#set-proration-mode). +mode](https://developer.android.com/google/play/billing/subscriptions#replacement-modes). #### New method for setting SKU details @@ -1005,7 +1006,7 @@ contains the following changes. - Added support to specify a proration mode in [`BillingFlowParams`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams) when upgrading/downgrading an existing subscription. - The `replaceSkusProration` boolean flag in [`BillingFlowParams`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams) is no longer supported. Use `replaceSkusProrationMode` instead. -- [`launchBillingFlow()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#launchBillingFlow(android.app.Activity,%0Acom.android.billingclient.api.BillingFlowParams)) now triggers a callback for failed responses. +- [`launchBillingFlow()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#launchBillingFlow(android.app.Activity,com.android.billingclient.api.BillingFlowParams)) now triggers a callback for failed responses. ### Behavior changes @@ -1064,16 +1065,16 @@ longer supported. The Billing Library will always trigger the [`PurhcasesUpdatedListener`](https://developer.android.com/reference/com/android/billingclient/api/PurchasesUpdatedListener) callback and return a -[`BillingResponse`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponse) +[`BillingResponse`](https://developer.android.com/reference/com/android/billingclient/api/BillingResult) asynchronously. The synchronous return value of -[`BillingResponse`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponse) +[`BillingResponse`](https://developer.android.com/reference/com/android/billingclient/api/BillingResult) is kept as well. ### Bug fixes - Properly exits early in async methods when service is disconnected. - [`Builder`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder) param objects no longer mutates built objects. -- Issue [68087141](https://issuetracker.google.com/issues/68087141): [`launchBillingFlow()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#launchBillingFlow(android.app.Activity,%0Acom.android.billingclient.api.BillingFlowParams)) now trigger callback for failed responses. +- Issue [68087141](https://issuetracker.google.com/issues/68087141): [`launchBillingFlow()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#launchBillingFlow(android.app.Activity,com.android.billingclient.api.BillingFlowParams)) now trigger callback for failed responses. ## Google Play Billing Library 1.0 Release (2017-09-19, [Announcement](https://android-developers.googleblog.com/2017/09/google-play-billing-library-10-released.html)) @@ -1112,7 +1113,7 @@ billingClient = BillingClient.newBuilder(context).setListener(this).build(); #### launchBillingFlow method is now called using a BillingFlowParams class To initiate the billing flow for a purchase or subscription, the -[`launchBillingFlow()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#launchBillingFlow(android.app.Activity,%0Acom.android.billingclient.api.BillingFlowParams)) method receives a +[`launchBillingFlow()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#launchBillingFlow(android.app.Activity,com.android.billingclient.api.BillingFlowParams)) method receives a [`BillingFlowParams`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.html) instance initialized with parameters specific to the request: @@ -1187,7 +1188,7 @@ public void onSkuDetailsResponse(@BillingResponse int responseCode, List [!WARNING] > **Warning:** You should only use custom Semantics properties when it's hard to match a specific item using the given finders and matchers. Using custom Semantics properties to expose visual properties such as colors, font size or rounded corner radius is not recommended, as it can pollute production code and wrong implementations can lead to bugs that are hard to find. -### Verify state restoration +## Verify state restoration Verify that the state of your Compose elements is correctly restored when the activity or process is recreated. Perform such checks without relying on @@ -104,7 +104,7 @@ useful to verify the implementation of [`rememberSaveable`](https://developer.an } } -### Test different device configurations +## Test different device configurations Android apps need to adapt to many changing conditions: window sizes, locales, font sizes, dark and light themes, and more. Most of these conditions are @@ -170,6 +170,104 @@ composeTestRule.setContent {
+## Custom failure handling in tests + +Debugging UI tests often requires inspecting the screen state and composition +hierarchy when an assertion fails. + +Compose provides a native failure handling pipeline to streamline diagnostics. +You can automatically capture screenshots and UI hierarchies upon failure, or +register custom handlers to forward crash telemetry to external tools. + +> [!NOTE] +> **Note:** The APIs associated with the failure handler pipeline ([`TestFailurePolicy`](https://developer.android.com/reference/kotlin/androidx/compose/ui/test/TestFailurePolicy), [`TestFailureHandler`](https://developer.android.com/reference/kotlin/androidx/compose/ui/test/TestFailureHandler), and [`FailureContext`](https://developer.android.com/reference/kotlin/androidx/compose/ui/test/FailureContext)) are included in [`androidx.compose.ui:ui-test:1.13.0-alpha01+`](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.13.0-alpha01) and [`androidx.compose.ui:ui-test-junit4:1.13.0-alpha01+`](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.13.0-alpha01). + +### Configure failure handling + +To configure failure handling, pass a [`TestFailurePolicy`](https://developer.android.com/reference/kotlin/androidx/compose/ui/test/TestFailurePolicy) object to +[`ComposeUiTestConfig`](https://developer.android.com/reference/kotlin/androidx/compose/ui/test/ComposeUiTestConfig). + +#### Capture built-in failure artifacts + +To capture screenshots and UI hierarchies upon test failure, set the +[`screenshotCaptureMode`](https://developer.android.com/reference/kotlin/androidx/compose/ui/test/TestFailurePolicy#screenshotCaptureMode()) and [`uiHierarchyCaptureMode`](https://developer.android.com/reference/kotlin/androidx/compose/ui/test/TestFailurePolicy#uiHierarchyCaptureMode()) properties in +`TestFailurePolicy`. + + +```kotlin +class MyComposeTest { + private val customConfig = ComposeUiTestConfig( + failurePolicy = TestFailurePolicy( + screenshotCaptureMode = TestFailurePolicy.CaptureMode.Enabled, + uiHierarchyCaptureMode = TestFailurePolicy.CaptureMode.Enabled + ) + ) + + @Test + fun myFirstTest() = runComposeUiTest(config = customConfig) { + // ... + } +} +``` + +
+ +### Use a custom failure handler + +To define custom behavior for test failures, add a +[`TestFailureHandler`](https://developer.android.com/reference/kotlin/androidx/compose/ui/test/TestFailureHandler) to the [`failureHandlers` list](https://developer.android.com/reference/kotlin/androidx/compose/ui/test/TestFailurePolicy#failureHandlers()). Each handler +receives a [`FailureContext`](https://developer.android.com/reference/kotlin/androidx/compose/ui/test/FailureContext) object that contains the original error and a +list of artifacts generated by the preceding pipeline handlers. + + +```kotlin +class MyComposeTestWithHandler { + private val customConfig = ComposeUiTestConfig( + failurePolicy = TestFailurePolicy( + screenshotCaptureMode = TestFailurePolicy.CaptureMode.Enabled, + failureHandlers = listOf( + TestFailureHandler { context -> + val screenshot = context.artifacts.firstOrNull { + it.type == FailureArtifact.Type.Screenshot + } + // ... + } + ) + ) + ) + + @get:Rule + val rule = createComposeRule(config = customConfig) + + // ... +} +``` + +
+ +### Configure captures for an entire test suite + +You can enable failure artifact captures for an entire test suite by passing +instrumentation runner arguments in your module's `build.gradle` file. This +eliminates the need to update individual test files. + + // build.gradle.kts + android { + defaultConfig { + // ... + testInstrumentationRunnerArguments["androidx.compose.ui.test.failure.isUiHierarchyCaptureEnabled"] = "true" + testInstrumentationRunnerArguments["androidx.compose.ui.test.failure.isScreenshotCaptureEnabled"] = "true" + } + } + +#### Configuration precedence + +The framework evaluates both the local `TestFailurePolicy` and the suite-level +runner arguments: + +- If you explicitly set a mode to `CaptureMode.Enabled`/`Disabled` in your test `failurePolicy`, this overrides the runner argument. +- If you set a mode to `CaptureMode.Unspecified` (or leave `failurePolicy` as `null`), the framework falls back to the runner argument. + ## Additional Resources - **[Test apps on Android](https://developer.android.com/training/testing)**: The main Android testing landing page provides a broader view of testing fundamentals and techniques. diff --git a/testing/testing-setup/references/android/studio/preview/compose-screenshot-testing.md b/testing/testing-setup/references/android/studio/preview/compose-screenshot-testing.md index 9757b66..2cdcc80 100644 --- a/testing/testing-setup/references/android/studio/preview/compose-screenshot-testing.md +++ b/testing/testing-setup/references/android/studio/preview/compose-screenshot-testing.md @@ -186,7 +186,7 @@ integration or for the Gradle tasks. Try [Android CLI](https://developer.android.com/tools/agents) if you're not using Android Studio or prefer to do things from the command line. -For example, use the [`android studio render-compose-preview`](https://developer.android.com/tools/agents/android-cli#studio-render-compose-preview) command when you need to render a Compose preview for visual testing. +For example, use the [`android studio render-compose-preview`](https://developer.android.com/tools/agents/android-cli/commands/studio_render-compose-preview) command when you need to render a Compose preview for visual testing. android studio render-compose-preview diff --git a/xr/display-glasses-with-jetpack-compose-glimmer/SKILL.md b/xr/display-glasses-with-jetpack-compose-glimmer/SKILL.md index 9afe03d..b4c8634 100644 --- a/xr/display-glasses-with-jetpack-compose-glimmer/SKILL.md +++ b/xr/display-glasses-with-jetpack-compose-glimmer/SKILL.md @@ -9,7 +9,7 @@ description: Provides guidelines for developing projected Android XR apps for di license: Complete terms in LICENSE.txt metadata: author: Google LLC - last-updated: '2026-09-25' + last-updated: '2026-10-04' keywords: - Jetpack Compose Glimmer - display glasses diff --git a/xr/display-glasses-with-jetpack-compose-glimmer/references/android/design/ui/ai-glasses/guides/interaction/inputs.md b/xr/display-glasses-with-jetpack-compose-glimmer/references/android/design/ui/ai-glasses/guides/interaction/inputs.md index 8dd8dd6..c5b2c3b 100644 --- a/xr/display-glasses-with-jetpack-compose-glimmer/references/android/design/ui/ai-glasses/guides/interaction/inputs.md +++ b/xr/display-glasses-with-jetpack-compose-glimmer/references/android/design/ui/ai-glasses/guides/interaction/inputs.md @@ -20,6 +20,7 @@ the app, and others that are handled by the system. **Your app has access to the glasses:** +- Single-finger swipe on touchpad - Microphone - Point-of-View (POV) camera - Inertial Measurement Unit (IMU) @@ -28,7 +29,7 @@ the app, and others that are handled by the system. ![Design elements should be anchored to the bottom of the frame.](https://developer.android.com/static/images/design/ui/glasses/guides/glasses_ixd_inputs_hardware.png) -### AI Glasses +### Audio glasses | Input | Gesture | Interaction affect | @@ -45,7 +46,7 @@ frame.](https://developer.android.com/static/images/design/ui/glasses/guides/gla
-### AI Glasses with Display +### Display glasses | Input | Gesture | Interaction affect | @@ -64,4 +65,69 @@ frame.](https://developer.android.com/static/images/design/ui/glasses/guides/gla ![Design elements should be anchored to the bottom of the frame.](https://developer.android.com/static/images/design/ui/glasses/guides/glasses_ixd_inputs_focus.png) On -display focus states have a visual affordances. \ No newline at end of file +display focus states have a visual affordances. + +## Usability principles + +Use the following principles when designing for glasses inputs: + +### Input interaction is quick \& discreet + +Audio and display glasses are designed around short and quick gestures rather +than complex and +precise controls. +![](https://developer.android.com/static/images/design/ui/glasses/guides/glasses_input_complex_do.webp) + +### Do + +Design around fast gestures that don't feel out of place while a user is out and about. ![](https://developer.android.com/static/images/design/ui/glasses/guides/glasses_input_complex_dont.webp) + +### Don't + +Expect complex and precise gestures like those on mobile and desktop. + +### Recognition rather than recall for display glasses + +Limit the number of gestures that the user needs to remember, and take advantage +of existing user mental models. +![](https://developer.android.com/static/images/design/ui/glasses/guides/glasses_input_recognition_do.webp) + +### Do + +Provide UI elements for users to complete actions when the display is available. ![](https://developer.android.com/static/images/design/ui/glasses/guides/glasses_input_recognition_dont.webp) + +### Don't + +Overly rely on users remembering gestures if the display is available. + +### Always provide an immediate exit for users + +A user should be able to exit an experience on their glasses at any point. + +### Provide immediate, clear feedback about detected gesture and action taken + +For audio glasses, both audio output and earcons are crucial for gesture +feedback. Earcons should be played immediately after a gesture, be audible, and +be distinct in various environments. + +For display glasses, immediate visual feedback significantly enhances usability +due to the indirect interaction paradigm. + +### Promote a smooth experience + +Help users avoid mistakes by: + +- Removing memory burdens +- Accounting for possible accidental slips +- Supporting undo actions +- Warning users before important actions + +![](https://developer.android.com/static/images/design/ui/glasses/guides/glasses_input_error_do.webp) + +### Do + +Put initial focus on a lower-cost action, like accepting a call. ![](https://developer.android.com/static/images/design/ui/glasses/guides/glasses_input_error_dont.webp) + +### Don't + +Put initial focus on high-cost actions, like declining a call. \ No newline at end of file diff --git a/xr/display-glasses-with-jetpack-compose-glimmer/references/card-samples-source.md b/xr/display-glasses-with-jetpack-compose-glimmer/references/card-samples-source.md index b82c4ae..a8f26f5 100644 --- a/xr/display-glasses-with-jetpack-compose-glimmer/references/card-samples-source.md +++ b/xr/display-glasses-with-jetpack-compose-glimmer/references/card-samples-source.md @@ -23,7 +23,11 @@ package androidx.xr.glimmer.samples import androidx.annotation.Sampled import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Brush @@ -35,6 +39,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.xr.glimmer.ActionCard import androidx.xr.glimmer.Button +import androidx.xr.glimmer.ButtonGroup import androidx.xr.glimmer.Card import androidx.xr.glimmer.GlimmerTheme import androidx.xr.glimmer.Icon @@ -58,6 +63,7 @@ fun CardSampleUsage() { item { ImageCardWithTitleAndSubtitleAndLeadingIconSample() } item { LeadingImageCardSample() } item { TrailingImageCardSample() } + item { NonFocusableCardWithButtonGroupSample() } } } @@ -156,6 +162,19 @@ fun CardWithTitleAndSubtitleAndLeadingIconAndTrailingIconLongText() { } } +@Sampled +@Composable +fun NonFocusableCardWithButtonGroupSample() { + Column(verticalArrangement = Arrangement.spacedBy(GlimmerTheme.componentSpacingValues.large)) { + Card(focusable = false) { Text("This is a non-focusable card with a button group") } + ButtonGroup(modifier = Modifier.fillMaxWidth()) { + Button(onClick = {}) { Text("Button 1") } + Button(onClick = {}) { Text("Button 2") } + Button(onClick = {}) { Text("Button 3") } + } + } +} + // --- // ActionCard samples // --- @@ -239,6 +258,7 @@ fun ClickableImageCardWithTitleAndSubtitleAndLeadingIconSample() { // Leading/Trailing ImageCard samples // --- +@Sampled @Composable fun LeadingImageCardSample() { LeadingImageCard( @@ -250,6 +270,7 @@ fun LeadingImageCardSample() { } } +@Sampled @Composable fun TrailingImageCardSample() { TrailingImageCard( @@ -301,6 +322,12 @@ private fun CardWithTitleAndSubtitleAndLeadingIconAndTrailingIconLongTextPreview GlimmerTheme { CardWithTitleAndSubtitleAndLeadingIconAndTrailingIconLongText() } } +@Preview +@Composable +private fun NonFocusableCardWithButtonGroupPreview() { + GlimmerTheme { NonFocusableCardWithButtonGroupSample() } +} + @Preview @Composable private fun ActionCardWithTitlePreview() { diff --git a/xr/display-glasses-with-jetpack-compose-glimmer/references/card-source.md b/xr/display-glasses-with-jetpack-compose-glimmer/references/card-source.md index 206a52d..2b11e4a 100644 --- a/xr/display-glasses-with-jetpack-compose-glimmer/references/card-source.md +++ b/xr/display-glasses-with-jetpack-compose-glimmer/references/card-source.md @@ -44,9 +44,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.layout.layout -import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import kotlin.math.roundToInt @@ -58,7 +56,7 @@ import kotlin.math.roundToInt * [subtitle], which is placed on top of the [content]. A card fills the maximum width available by * default. * - * This Card is focusable - see the other [Card] overload for a clickable Card. + * This Card is focusable by default - see the other [Card] overload for a clickable Card. * * See [ActionCard] for a card with a primary action, and [ImageCard] for a card with an image. * @@ -76,7 +74,13 @@ import kotlin.math.roundToInt * A Card with a title, subtitle, and a leading icon: * * @sample androidx.xr.glimmer.samples.CardWithTitleAndSubtitleAndLeadingIconSample + * + * An example of usage when [focusable] is `false`, with focus handled by a [ButtonGroup] instead: + * + * @sample androidx.xr.glimmer.samples.NonFocusableCardWithButtonGroupSample * @param modifier the [Modifier] to be applied to this card + * @param focusable whether this card is focusable. When `false`, this card will not receive focus + * or participate in focus navigation * @param title optional title to be placed above [subtitle] and [content] * @param subtitle optional subtitle to be placed above [content], below [title] * @param leadingIcon optional leading icon to be placed before [content]. This is typically an @@ -100,6 +104,7 @@ import kotlin.math.roundToInt @Composable public fun Card( modifier: Modifier = Modifier, + focusable: Boolean = true, title: @Composable (() -> Unit)? = null, subtitle: @Composable (() -> Unit)? = null, leadingIcon: @Composable (() -> Unit)? = null, @@ -121,7 +126,7 @@ public fun Card( contentColor = contentColor, interactionSource = internalInteractionSource, ) - .focusable(interactionSource = internalInteractionSource), + .focusable(enabled = focusable, interactionSource = internalInteractionSource), title = title, subtitle = subtitle, image = null, @@ -141,8 +146,8 @@ public fun Card( * [subtitle], which is placed on top of the [content]. A card fills the maximum width available by * default. * - * This Card is focusable and clickable - see the other [Card] overload for a Card that is only - * focusable. + * This Card is focusable and clickable - see the other [Card] overload for a non-clickable variant + * with optional focus. * * See [ActionCard] for a card with a primary action, and [ImageCard] for a card with an image. * @@ -235,6 +240,12 @@ public fun Card( * For more documentation and samples of the other cards, see [Card]. * * @sample androidx.xr.glimmer.samples.ActionCardWithTitleSample + * + * ActionCard contains a single embedded [action]. If you need to have more than one action at a + * time, consider combining an unfocusable [Card] with a [ButtonGroup] instead. Cards can be + * configured as unfocusable by setting `focusable` to `false`. + * + * @sample androidx.xr.glimmer.samples.NonFocusableCardWithButtonGroupSample * @param action the action for this card. This should be a [Button], and represents the action * performed when a user interacts with this card. The action is placed inside the card along the * bottom, and fills up the width of the [ActionCard]. @@ -293,7 +304,8 @@ public fun ActionCard( * specified, [title] is placed on top of the [subtitle], which is placed on top of the [content], * below [image]. An image card fills the maximum width available by default. * - * This ImageCard is focusable - see the other [ImageCard] overload for a clickable ImageCard. + * This ImageCard is focusable by default - see the other [ImageCard] overload for a clickable + * ImageCard. * * For more documentation and samples of the other cards, see [Card]. * @@ -304,12 +316,18 @@ public fun ActionCard( * An ImageCard with a title, subtitle, and a leading icon: * * @sample androidx.xr.glimmer.samples.ImageCardWithTitleAndSubtitleAndLeadingIconSample + * + * An example of usage when [focusable] is `false`, with focus handled by a [ButtonGroup] instead: + * + * @sample androidx.xr.glimmer.samples.NonFocusableCardWithButtonGroupSample * @param image image to be placed at the top of the card. This image should typically fill the max * width available, for example using [androidx.compose.ui.layout.ContentScale.FillWidth]. Images * are constrained to a maximum aspect ratio (1.6) to avoid taking up too much vertical space, so * using a modifier such as [androidx.compose.foundation.layout.fillMaxSize] will result in an * image that fills the maximum aspect ratio. * @param modifier the [Modifier] to be applied to this card + * @param focusable whether this card is focusable. When `false`, this card will not receive focus + * or participate in focus navigation * @param title optional title to be placed above [subtitle] and [content], below [image] * @param subtitle optional subtitle to be placed above [content], below [title] * @param leadingIcon optional leading icon to be placed before [content]. This is typically an @@ -334,6 +352,7 @@ public fun ActionCard( public fun ImageCard( image: @Composable () -> Unit, modifier: Modifier = Modifier, + focusable: Boolean = true, title: @Composable (() -> Unit)? = null, subtitle: @Composable (() -> Unit)? = null, leadingIcon: @Composable (() -> Unit)? = null, @@ -355,7 +374,7 @@ public fun ImageCard( contentColor = contentColor, interactionSource = internalInteractionSource, ) - .focusable(interactionSource = internalInteractionSource), + .focusable(enabled = focusable, interactionSource = internalInteractionSource), title = title, subtitle = subtitle, image = image, @@ -378,8 +397,8 @@ public fun ImageCard( * specified, [title] is placed on top of the [subtitle], which is placed on top of the [content], * below [image]. An image card fills the maximum width available by default. * - * This ImageCard is focusable and clickable - see the other [ImageCard] overload for an ImageCard - * that is only focusable. + * This ImageCard is focusable and clickable - see the other [ImageCard] overload for a + * non-clickable variant with optional focus. * * For more documentation and samples of the other cards, see [Card]. * @@ -462,12 +481,21 @@ public fun ImageCard( * which is placed on top of the [content]. A leading image card fills the maximum width available * by default. * - * This LeadingImageCard is focusable - see the other [LeadingImageCard] overload for a clickable - * LeadingImageCard. + * This LeadingImageCard is focusable by default - see the other [LeadingImageCard] overload for a + * clickable LeadingImageCard. * + * A LeadingImageCard with a title and subtitle: + * + * @sample androidx.xr.glimmer.samples.LeadingImageCardSample + * + * An example of usage when [focusable] is `false`, with focus handled by a [ButtonGroup] instead: + * + * @sample androidx.xr.glimmer.samples.NonFocusableCardWithButtonGroupSample * @param image the image to be placed before the text content. This image is allocated 30% of the * card width. * @param modifier the [Modifier] to be applied to this card + * @param focusable whether this card is focusable. When `false`, this card will not receive focus + * or participate in focus navigation * @param title optional title to be placed above [subtitle] and [content] * @param subtitle optional subtitle to be placed above [content], below [title] * @param shape the [Shape] used to clip this card, and also used to draw the background and border @@ -487,6 +515,7 @@ public fun ImageCard( public fun LeadingImageCard( image: @Composable () -> Unit, modifier: Modifier = Modifier, + focusable: Boolean = true, title: @Composable (() -> Unit)? = null, subtitle: @Composable (() -> Unit)? = null, shape: Shape = LeadingImageCardDefaults.shape, @@ -497,7 +526,6 @@ public fun LeadingImageCard( content: @Composable () -> Unit, ) { val internalInteractionSource = interactionSource ?: remember { MutableInteractionSource() } - val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr SmallImageCardImpl( modifier = modifier @@ -507,12 +535,12 @@ public fun LeadingImageCard( contentColor = contentColor, interactionSource = internalInteractionSource, ) - .focusable(interactionSource = internalInteractionSource), + .focusable(enabled = focusable, interactionSource = internalInteractionSource), image = image, title = title, subtitle = subtitle, contentPadding = contentPadding, - isLeading = isLtr, + isLeading = true, content = content, ) } @@ -525,7 +553,7 @@ public fun LeadingImageCard( * by default. * * This LeadingImageCard is focusable and clickable - see the other [LeadingImageCard] overload for - * a LeadingImageCard that is only focusable. + * a non-clickable variant with optional focus. * * @param onClick called when this card item is clicked * @param image the image to be placed before the text content. This image is allocated 30% of the @@ -561,7 +589,6 @@ public fun LeadingImageCard( content: @Composable () -> Unit, ) { val internalInteractionSource = interactionSource ?: remember { MutableInteractionSource() } - val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr SmallImageCardImpl( modifier = modifier @@ -576,7 +603,7 @@ public fun LeadingImageCard( title = title, subtitle = subtitle, contentPadding = contentPadding, - isLeading = isLtr, + isLeading = true, content = content, ) } @@ -588,12 +615,21 @@ public fun LeadingImageCard( * [subtitle], which is placed on top of the [content]. A trailing image card fills the maximum * width available by default. * - * This TrailingImageCard is focusable - see the other [TrailingImageCard] overload for a clickable - * TrailingImageCard. + * This TrailingImageCard is focusable by default - see the other [TrailingImageCard] overload for a + * clickable TrailingImageCard. + * + * A TrailingImageCard with a title and subtitle: + * + * @sample androidx.xr.glimmer.samples.TrailingImageCardSample + * + * An example of usage when [focusable] is `false`, with focus handled by a [ButtonGroup] instead: * + * @sample androidx.xr.glimmer.samples.NonFocusableCardWithButtonGroupSample * @param image the image to be placed after the text content. This image is allocated 30% of the * card width. * @param modifier the [Modifier] to be applied to this card + * @param focusable whether this card is focusable. When `false`, this card will not receive focus + * or participate in focus navigation * @param title optional title to be placed above [subtitle] and [content] * @param subtitle optional subtitle to be placed above [content], below [title] * @param shape the [Shape] used to clip this card, and also used to draw the background and border @@ -613,6 +649,7 @@ public fun LeadingImageCard( public fun TrailingImageCard( image: @Composable () -> Unit, modifier: Modifier = Modifier, + focusable: Boolean = true, title: @Composable (() -> Unit)? = null, subtitle: @Composable (() -> Unit)? = null, shape: Shape = TrailingImageCardDefaults.shape, @@ -623,7 +660,6 @@ public fun TrailingImageCard( content: @Composable () -> Unit, ) { val internalInteractionSource = interactionSource ?: remember { MutableInteractionSource() } - val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr SmallImageCardImpl( modifier = modifier @@ -633,12 +669,12 @@ public fun TrailingImageCard( contentColor = contentColor, interactionSource = internalInteractionSource, ) - .focusable(interactionSource = internalInteractionSource), + .focusable(enabled = focusable, interactionSource = internalInteractionSource), image = image, title = title, subtitle = subtitle, contentPadding = contentPadding, - isLeading = !isLtr, + isLeading = false, content = content, ) } @@ -651,7 +687,7 @@ public fun TrailingImageCard( * width available by default. * * This TrailingImageCard is focusable and clickable - see the other [TrailingImageCard] overload - * for a TrailingImageCard that is only focusable. + * for a non-clickable variant with optional focus. * * @param onClick called when this card item is clicked * @param image the image to be placed after the text content. This image is allocated 30% of the @@ -687,7 +723,6 @@ public fun TrailingImageCard( content: @Composable () -> Unit, ) { val internalInteractionSource = interactionSource ?: remember { MutableInteractionSource() } - val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr SmallImageCardImpl( modifier = modifier @@ -702,7 +737,7 @@ public fun TrailingImageCard( title = title, subtitle = subtitle, contentPadding = contentPadding, - isLeading = !isLtr, + isLeading = false, content = content, ) }