diff --git a/device-ai/appfunctions/SKILL.md b/device-ai/appfunctions/SKILL.md index 16ae94c..b2bd18e 100644 --- a/device-ai/appfunctions/SKILL.md +++ b/device-ai/appfunctions/SKILL.md @@ -1,15 +1,19 @@ --- 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. +description: Analyzes Android apps to find user workflows to expose as AppFunctions, + such as creating a note, playing media, or sending a message. AI agents, voice commands, + and system shortcuts then run them without opening the app UI. Generates Kotlin + code with the androidx.appfunctions library, and refines KDoc and the app description + so that agents choose and call functions correctly. Use this skill when discovering, + implementing, documenting, testing, or upgrading AppFunctions in an Android app, + including @AppFunctionDeclaration, @AppFunctionSerializable, @AppFunctionServiceEntryPoint, + AppFunctionService, app_metadata.xml, appfn:instructions, adb shell cmd app_function, + and legacy @AppFunction or AppFunctionConfiguration.Provider code. Don't use for + building agents that call AppFunctions or for remote MCP servers. license: Complete terms in LICENSE.txt metadata: author: Google LLC - last-updated: '2026-10-25' + last-updated: '2026-10-28' keywords: - AppFunctions - Kotlin @@ -20,15 +24,14 @@ metadata: - 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. +Analyzes Android apps to identify key user workflows to expose as AppFunctions, +such as creating a note, playing media, or sending a message, for AI agents, +voice commands, and system shortcuts to run without opening 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 +Also refines KDoc documentation and the app description so AI agents correctly understand and use the provided functionality. ## Prerequisites @@ -37,6 +40,8 @@ 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. +Use `androidx.appfunctions:appfunctions` and the KSP compiler +`androidx.appfunctions:appfunctions-compiler`, version 1.0.0-beta01 or later. ## Workflows @@ -48,24 +53,27 @@ 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 3: KDoc refinement](references/kdoc-refinement-optimization.md)*: Optimize AppFunction KDoc and the app description 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. +When the user asks for a subset of the steps, run those steps and any step +that the critical constraints require. Then list the skipped steps and ask +whether to run them. 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. +- *[Migration to service entry point](references/migrate-to-service-entry-point.md)* : Migrates apps on AppFunctions 1.0.0-alpha09 or lower to the `AppFunctionServiceEntryPoint` architecture from 1.0.0-alpha10, and upgrades apps to 1.0.0-beta01. Load it when a user asks to migrate or upgrade existing AppFunctions code. When you find legacy `AppFunctionConfiguration.Provider` implementations, `@AppFunction` annotations, or `appfn:description` attributes, load it and ask the user before you migrate. ## 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. +- **Security**: Expose only safe, non-destructive tasks that the app's UI already offers on that form factor. Never give agents wide-ranging access to user data or system resources. Never require or return passwords, financial details, private keys, or other highly sensitive personal information. +- **Descriptions**: Describe what each function does, never how an agent must behave. Put call order across functions in the app description, not in function KDoc. ## 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). +- If you encounter issues when upgrading AppFunctions, such as errors on `@AppFunction`, which is no longer an annotation, see [Migration to service entry point](references/migrate-to-service-entry-point.md). diff --git a/device-ai/appfunctions/references/context.md b/device-ai/appfunctions/references/context.md index dc49d25..d3337a1 100644 --- a/device-ai/appfunctions/references/context.md +++ b/device-ai/appfunctions/references/context.md @@ -9,18 +9,27 @@ This compile-time AppFunctions architecture was introduced in version XML metadata and routes services at compile time. Legacy manual provider API: -This deprecated AppFunctions architecture applies to version +This 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. +a standalone `appfunctions-service` library. Version 1.0.0-alpha12 removed +`AppFunctionConfiguration` and `AppFunctionContext`. + +App description: +The app-level text that agents read to understand the app's AppFunctions. +It names the app's primary domain, lists its tasks, and states call order +across functions. Write it in the `appfn:instructions` attribute of +`app_metadata.xml`. Before 1.0.0-beta01, this attribute was +`appfn:description`. ## 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 +declare `@AppFunctionDeclaration` methods directly inside an abstract class +extending `AppFunctionService`. Before 1.0.0-alpha13, this annotation was +`@AppFunction`. This class uses `@AppFunctionServiceEntryPoint`, plus +`@AndroidEntryPoint` when the app uses Hilt. 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 index 583f67c..5d06ada 100644 --- a/device-ai/appfunctions/references/feature-discovery-analysis.md +++ b/device-ai/appfunctions/references/feature-discovery-analysis.md @@ -6,7 +6,7 @@ Analyzes Android codebases to identify and recommend high-value AppFunctions. 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." +3. **Evaluate AI value**: Prioritize tasks that users perform often, or that are easier to express in conversation than through UI navigation, 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 @@ -19,9 +19,18 @@ existing system actions. Focus on "mutations" (writing data) or "rich queries" ### Security and privacy -Don't recommend exposing functions that handle raw credentials, financial -secrets, or irreversible destructive actions without explicit user confirmation -steps. +Recommend only safe, non-destructive tasks that the app's UI already offers on +that form factor. Never recommend functions that require or return +passwords, financial details, private keys, or other highly sensitive personal +information, or that give agents wide-ranging access to user data or system +resources. On shared devices, such as TV, Android Auto, and Android +Automotive OS, respect the user's privacy. + +### Focused functions + +Recommend functions with a single, well-defined purpose. Never recommend +multi-purpose functions, several functions with similar intent, or more +functions than a use case needs, because agents confuse similar functions. ## Examples diff --git a/device-ai/appfunctions/references/implementation-configuration.md b/device-ai/appfunctions/references/implementation-configuration.md index fb9292c..1274489 100644 --- a/device-ai/appfunctions/references/implementation-configuration.md +++ b/device-ai/appfunctions/references/implementation-configuration.md @@ -8,7 +8,8 @@ handling system-wide configuration, and managing build dependencies. 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. +1. **Version check** : Use library version `1.0.0-beta01` or later from maven.google.com. +2. **Version catalog** : The following snippet uses version catalog aliases for `androidx.appfunctions:appfunctions` and `androidx.appfunctions:appfunctions-compiler`. Add both to `libs.versions.toml`. Without a version catalog, declare the coordinates and version directly. ```kotlin @@ -25,7 +26,12 @@ 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`. +`res/xml/app_metadata.xml`. Write the app description in the +`appfn:instructions` attribute, as described in the KDoc refinement step. +Before 1.0.0-beta01, this attribute was `appfn:description`. The optional +`appfn:displayDescription` attribute references a string resource with a +short, user-visible summary of what the app's functions let agents do. +Define that resource, or omit the attribute. ```xml @@ -67,39 +73,60 @@ within the `` tag:
+Set `android:name` to the class that KSP generates from the `serviceName` of +the `@AppFunctionServiceEntryPoint` class in Step 4, in that class's package. +Set the `android.app.appfunctions.v2` value to its `appFunctionXmlFileName` +plus `.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 the function with `@AppFunctionDeclaration(isDescribedByKDoc = true)`. Before 1.0.0-alpha13, this annotation was `@AppFunction`. - Annotate associated data classes with `@AppFunctionSerializable(isDescribedByKDoc = true)`. -2. **Parameter strategy** : + - Always set `isDescribedByKDoc` explicitly. Since 1.0.0-beta01, it has no default value. +2. **Naming and parameters** : + - **Function names** : Give each function a human-readable name that identifies its purpose, such as `createTask`. Never use a name that misstates what the code does or resembles another tool or service. - **Specificity**: Keep parameters specific. State objects must be unambiguous. + - **Necessity**: Define parameters only for data that the function uses. + - **Naming** : Name parameters after the expected input, such as `taskId`, `searchQuery`, or `recipientEmail`. Never use names such as `full_context` or `conversation_history`. These ambiguous names risk agents passing unneeded user data to the function. - **Optionality**: If a parameter isn't essential, make it optional with a default value. + - **Updates** : For functions that update existing items, distinguish "set to null" from "don't change". Declare each updatable parameter as a nullable `@AppFunctionSerializable` wrapper class that the app defines, such as `SetField`. A `null` wrapper means "don't change", and a wrapper that holds `null` means "set to null". Never unwrap a nullable field with `?:`, which turns "set to null" into "don't change". 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. + - **Native types** : `String`, `PendingIntent`, `Uri`, `LocalTime`, `LocalDate`, `LocalDateTime`, `Instant`, `ZoneId`. Prefer using `LocalDateTime` or `Instant` for date and time fields. - **Custom objects** : Classes annotated with `@AppFunctionSerializable`. - **Collections** : `List` of any supported non-primitive type + - Use these types instead of defining custom date, time, or link classes. The library represents them the same way in every app. 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`. + - Optional parameters MUST be nullable, primitive, list, or array types. + - Declare defaults that match the type's empty state, such as `0` for `Int`, `null` for nullable objects, and `emptyList()` for `List`. + - When the agent omits a parameter, the library passes this empty value, not the declared default. Resolve meaningful defaults in the function body. 6. **Error handling** : - - Throw subclasses of `androidx.appfunctions.AppFunctionException` to report errors to callers. + - Throw the `androidx.appfunctions.AppFunctionException` subclass that matches the cause, with a plain-language message. For example, throw `AppFunctionInvalidArgumentException` for an invalid argument, or `AppFunctionElementNotFoundException` for a missing item. + - Never let other exceptions escape. The agent receives them as `AppFunctionAppUnknownException`, which doesn't identify the failure. 7. **Security** : - - Don't expose highly sensitive user data, such as passwords or financial details. - - Don't expose irreversible destructive actions without confirmation steps. + - Expose only safe, non-destructive tasks that the app's UI already offers on that form factor. + - Never give agents wide-ranging access to user data or system resources. + - Never require or return passwords, financial details, private keys, or other highly sensitive personal information. Share other personal or confidential data only when the user explicitly consents to share it for the requested action. + - Never perform actions that the user didn't request. Actions that the user scheduled, such as sending a message later, are allowed. +8. **Opening screens** : + - To let the agent open a screen in the app, return a `PendingIntent`. Create it from an explicit intent with `FLAG_IMMUTABLE`, which locks where it leads. ### 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. +generates the concrete service class and XML schema. Declare only one entry +point class: before `Build.VERSION_CODES.CINNAMON_BUN`, Android allows only +one service per app to handle the +`android.app.appfunctions.AppFunctionService` action. #### Recommended approach with Hilt @@ -169,10 +196,12 @@ abstract class BaseAppFunctionServiceLocator : AppFunctionService() { ### 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 +Don't attempt to make an AppFunction class or method OS-agnostic---AppFunctions +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. +orchestrators) to execute the behavior within the `@AppFunctionDeclaration` +methods rather than creating redundant abstraction layers around the OS service.
@@ -183,11 +212,15 @@ than creating redundant abstraction layers around the OS service. **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. +Declare every primary constructor parameter as a `val` property, and keep the +primary constructor public. Other constructor parameters cause a compiler +error. ### Package integrity -Configuration APIs and the `@AppFunction` annotation are located in -`androidx.appfunctions`. +The `@AppFunctionDeclaration` annotation, `AppFunctionService`, and +`AppFunctionName` are located in `androidx.appfunctions`. Before 1.0.0-beta01, +`AppFunctionName` was in `androidx.appfunctions.metadata`. ## Examples @@ -270,6 +303,14 @@ abstract class BaseTaskAppFunctionService : AppFunctionService() { ## Troubleshooting +### Error: Build fails after adding or upgrading the library + +**Cause** : An annotation omits `isDescribedByKDoc`, which has no default value +since 1.0.0-beta01, or the module's `compileSdk` is lower than 37. + +**Solution** : Set `isDescribedByKDoc = true` on every `@AppFunctionDeclaration` +and `@AppFunctionSerializable`, and set `compileSdk` to 37 or higher. + ### Error: "AppFunction unavailable" or "Metadata missing" **Cause**: The AppSearch indexing failed to extract the schema correctly. @@ -278,5 +319,4 @@ abstract class BaseTaskAppFunctionService : AppFunctionService() { 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 +3. Confirm the `ksp("androidx.appfunctions:appfunctions-compiler")` dependency is correctly applied. \ 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 index c0a3209..e1768c4 100644 --- a/device-ai/appfunctions/references/kdoc-refinement-optimization.md +++ b/device-ai/appfunctions/references/kdoc-refinement-optimization.md @@ -1,41 +1,62 @@ -Optimizes AppFunction KDoc for AI agents and Model Context Protocol. +Optimizes AppFunction KDoc and the app description 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"**. +1. **Identify the core outcome** : Start the description with a third-person verb and the resource it acts on. Examples: "Searches contacts", "Creates a task", and "Updates a channel". Put key information first, because some agents don't read the whole description. Focus on the *user benefit*, not the code implementation. +2. **Behavior** : State conditional behavior, for example, "If `query` is null, returns the 3 most recent contacts." Never describe call order across functions, such as "Search for the contact first." Write call order in the app description instead. 3. **Parameter documentation** : - - For **functions** : Use specific `@param` tags. Isolate validation rules and default values here. + - For **functions** : Use specific `@param` tags. Isolate validation rules and default values here. Describe the result with `@return`. Only the summary, `@param`, and `@return` reach agents; other tags, such as `@throws`, don't. - 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." +4. **Error surface mapping** : The agent receives the type and message of the exception that a function throws. Throw the matching `AppFunctionException` subclass with a plain-language message, for example, "No contact matches `query`." -### Workflow: Global app description for server instructions +### Workflow: App description -When writing the `appfn:description` for `app_metadata.xml`, follow these -instructions: +When writing the app description in the `appfn:instructions` attribute of +`app_metadata.xml`, follow these instructions. Before 1.0.0-beta01, this +attribute was `appfn:description`. -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** : +1. **Lead with the domain**: Start with the app's primary domain, then give a concise list of the tasks that its AppFunctions perform. +2. **Capture cross-function relationships** : Explain dependencies or sequences between functions, for example, "Call 'searchContacts' to get the `endpointValue` for 'send'." Agents decide whether to follow the app description, so never rely on it to enforce critical behavior. +3. **Document operational patterns**: Guide the LLM on token-conserving usage, for example, "Use 'batch_update' over multiple 'update' calls." +4. **Specify constraints** : State constraints that apply across functions, for example, "File operations limited to workspace" or "Rate limit: 10 requests per minute." Put limits on a single parameter in that function's `@param` tag. +5. **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 +### Descriptive, not commanding Describe what the function *does* , not what the LLM *must* do. Avoid phrases -like "You must call this..." in favor of "This function provides...". +like "You must call this..." or "You must ask the user for...". Start with the +outcome instead, such as "Creates a task with a title and due date." ### No "fluff" Remove conversational padding like "This method is used to..." or "Helpful -for...". Be concise and technical. +for...". Be concise, specific, and neutral. + +### No emphasis markup + +Never use excessive capitalization or special characters to draw attention, +such as `**CRITICAL**` or `VERY IMPORTANT`. + +### Accurate and self-contained + +The description matches what the code does, including its parameters and +output. A function described as read-only never writes or deletes data. Never +describe more than the function does, because agents then call it for +unrelated requests. Never reference or try to influence other AppFunctions, +including other apps' AppFunctions. + +### Instruction annotations override KDoc + +If an element has `@AppFunctionInstruction`, its text replaces that element's +KDoc in the generated metadata. Refine the annotation text instead of the KDoc. ### Inline KDoc for serializables @@ -54,17 +75,17 @@ for these classes. **Refined**: /** - * Search for message recipients by name or email. - * Required workflow: Call this before "sendMessage" to obtain valid recipient IDs. + * Searches for message recipients by name or email. * @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 +### Example: App description **Refined**: - This app provides functions for task management and team collaboration. + This app manages tasks for teams: creating and assigning tasks, updating task + status, and searching users. Operational Patterns: - Always use 'searchUsers' to resolve user handles to internal IDs before calling 'assignTask'. diff --git a/device-ai/appfunctions/references/migrate-to-service-entry-point.md b/device-ai/appfunctions/references/migrate-to-service-entry-point.md index f350f61..f1b485e 100644 --- a/device-ai/appfunctions/references/migrate-to-service-entry-point.md +++ b/device-ai/appfunctions/references/migrate-to-service-entry-point.md @@ -1,7 +1,8 @@ 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`. +`1.0.0-alpha10`. Then follow the Upgrade to 1.0.0-beta01 section. Apps that +already use `@AppFunctionServiceEntryPoint` only need that section. *** ** * ** *** @@ -21,7 +22,7 @@ In version `1.0.0-alpha10` featuring `@AppFunctionServiceEntryPoint`: - 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 +### Required changes from 1.0.0-alpha09 to the entry point API When focusing solely on the mandatory API changes required by the new `@AppFunctionServiceEntryPoint` architecture, the migration consists of four @@ -29,7 +30,7 @@ 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`. +3. **Annotation and context decoupling** : Move AppFunction annotations to the new wrapper methods as `@AppFunctionDeclaration`. 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. *** ** * ** *** @@ -52,7 +53,7 @@ within the main `appfunctions` artifact. # libs.versions.toml [versions] - appfunctions = "1.0.0-alpha10" + appfunctions = "1.0.0-beta01" [libraries] androidx-appfunctions = { module = "androidx.appfunctions:appfunctions", version.ref = "appfunctions" } @@ -60,7 +61,7 @@ within the main `appfunctions` artifact. 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. +> **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-beta01` directly. *** ** * ** *** @@ -166,12 +167,12 @@ Update your `Application` class by removing ### Avoid redundant abstraction layers -Don't attempt to make an `AppFunction` class or method OS-agnostic---AppFunctions +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. +orchestrators, to execute the behavior within the `@AppFunctionDeclaration` +methods rather than creating redundant abstraction layers around the OS service. *** ** * ** *** @@ -205,6 +206,29 @@ within the `` tag:
+Set `android:name` to the class that KSP generates from `serviceName`, and +the `android.app.appfunctions.v2` value to `appFunctionXmlFileName` plus +`.xml`. + +*** ** * ** *** + +## Upgrade to 1.0.0-beta01 + +Apply these changes to every app that uses `@AppFunctionServiceEntryPoint`, +including apps migrated with the preceding steps: + +1. **Annotation rename** : Replace each `@AppFunction` annotation with `@AppFunctionDeclaration`, and update its import. Match the whole name, so annotations such as `@AppFunctionSerializable` stay unchanged. Version 1.0.0-alpha13 renamed this annotation. +2. **Explicit KDoc flag** : Set `isDescribedByKDoc = true` on every `@AppFunctionDeclaration`, `@AppFunctionSerializable`, and `@AppFunctionSignature`. Since 1.0.0-beta01, it has no default value. +3. **App description attribute** : In `app_metadata.xml`, rename `appfn:description` to `appfn:instructions`. +4. **Imports** : Import `AppFunctionName` from `androidx.appfunctions`, not from `androidx.appfunctions.metadata`. +5. **Removed APIs** : Remove remaining uses of `AppFunctionConfiguration` and `AppFunctionContext`. Version 1.0.0-alpha12 removed them. +6. **Other API changes** : If the app uses these APIs, update them: + - `@AppFunctionStringValueConstraint`: Replace regular expression patterns with `patternMatchers` (1.0.0-beta01). + - `AppFunctionResourceContainer` and `AppFunctionTextResource`: Opt in with `@OptIn(ExperimentalAppFunctionsApi::class)` (1.0.0-beta01). + - `AppFunctionAppMetadata.description`: Read `instructions` instead (1.0.0-beta01). + - `AppFunctionMetadata.isEnabled`: Call `AppFunctionManager.getAppFunctionStates` instead (1.0.0-alpha11). + - `ObserveAppFunctionsEvent`: Use `AppFunctionsChangeEvent` instead (1.0.0-alpha13). + *** ** * ** *** ## Verification and troubleshooting diff --git a/device-ai/litert/litert-runtime/SKILL.md b/device-ai/litert/litert-runtime/SKILL.md index 066d272..0a4a18e 100644 --- a/device-ai/litert/litert-runtime/SKILL.md +++ b/device-ai/litert/litert-runtime/SKILL.md @@ -11,7 +11,7 @@ description: Configures and implements LiteRT (.tflite) on-device inference on A license: Complete terms in LICENSE.txt metadata: author: Google LLC - last-updated: '2026-10-23' + last-updated: '2026-10-28' keywords: - LiteRT - CompiledModel @@ -398,9 +398,33 @@ instead: 3. **Swap the dependencies** : Delete every legacy artifact line (never leave it commented out) and add the dependencies of the runtime that Step 1 picks: by default those of [LiteRT in Google Play services](references/play-services.md), with the same fallback to the bundled runtime when Gradle can't find `play-services-litert`. On the Play services runtime, replace the Support library classes as [LiteRT in Google Play services](references/play-services.md) describes. On the bundled runtime, an app keeps them with `com.google.ai.edge.litert:litert-support:2.3.0`, with the package `org.tensorflow.lite.support` renamed to `com.google.ai.edge.litert.support` ([LiteRT Support migration guide](https://github.com/google-ai-edge/LiteRT/blob/main/litert/support_lib/MIGRATION_GUIDE.md)). 4. **Replace the delegates** : `GpuDelegate` becomes `CompiledModel.Options(Accelerator.GPU, Accelerator.CPU)`, so the CPU runs the operators that the GPU doesn't support. An NNAPI choice in the app maps to the same GPU option, not to `Accelerator.CPU` alone or to an NPU accelerator. `setNumThreads(n)` becomes new CPU options with `cpuOptions = CompiledModel.CpuOptions(numThreads = n)`. -5. **Rewrite the inference code** : Replace `Interpreter` and `FileUtil.loadMappedFile` with the `Classifier` helper of Step 2, created and run only on the single-thread executor of Step 3, never in a constructor that the main thread calls. When no caller retries on the CPU as in Step 3, create the helper's GPU option with the CPU as a second accelerator (`CompiledModel.Options(Accelerator.GPU, Accelerator.CPU)`). -6. **Verify in two stages**: The build passes and the searches for legacy references print nothing; then the migrated output matches the golden output on a device. -7. **Report what changed and what didn't migrate** : When the app used NNAPI, tell the user that it now runs on the GPU with the CPU as a second accelerator, which changes the speed and the numerical results of a quantized model that ran on a DSP or an NPU before; point them to [NPU acceleration with LiteRT](https://ai.google.dev/edge/litert/next/npu) for the NPU. Tell the user about the TensorFlow Lite Task Library, C++ modules, and Select TensorFlow operators, which this skill doesn't migrate. +5. **Rewrite the inference code** : Replace `Interpreter` and + `FileUtil.loadMappedFile` with the `Classifier` helper of Step 2, created and + run only on the single-thread executor of Step 3, never in a constructor + that the main thread calls. When no caller retries on the CPU as in Step 3, + create the helper's GPU option with the CPU as a second accelerator + (`CompiledModel.Options(Accelerator.GPU, Accelerator.CPU)`). An + `interpreter.runSignature(inputs, outputs, key)` call becomes the helper's + buffers and `run` call with the signature key, as follows. This form runs on + both runtimes, also after the app changes runtime or upgrades LiteRT; when + the signature has more than one input or output, you must follow the + signature rules in [migration](references/migration.md): + + inputs += model.createInputBuffers("serving_default") + outputs += model.createOutputBuffers("serving_default") + model.run(inputs, outputs, "serving_default") + +6. **Verify in two stages**: The build passes and the searches for legacy + references print nothing; then the migrated output matches the golden + output on a device. + +7. **Report what changed and what didn't migrate** : When the app used NNAPI, + tell the user that it now runs on the GPU with the CPU as a second + accelerator, which changes the speed and the numerical results of a + quantized model that ran on a DSP or an NPU before; point them to + [NPU acceleration with LiteRT](https://ai.google.dev/edge/litert/next/npu) for the NPU. Tell the user about the + TensorFlow Lite Task Library, C++ modules, and Select TensorFlow operators, + which this skill doesn't migrate. Migration mandates that apply to every migrated app: diff --git a/device-ai/litert/litert-runtime/references/migration.md b/device-ai/litert/litert-runtime/references/migration.md index 10a9fad..de75e80 100644 --- a/device-ai/litert/litert-runtime/references/migration.md +++ b/device-ai/litert/litert-runtime/references/migration.md @@ -163,7 +163,7 @@ Keep the app's own classes and their public functions as the callers. - **Inference** : `run` on `ByteBuffer` objects becomes `writeFloat`, `run`, and `readFloat` on buffers that the helper creates once. Use `writeInt8` and `readInt8` for int8 and uint8 tensors. - **Thread count** : `setNumThreads` becomes `CpuOptions(numThreads = n)` on new CPU options; combine `numThreads` with `xnnPackFlags = 0x23` and `xnnPackWeightCachePath` in the same `CpuOptions` when memory also matters. - **Tensor shapes** : `getInputTensor` and its `shape` become a lookup by name. `getInputTensorType` throws `LiteRtException` for `uint8` tensors; for the size, use `getInputBufferRequirements(name, signature).bufferSize`, which is in bytes for every element type. `resizeInput` has no counterpart in 2.3.0. -- **Signatures** : `runSignature` becomes `run` with maps from the signature's input and output names to buffers. Every by-name call (`run` with maps, `getInputTensorType`, `getInputBufferRequirements`) takes the names in the signature, such as `x` or `args_0` and `output_0`, not the graph tensor names such as `serving_default_x:0`. When the model has no signatures, the names are its input and output tensor names (such as `images` and `Softmax`). `CompiledModel` has no call that lists them, so take them from the legacy code (Step 2). A missing or wrong name in an output map crashes the process (JNI abort) instead of throwing, so check the names before the first `run`. +- **Signatures** : `runSignature(inputs, outputs, key)` becomes `run(inputs, outputs, key)` on the lists that `createInputBuffers(key)` and `createOutputBuffers(key)` return, created once with the model. Use this List overload by default on both runtimes, and always on the Google Play services runtime: with `litert-api` 2.3.0, the `Map` overload of `run` aborts the process on that runtime, and the List overload doesn't, so code that uses it keeps running when the app changes runtime or upgrades LiteRT. The lists follow the order of the signature's inputs and outputs in the model, so a signature with one input and one output needs no names. For a signature with more than one input or output on the bundled runtime, create each buffer by name with `createInputBuffer(name, key)` and `createOutputBuffer(name, key)`, and pass maps from those names to `run(inputs, outputs, key)`; on the Google Play services runtime, keep the lists and check each position against the golden output. Every by-name call (`createInputBuffer`, `createOutputBuffer`, `run` with maps, `getInputTensorType`, `getInputBufferRequirements`) takes the names in the signature, such as `x` or `args_0` and `output_0`, not the graph tensor names such as `serving_default_x:0`. When the model has no signatures, the names are its input and output tensor names (such as `images` and `Softmax`). `CompiledModel` has no call that lists them, so take them from the legacy code (Step 2). A missing or wrong name in an output map crashes the process (JNI abort) instead of throwing, so check the names before the first `run`. - **Closing** : Closing the interpreter becomes the helper's `close`. // Replaces Interpreter.Options.setNumThreads. Create new options; never change @@ -182,19 +182,39 @@ Keep the app's own classes and their public functions as the callers. fun inputSizeBytes(model: CompiledModel, inputName: String, signature: String): Int = model.getInputBufferRequirements(inputName, signature).bufferSize - // Replaces interpreter.runSignature. Create the buffers once with + // Replaces interpreter.runSignature(mapOf("args_0" to input), + // mapOf("output_0" to output), "serving_default") for a signature with one + // input and one output, on both runtimes. The Classifier helper of Step 2 of + // the skill creates its buffers once in init with // model.createInputBuffers(signature) and model.createOutputBuffers(signature), - // and reuse them for every call. The map keys are the signature's input and - // output names (such as "args_0" and "output_0"). + // and calls this from infer. fun runSignature( model: CompiledModel, signature: String, - inputs: Map, - outputs: Map - ) { + inputs: List, + outputs: List, + input: FloatArray + ): FloatArray { + inputs[0].writeFloat(input) model.run(inputs, outputs, signature) + return outputs[0].readFloat() } + // Bundled runtime only, for a signature with more than one input or output: + // create each buffer by its signature name, once. Never use the map overload + // of run on the Google Play services runtime, where it aborts with litert-api + // 2.3.0; change it to the List overload when the app moves to that runtime. + fun createSignatureBuffers( + model: CompiledModel, + signature: String, + inputNames: List, + outputNames: List + ): Pair, Map> = + inputNames.associateWith { model.createInputBuffer(it, signature) } to + outputNames.associateWith { model.createOutputBuffer(it, signature) } + + // Then, for every call: model.run(inputs, outputs, signature) + ### With the Support library Migrated code must not import any `org.tensorflow.lite` class, including diff --git a/device-ai/litert/litert-runtime/references/play-services.md b/device-ai/litert/litert-runtime/references/play-services.md index f0b3c2b..8b7306e 100644 --- a/device-ai/litert/litert-runtime/references/play-services.md +++ b/device-ai/litert/litert-runtime/references/play-services.md @@ -139,21 +139,22 @@ buffers, `infer`, and `close` stay as in the skill. 1. Check the first table row. When it matches, stop and tell the user. 2. Swap the dependencies as in "Gradle". 3. Replace `Environment.create` with `PlayServicesEnvironment`, and pass the `Environment` to every `CompiledModel.create` call. When a `create` call omits the `Environment`, add it. -4. Keep the buffer and inference code. When no caller retries on the CPU as in Step 3, pass `Accelerator.CPU` next to `Accelerator.GPU` in `CompiledModel.Options`. +4. Keep the buffer and inference code. When no caller retries on the CPU as in Step 3, pass `Accelerator.CPU` next to `Accelerator.GPU` in `CompiledModel.Options`. Change every `model.run` call that passes `Map` arguments to the List overload (see "From the bundled TensorFlow Lite Interpreter"), because the map overload aborts on this runtime with `litert-api` 2.3.0. ### From the bundled TensorFlow Lite Interpreter Follow the migration guide of the skill. In its step 3, add the dependencies of "Gradle" instead of the bundled ones, and in its step 5, add `PlayServicesEnvironment` of "Create the Environment" and create the -`Classifier` with its `Environment`. For a signature call, pass -the `List` lists from `createInputBuffers(signature)` and -`createOutputBuffers(signature)` to `model.run(inputs, outputs, signature)`, -not `Map`, which aborts with `SIGABRT` in `17.0.0` -looking for `libLiteRt.so`. The Play services runtime -doesn't support `litert-support`, so replace every Support class, including the -ones under `com.google.ai.edge.litert.support`, with `preprocess` of Step 4 of -the skill and labels read from assets. +`Classifier` with its `Environment`. For a signature call, pass the +`List` lists from `createInputBuffers(signature)` and +`createOutputBuffers(signature)` to `model.run(inputs, outputs, signature)`, not +`Map`, which aborts with `SIGABRT` looking for +`libLiteRt.so` with `litert-api` 2.3.0. Keep the List overload when you upgrade +`play-services-litert` or `litert-api`. The Play services runtime doesn't +support `litert-support`, so replace every Support class, including the ones +under `com.google.ai.edge.litert.support`, with `preprocess` of Step 4 of the +skill and labels read from assets. ### From the Interpreter API in Google Play services diff --git a/identity/verified-email/SKILL.md b/identity/verified-email/SKILL.md index 31a75d0..b95c774 100644 --- a/identity/verified-email/SKILL.md +++ b/identity/verified-email/SKILL.md @@ -8,7 +8,7 @@ description: Provides a complete workflow for implementing verified email retrie license: Complete terms in LICENSE.txt metadata: author: Google LLC - last-updated: '2026-09-16' + last-updated: '2026-10-28' keywords: - implementation - Android @@ -120,7 +120,7 @@ actions: ## 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 +[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 @@ -344,7 +344,7 @@ additional metadata as well along with verified email: */ > [!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). +> **Important:** We highly recommend that after receiving the verified email, you trigger Credential Manager's [passkey creation](references/android/identity/passkeys/create-passkeys.md). ## Server-side validation for account creation 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 deleted file mode 100644 index c66bf50..0000000 --- a/identity/verified-email/references/android/identity/digital-credentials/credential-verifier.md +++ /dev/null @@ -1,166 +0,0 @@ -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 index 4946eb4..52a7443 100644 --- 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 @@ -109,7 +109,7 @@ The request contains the following key information: - `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 + > **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) 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 @@ -237,7 +237,7 @@ additional metadata as well along with verified email: */ > [!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). +> **Important:** We highly recommend that after receiving the verified email, you trigger Credential Manager's [passkey creation](https://developer.android.com/identity/passkeys/create-passkeys). ## Server-side validation for account creation diff --git a/identity/verified-email/references/android/identity/digital-credentials/index.md b/identity/verified-email/references/android/identity/digital-credentials/index.md index 83b6156..8e933b0 100644 --- a/identity/verified-email/references/android/identity/digital-credentials/index.md +++ b/identity/verified-email/references/android/identity/digital-credentials/index.md @@ -21,8 +21,8 @@ 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). +- **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/credential-verifier/about-verifying-phone-numbers). +- **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/about-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. @@ -31,7 +31,7 @@ the following: 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. +- **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/present-credentials) process. - **Verifiers**: Verifiers are apps that verify and use digital credentials. Credential Manager's Digital Credentials API orchestrates the interaction @@ -106,7 +106,7 @@ To test the flow, complete the following steps: - 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 holder apps, see the [holder guide](https://developer.android.com/identity/digital-credentials/credential-holder/present-credentials). +- For more information about verifying users based on digital credentials, see the [verifier guide](https://developer.android.com/identity/digital-credentials/credential-verifier/request-credential). - 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/jetpack-compose/ink/ink-app-builder/SKILL.md b/jetpack-compose/ink/ink-app-builder/SKILL.md index c85650f..8870fd0 100644 --- a/jetpack-compose/ink/ink-app-builder/SKILL.md +++ b/jetpack-compose/ink/ink-app-builder/SKILL.md @@ -11,7 +11,7 @@ description: Adds inking and freehand drawing capabilities to an Android app usi license: Complete terms in LICENSE.txt metadata: author: Google LLC - last-updated: '2026-10-24' + last-updated: '2026-10-28' keywords: - Android - Jetpack Ink diff --git a/jetpack-compose/ink/ink-app-builder/references/android/develop/ui/compose/touch-input/stylus-input/ink-api-brush-apis.md b/jetpack-compose/ink/ink-app-builder/references/android/develop/ui/compose/touch-input/stylus-input/ink-api-brush-apis.md index 01b2c81..f3fcca8 100644 --- a/jetpack-compose/ink/ink-app-builder/references/android/develop/ui/compose/touch-input/stylus-input/ink-api-brush-apis.md +++ b/jetpack-compose/ink/ink-app-builder/references/android/develop/ui/compose/touch-input/stylus-input/ink-api-brush-apis.md @@ -2,6 +2,19 @@ The [`Brush`](https://developer.android.com/reference/kotlin/androidx/ink/brush/ define the visual style of your strokes. You can create brushes with different colors, sizes, and families to achieve a variety of looks. + +## Android skills + +[View on GitHub](https://github.com/android/skills/tree/main/jetpack-compose/ink/ink-custom-brush-builder) + +### Create custom Ink brushes + +Use the `ink-custom-brush-builder` [Android skill](https://developer.android.com/tools/agents/android-skills) to design custom BrushFamily styles in Jetpack Compose with custom tips, dynamic behaviors, textures, and coats. To install the skill from the [Android CLI](https://developer.android.com/tools/agents/android-cli), run: + + android skills add ink-custom-brush-builder + +
+ ## Create a brush To create a brush, use the Compose [`Brush`](https://developer.android.com/reference/kotlin/androidx/ink/brush/Brush) diff --git a/jetpack-compose/ink/ink-custom-brush-builder/SKILL.md b/jetpack-compose/ink/ink-custom-brush-builder/SKILL.md index 92fbec2..7f983a2 100644 --- a/jetpack-compose/ink/ink-custom-brush-builder/SKILL.md +++ b/jetpack-compose/ink/ink-custom-brush-builder/SKILL.md @@ -12,7 +12,7 @@ description: Creates custom BrushFamily definitions for an Android app using Jet license: Complete terms in LICENSE.txt metadata: author: Google LLC - last-updated: '2026-10-24' + last-updated: '2026-10-28' keywords: - Android - Jetpack Ink diff --git a/jetpack-compose/ink/ink-custom-brush-builder/references/android/develop/ui/compose/touch-input/stylus-input/ink-api-brush-apis.md b/jetpack-compose/ink/ink-custom-brush-builder/references/android/develop/ui/compose/touch-input/stylus-input/ink-api-brush-apis.md index 01b2c81..f3fcca8 100644 --- a/jetpack-compose/ink/ink-custom-brush-builder/references/android/develop/ui/compose/touch-input/stylus-input/ink-api-brush-apis.md +++ b/jetpack-compose/ink/ink-custom-brush-builder/references/android/develop/ui/compose/touch-input/stylus-input/ink-api-brush-apis.md @@ -2,6 +2,19 @@ The [`Brush`](https://developer.android.com/reference/kotlin/androidx/ink/brush/ define the visual style of your strokes. You can create brushes with different colors, sizes, and families to achieve a variety of looks. + +## Android skills + +[View on GitHub](https://github.com/android/skills/tree/main/jetpack-compose/ink/ink-custom-brush-builder) + +### Create custom Ink brushes + +Use the `ink-custom-brush-builder` [Android skill](https://developer.android.com/tools/agents/android-skills) to design custom BrushFamily styles in Jetpack Compose with custom tips, dynamic behaviors, textures, and coats. To install the skill from the [Android CLI](https://developer.android.com/tools/agents/android-cli), run: + + android skills add ink-custom-brush-builder + +
+ ## Create a brush To create a brush, use the Compose [`Brush`](https://developer.android.com/reference/kotlin/androidx/ink/brush/Brush) diff --git a/play/engage-sdk-integration/SKILL.md b/play/engage-sdk-integration/SKILL.md index 24e957a..70196e8 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-10-17' + last-updated: '2026-10-28' keywords: - android - engage 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 e90b6b0..8499c09 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 @@ -195,13 +195,14 @@ The following chart outlines attributes and requirements for each type. |---|---|---| | Name | **Required** | | | Poster images | **Required** | At least one image is required, and must be provided with an aspect ratio. (Landscape is preferred but passing both portrait and landscape images for different scenarios is recommended.) See [Image Specifications](https://developer.android.com/guide/playcore/engage/watch#image-specs) for guidance. | -| Playback uri | **Required** | The deep link to the provider app to start playing the movie. Note: You can use deep links for attribution. [Refer to this FAQ](https://developer.android.com/guide/playcore/engage/faq#deeplinks-attribution) | +| Playback uri | **Required** for mobile | The deep link to the provider app to start playing the movie. Note: You can use deep links for attribution. [Refer to this FAQ](https://developer.android.com/guide/playcore/engage/faq#deeplinks-attribution) | +| Platform specific playback URIs | Optional for mobile; required for Google TV | Platform-specific deep links to the provider app to start playing the movie. | | Info page uri | Optional | The deep link to the provider app to show details about the movie. Note: You can use deep links for attribution. [Refer to this FAQ](https://developer.android.com/guide/playcore/engage/faq#deeplinks-attribution) | | Release date | Optional | In epoch milliseconds. | -| Availability | **Required** | AVAILABLE: The content is available to the user without any further action. FREE_WITH_SUBSCRIPTION: The content is available after the user purchases a subscription. PAID_CONTENT: The content requires user purchase or rental. PURCHASED: The content has been purchased or rented by the user. | +| Availability | Optional | AVAILABLE: The content is available to the user without any further action. FREE_WITH_SUBSCRIPTION: The content is available after the user purchases a subscription. PAID_CONTENT: The content requires user purchase or rental. PURCHASED: The content has been purchased or rented by the user. | | Offer price | Optional | Free text | | Duration | **Required** | In milliseconds. | -| Genre | **Required** | Free text | +| Genre | Optional | Free text | | Content ratings | Optional | Free text, follow the industry standard. ([Example](https://www.spectrum.net/support/tv/tv-and-movie-ratings-descriptions)) | | Call to action text | Optional | Free text to be displayed as a call to action. | | Tags | Optional | List of tags associated with the entity. | @@ -217,12 +218,13 @@ The following chart outlines attributes and requirements for each type. | Poster images | **Required** | At least one image is required, and must be provided with an aspect ratio. (Landscape is preferred but passing both portrait and landscape images for different scenarios is recommended.) See [Image Specifications](https://developer.android.com/guide/playcore/engage/watch#image-specs) for guidance. | | Info page uri | **Required** | The deep link to the provider app to show the details of the TV show. Note: You can use deep links for attribution. [Refer to this FAQ](https://developer.android.com/guide/playcore/engage/faq#deeplinks-attribution) | | Playback uri | Optional | The deep link to the provider app to start playing the TV show. Note: You can use deep links for attribution. [Refer to this FAQ](https://developer.android.com/guide/playcore/engage/faq#deeplinks-attribution) | +| Platform specific playback URIs | Optional for mobile; required for Google TV | Platform-specific deep links to the provider app to start playing the TV show. | | First episode air date | Optional | In epoch milliseconds. | | Latest episode air date | Optional | In epoch milliseconds. | -| Availability | **Required** | AVAILABLE: The content is available to the user without any further action. FREE_WITH_SUBSCRIPTION: The content is available after the user purchases a subscription. PAID_CONTENT: The content requires user purchase or rental. PURCHASED: The content has been purchased or rented by the user. | +| Availability | Optional | AVAILABLE: The content is available to the user without any further action. FREE_WITH_SUBSCRIPTION: The content is available after the user purchases a subscription. PAID_CONTENT: The content requires user purchase or rental. PURCHASED: The content has been purchased or rented by the user. | | Offer price | Optional | Free text | -| Season count | **Required** | Positive integer | -| Genre | **Required** | Free text | +| Season count | Optional | Positive integer | +| Genre | Optional | Free text | | Content ratings | Optional | Free text, follow the industry standard. ([Example](https://www.spectrum.net/support/tv/tv-and-movie-ratings-descriptions)) | | Call to action text | Optional | Free text to be displayed as a call to action. | | Tags | Optional | List of tags associated with the entity. | @@ -238,13 +240,13 @@ The following chart outlines attributes and requirements for each type. | Poster images | **Required** | At least one image is required, and must be provided with an aspect ratio. (Landscape is preferred but passing both portrait and landscape images for different scenarios is recommended.) See [Image Specifications](https://developer.android.com/guide/playcore/engage/watch#image-specs) for guidance. | | Info page uri | **Required** | The deep link to the provider app to show the details of the TV show season. Note: You can use deep links for attribution. [Refer to this FAQ](https://developer.android.com/guide/playcore/engage/faq#deeplinks-attribution) | | Playback uri | Optional | The deep link to the provider app to start playing the TV show season. Note: You can use deep links for attribution. [Refer to this FAQ](https://developer.android.com/guide/playcore/engage/faq#deeplinks-attribution) | -| Display Season number | **Optional** **Available in v1.3.1** | String | +| Display Season number | Optional **Available in v1.3.1** | String | | First episode air date | Optional | In epoch milliseconds. | | Latest episode air date | Optional | In epoch milliseconds. | -| Availability | **Required** | AVAILABLE: The content is available to the user without any further action. FREE_WITH_SUBSCRIPTION: The content is available after the user purchases a subscription. PAID_CONTENT: The content requires user purchase or rental. PURCHASED: The content has been purchased or rented by the user. | +| Availability | Optional | AVAILABLE: The content is available to the user without any further action. FREE_WITH_SUBSCRIPTION: The content is available after the user purchases a subscription. PAID_CONTENT: The content requires user purchase or rental. PURCHASED: The content has been purchased or rented by the user. | | Offer price | Optional | Free text | -| Episode count | **Required** | Positive integer | -| Genre | **Required** | Free text | +| Episode count | Optional | Positive integer | +| Genre | Optional | Free text | | Content ratings | Optional | Free text, follow the industry standard. ([Example](https://www.spectrum.net/support/tv/tv-and-movie-ratings-descriptions)) | | Call to action text | Optional | Free text to be displayed as a call to action. | | Tags | Optional | List of tags associated with the entity. | @@ -258,14 +260,17 @@ The following chart outlines attributes and requirements for each type. |---|---|---| | Name | **Required** | | | Poster images | **Required** | At least one image is required, and must be provided with an aspect ratio. (Landscape is preferred but passing both portrait and landscape images for different scenarios is recommended.) See [Image Specifications](https://developer.android.com/guide/playcore/engage/watch#image-specs) for guidance. | -| Playback uri | **Required** | The deep link to the provider app to start playing the episode. Note: You can use deep links for attribution. [Refer to this FAQ](https://developer.android.com/guide/playcore/engage/faq#deeplinks-attribution) | +| Playback uri | **Required** for mobile | The deep link to the provider app to start playing the episode. Note: You can use deep links for attribution. [Refer to this FAQ](https://developer.android.com/guide/playcore/engage/faq#deeplinks-attribution) | +| Platform specific playback URIs | Optional for mobile; required for Google TV | Platform-specific deep links to the provider app to start playing the episode. | | Info page uri | Optional | The deep link to the provider app to show details about the TV show episode. Note: You can use deep links for attribution. [Refer to this FAQ](https://developer.android.com/guide/playcore/engage/faq#deeplinks-attribution) | -| Display Episode number | **Optional** **Available in v1.3.1** | String | -| Air date | **Required** | In epoch milliseconds. | -| Availability | **Required** | AVAILABLE: The content is available to the user without any further action. FREE_WITH_SUBSCRIPTION: The content is available after the user purchases a subscription. PAID_CONTENT: The content requires user purchase or rental. PURCHASED: The content has been purchased or rented by the user. | +| Show title | Optional for mobile; required for Google TV Continue Watching | Title of the parent TV show of the episode. | +| Season number | Optional for mobile; required for Google TV Continue Watching | String | +| Display Episode number | Optional for mobile; required for Google TV Continue Watching **Available in v1.3.1** | String | +| Air date | Optional | In epoch milliseconds. | +| Availability | Optional | AVAILABLE: The content is available to the user without any further action. FREE_WITH_SUBSCRIPTION: The content is available after the user purchases a subscription. PAID_CONTENT: The content requires user purchase or rental. PURCHASED: The content has been purchased or rented by the user. | | Offer price | Optional | Free text | | Duration | **Required** | Must be a positive value in milliseconds. | -| Genre | **Required** | Free text | +| Genre | Optional | Free text | | Content ratings | Optional | Free text, follow the industry standard. ([Example](https://www.spectrum.net/support/tv/tv-and-movie-ratings-descriptions)) | | Call to action text | Optional | Free text to be displayed as a call to action. | | Tags | Optional | List of tags associated with the entity. | @@ -279,8 +284,9 @@ The following chart outlines attributes and requirements for each type. |---|---|---| | Name | **Required** | | | Poster images | **Required** | At least one image is required, and must be provided with an aspect ratio. (Landscape is preferred but passing both portrait and landscape images for different scenarios is recommended.) See [Image Specifications](https://developer.android.com/guide/playcore/engage/watch#image-specs) for guidance. | -| Playback uri | **Required** | The deep link to the provider app to start playing the video. Note: You can use deep links for attribution. [Refer to this FAQ](https://developer.android.com/guide/playcore/engage/faq#deeplinks-attribution) | -| Broadcaster | **Required** | Free text | +| Playback uri | **Required** for mobile | The deep link to the provider app to start playing the video. Note: You can use deep links for attribution. [Refer to this FAQ](https://developer.android.com/guide/playcore/engage/faq#deeplinks-attribution) | +| Platform specific playback URIs | Optional for mobile; required for Google TV | Platform-specific deep links to the provider app to start playing the video. | +| Broadcaster | Optional | Free text | | Start time | Optional | In epoch milliseconds. | | End time | Optional | In epoch milliseconds. | | View count | Optional | Free text, must be localized. | @@ -299,10 +305,11 @@ such as TikTok or YouTube. |---|---|---| | Name | **Required** | | | Poster images | **Required** | At least one image is required, and must be provided with an aspect ratio. (Landscape is preferred but passing both portrait and landscape images for different scenarios is recommended.) See [Image Specifications](https://developer.android.com/guide/playcore/engage/watch#image-specs) for guidance. | -| Playback uri | **Required** | The deep link to the provider app to start playing the video. Note: You can use deep links for attribution. [Refer to this FAQ](https://developer.android.com/guide/playcore/engage/faq#deeplinks-attribution) | -| Created time | **Required** | In epoch milliseconds. | +| Playback uri | **Required** for mobile | The deep link to the provider app to start playing the video. Note: You can use deep links for attribution. [Refer to this FAQ](https://developer.android.com/guide/playcore/engage/faq#deeplinks-attribution) | +| Platform specific playback URIs | Optional for mobile; required for Google TV | Platform-specific deep links to the provider app to start playing the video. | +| Created time | Optional | In epoch milliseconds. | | Duration | **Required** | Must be a positive value in milliseconds. | -| Creator | **Required** | Free text | +| Creator | Optional | Free text | | Creator image | Optional | Image of the Creator avatar | | View count | Optional | Free text, must be localized. | | Call to action text | Optional | Free text to be displayed as a call to action. | diff --git a/security/android-permissions-security/SKILL.md b/security/android-permissions-security/SKILL.md index e7911ab..60cad10 100644 --- a/security/android-permissions-security/SKILL.md +++ b/security/android-permissions-security/SKILL.md @@ -7,7 +7,7 @@ description: Audits, detects gaps, and remediates Android permissions and IPC co license: Complete terms in LICENSE.txt metadata: author: Google LLC - last-updated: '2026-10-22' + last-updated: '2026-10-28' keywords: - recipe - Android @@ -60,9 +60,9 @@ vulnerabilities, systematically inspect each area: | Vulnerability Area | Search In / Pattern | Risk | Remediation Action | |---|---|---|---| -| **Weak Custom Permission Protection** | `AndroidManifest.xml` with `android:protectionLevel="normal"`, `"dangerous"`, or `"signatureOrSystem"` | Unauthorized apps request or claim permission without restriction | Upgrade to `signature` or `signature|knownSigner` with `android:knownCerts`. | +| **Weak Custom Permission Protection** | `AndroidManifest.xml` with `android:protectionLevel="normal"`, `"dangerous"`, or `"signatureOrSystem"` | Unauthorized apps request or claim permission without restriction | Upgrade to `signature` or `signature` with `knownSigner` and `android:knownCerts`. | | **Overly Broad URI Permission Grants** | `` or `pathPrefix=""` in `` | Leaks all provider data to third parties | Replace with explicit scoped subpath (e.g. `android:pathPrefix="/shared/"`). Retain `android:grantUriPermissions="true"`. Remove any wildcard `pathPrefix="/"`. | -| **Missing ContentProvider Split Permissions** | `` with only generic `android:permission` or missing `android:readPermission` and `android:writePermission` | Read-only callers execute write operations or write-only callers access read operations | Configure granular `android:readPermission="...READ_DATA"` and `android:writePermission="...WRITE_DATA"`, AND declare the custom `` tags with `android:protectionLevel="signature"` (or `signature|knownSigner`) at the `` root. | +| **Missing ContentProvider Split Permissions** | `` with only generic `android:permission` or missing `android:readPermission` and `android:writePermission` | Read-only callers execute write operations or write-only callers access read operations | Configure granular `android:readPermission="...READ_DATA"` and `android:writePermission="...WRITE_DATA"`, AND declare the custom `` tags with `android:protectionLevel="signature"` (or `signature` with `knownSigner`) at the `` root. | | **Spoofable Caller Identity Checks** | `intent.getStringExtra("calling_package")`, `intent.getStringExtra("sender")`, `callingPackage == "..."` in Services | Malicious callers forge intent extras or strings | Remove string checks; authenticate caller UID cryptographically using `Binder.getCallingUid()` and `pm.hasSigningCertificate()`. | | **Bound Service Permission Bypass** | `checkCallingOrSelfPermission()` or `enforceCallingOrSelfPermission()` in Binder AIDL stubs | Falls back to host app UID outside IPC or when identity is cleared, causing Confused Deputy bypass | Replace with `enforceCallingPermission("...WRITE_DATA", ...)` or `checkCallingPermission("...WRITE_DATA")`. | | **Missing Method-Level Checks in Bound Services** | Bound Service AIDL methods mutating state without enforcing write permissions | Callers with read-only binding access invoke mutating write operations | Add `enforceCallingPermission("...WRITE_DATA", "Caller lacks WRITE_DATA permission")` inside mutating AIDL methods. | @@ -142,9 +142,9 @@ certificate digests. - **Granular `UriPermission` Management** : Instead of broad manifest grants, use runtime `android.content.UriPermission` tokens for least-privilege per-URI sharing: - *Grant temporary access* : `context.grantUriPermission(targetPackage, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)` or attach `Intent.FLAG_GRANT_READ_URI_PERMISSION` to outgoing intents. - - *Persist access* : The receiving app calls `contentResolver.takePersistableUriPermission(uri, modeFlags)` for durable access across reboots. + - *Persist and release access* : The receiving app calls `contentResolver.takePersistableUriPermission(uri, modeFlags)` for durable access across reboots, and `contentResolver.releasePersistableUriPermission(uri, modeFlags)` when durable access is no longer needed. - *Audit active grants* : Inspect `contentResolver.persistedUriPermissions` (`List`) to verify granted URIs (`uriPermission.uri`, `uriPermission.isReadPermission()`, `uriPermission.isWritePermission()`). - - *Revoke access* : Call `context.revokeUriPermission(uri, modeFlags)` or `contentResolver.releasePersistableUriPermission(uri, modeFlags)` as soon as operations conclude. + - *Revoke temporary access* : Call `context.revokeUriPermission(targetPackage, uri, modeFlags)` (API 26+), falling back to `context.revokeUriPermission(uri, modeFlags)` on API 25 and lower so access fails closed as soon as operations conclude. ### Manifest snippet: Complete secure ContentProvider declaration @@ -363,7 +363,7 @@ caching states or creating infinite retry loops: 1. **Check Granted State** : `ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED`. -2. **Check Rationale** : `shouldShowRequestPermissionRationale(permission)` to present educational context before requesting. +2. **Check Rationale** : When `shouldShowRequestPermissionRationale(permission)` returns `true`, show a rationale dialog and launch the permission request only from its Continue button. 3. **Launch Contract** : `ActivityResultContracts.RequestPermission()`. 4. **Handle Permanent Denial** : In the callback, if permission was denied and `!shouldShowRequestPermissionRationale(permission)`, guide the user to `Settings.ACTION_APPLICATION_DETAILS_SETTINGS`. diff --git a/testing/testing-setup/SKILL.md b/testing/testing-setup/SKILL.md index 2c02333..f1b99a8 100644 --- a/testing/testing-setup/SKILL.md +++ b/testing/testing-setup/SKILL.md @@ -6,7 +6,7 @@ description: Analyze and create a testing strategy for Android apps - install te license: Complete terms in LICENSE.txt metadata: author: Google LLC - last-updated: '2026-10-23' + last-updated: '2026-10-28' keywords: - android - testing 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 2cdcc80..2c47c50 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 @@ -1,8 +1,5 @@ -> [!WARNING] -> **Deprecated:** Starting with Android Gradle Plugin (AGP) 9.5.0-alpha03 and Compose Preview Screenshot Testing 0.0.1-alpha16, we recommend configuring screenshot tests using [AGP test suites](https://developer.android.com/studio/preview/compose-screenshot-testing-with-testsuites). The standalone plugin method described on this page is deprecated. - -> [!WARNING] -> **Experimental:** Compose Preview Screenshot Testing is still in development. Its features and APIs are subject to change substantially during the alpha phase. Report any feedback and issues through the [issue tracker](https://issuetracker.google.com/issues/new?component=192708&template=840533). +> [!CAUTION] +> **Caution:** The standalone `com.android.compose.screenshot` plugin described on this page is no longer maintained. We recommend setting up screenshot tests with [AGP test suites](https://developer.android.com/studio/preview/compose-screenshot-testing-with-testsuites) using Android Gradle Plugin (AGP) 9.5 or higher. To switch, see [Migrate an existing project](https://developer.android.com/studio/preview/compose-screenshot-testing-with-testsuites#migration). Screenshot testing is an effective way to verify how your UI looks to users. The Compose Preview Screenshot Testing tool combines the simplicity and diff --git a/wear/wear-compose-m3/SKILL.md b/wear/wear-compose-m3/SKILL.md index c84c143..f206a3c 100644 --- a/wear/wear-compose-m3/SKILL.md +++ b/wear/wear-compose-m3/SKILL.md @@ -8,7 +8,7 @@ description: Guidance for Wear OS Compose Material3 (Wear Compose Material 3 / W license: Complete terms in LICENSE.txt metadata: author: Google LLC - last-updated: '2026-10-24' + last-updated: '2026-10-28' keywords: - Wear OS - Compose diff --git a/wear/wear-compose-m3/references/material3/OneHandedGestureSamples.kt.md b/wear/wear-compose-m3/references/material3/OneHandedGestureSamples.kt.md index 70d2800..d8aab89 100644 --- a/wear/wear-compose-m3/references/material3/OneHandedGestureSamples.kt.md +++ b/wear/wear-compose-m3/references/material3/OneHandedGestureSamples.kt.md @@ -41,6 +41,8 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onVisibilityChanged +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.wear.compose.foundation.AmbientMode import androidx.wear.compose.foundation.LocalAmbientModeManager @@ -552,6 +554,98 @@ fun OneHandedGestureScalingLazyColumnScrollToNextItemSample() { } } +@Sampled +@Composable +fun OneHandedGestureVisibilitySample() { + var label by remember { mutableStateOf("Double-pinch") } + val onClick = { label = "Gestured" } + val scrollState = rememberTransformingLazyColumnState() + val coroutineScope = rememberCoroutineScope() + + val scrollGestureConfig = + rememberOneHandedGestureConfiguration( + action = OneHandedGestureAction.Primary, + priority = OneHandedGesturePriority.Scrollable, + ) + val scrollIndicatorState = + remember(scrollGestureConfig) { OneHandedGestureScrollIndicatorState() } + + ScreenScaffold( + scrollState = scrollState, + scrollIndicator = { + OneHandedGestureScrollIndicator( + gestureConfiguration = scrollGestureConfig, + indicatorState = scrollIndicatorState, + scrollState = scrollState, + modifier = Modifier.align(Alignment.CenterEnd), + ) + }, + ) { contentPadding -> + TransformingLazyColumn( + state = scrollState, + contentPadding = contentPadding, + modifier = + Modifier.fillMaxSize() + .oneHandedGesture( + gestureConfiguration = scrollGestureConfig, + onGestureLabel = "scroll", + onGestureAvailable = { + coroutineScope.launch { scrollIndicatorState.showIndicator() } + }, + onGesture = { OneHandedGestureDefaults.scrollDown(scrollState) }, + ), + ) { + items(10) { Text("Item $it") } + item { + var isButtonVisible by remember { mutableStateOf(false) } + val buttonInteractionSource = remember { MutableInteractionSource() } + val buttonGestureConfig = + rememberOneHandedGestureConfiguration( + action = OneHandedGestureAction.Primary, + priority = OneHandedGesturePriority.Clickable, + ) + val buttonIndicatorState = remember { OneHandedGestureClickIndicatorState() } + + Button( + onClick = onClick, + interactionSource = buttonInteractionSource, + modifier = + Modifier.onVisibilityChanged(minFractionVisible = 0.9f) { + isButtonVisible = it + } + .then( + if (isButtonVisible) { + // Apply the one-handed gesture modifier only when the button + // is at least 90% visible. + Modifier.oneHandedGesture( + gestureConfiguration = buttonGestureConfig, + interactionSource = buttonInteractionSource, + onGestureLabel = "activate the button", + onGestureAvailable = { + coroutineScope.launch { + buttonIndicatorState.showIndicator() + } + }, + onGesture = onClick, + ) + } else { + Modifier + } + ), + ) { + OneHandedGestureClickIndicator(buttonGestureConfig, buttonIndicatorState) { + Text( + label, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + ) + } + } + } + } + } +} + @Sampled @Composable fun OneHandedGestureHorizontalPagerSample() { diff --git a/wear/wear-compose-m3/references/material3/ScaffoldSample.kt.md b/wear/wear-compose-m3/references/material3/ScaffoldSample.kt.md index 19f15bb..9620d69 100644 --- a/wear/wear-compose-m3/references/material3/ScaffoldSample.kt.md +++ b/wear/wear-compose-m3/references/material3/ScaffoldSample.kt.md @@ -119,7 +119,7 @@ fun ScaffoldWithTLCEdgeButtonSample() { Text("Clear All") } }, - ) { conten>tPadding - + ) { contentPadding -> TransformingLazyColumn( state = listState, modifier = Modifier.fillMaxSize(), diff --git a/wear/wear-compose-m3/references/material3/SwipeToRevealSample.kt.md b/wear/wear-compose-m3/references/material3/SwipeToRevealSample.kt.md index 4f1fc8b..b012dbe 100644 --- a/wear/wear-compose-m3/references/material3/SwipeToRevealSample.kt.md +++ b/wear/wear-compose-m3/references/material3/SwipeToRevealSample.kt.md @@ -157,10 +157,10 @@ fun SwipeToRevealSingleActionCardSample() { @Sampled fun SwipeToRevealWithTransformingLazyColumnSample() { val transformationSpec = rememberTransformationSpec() - val tlcState = rememberTransformingLazyState() + val tlcState = rememberTransformingLazyColumnState() val coroutineScope = rememberCoroutineScope() val messages = remember { - mutableStateListOfString().apply { + mutableStateListOf().apply { for (i in 1..100) { add("Message #${i}") } @@ -169,16 +169,16 @@ fun SwipeToRevealWithTransformingLazyColumnSample() { TransformingLazyColumn( state = tlcState, - contentPadding = PaddingValues(horizontal = 16.dp, vertica>l = 20.dp), + contentPadding = PaddingValues(horizontal = 16.dp, vertical = 20.dp), modifier = Modifier.background(Color.Black), ) { - items(items = messages, key = { it }) { message - + items(items = messages, key = { it }) { message -> val revealState = rememberRevealState() - // SwipeToReveal should be reset to covered when scrolling occur&&s. + // SwipeToReveal should be reset to covered when scrolling occurs. LaunchedEffect(tlcState.isScrollInProgress) { if ( - tlcState.isScrollInProgress revealState.currentValue != RevealValue.Covered + tlcState.isScrollInProgress && revealState.currentValue != RevealValue.Covered ) { coroutineScope.launch { revealState.animateTo(targetValue = RevealValue.Covered) @@ -231,11 +231,11 @@ fun SwipeToRevealWithTransformingLazyColumnSample() { @Preview @Composable @Sampled -fun SwipeToRevealWithScalyColumnSample() { +fun SwipeToRevealWithScalingLazyColumnSample() { val slcState = rememberScalingLazyListState() val coroutineScope = rememberCoroutineScope() val messages = remember { - mutableStateListOfString().apply { + mutableStateListOf().apply { for (i in 1..100) { add("This Button $i has two actions") } @@ -244,16 +244,16 @@ fun SwipeToRevealWithScalyColumnSample() { ScalingLazyColumn( state = slcState, - > contentPadding = PaddingValues(horizontal = 16.dp, vertical = 20.dp), + contentPadding = PaddingValues(horizontal = 16.dp, vertical = 20.dp), modifier = Modifier.background(Color.Black), ) { - items(items = messages, key = { it }) { message - + items(items = messages, key = { it }) { message -> val revealState = rememberRevealState() - // S&&wipeToReveal should be reset to covered when scrolling occurs. + // SwipeToReveal should be reset to covered when scrolling occurs. LaunchedEffect(slcState.isScrollInProgress) { if ( - slcState.isScrollInProgress revealState.currentValue != RevealValue.Covered + slcState.isScrollInProgress && revealState.currentValue != RevealValue.Covered ) { coroutineScope.launch { revealState.animateTo(targetValue = RevealValue.Covered) @@ -300,7 +300,7 @@ fun SwipeToRevealWithScalyColumnSample() { onClick = {}, ) { Text(message, modifier = Modifier.fillMaxSize()) - < }> + } } } } @@ -312,19 +312,19 @@ fun SwipeToRevealWithScalyColumnSample() { fun SwipeToRevealNoPartialRevealWithScalingLazyColumnSample() { val slcState = rememberScalingLazyListState() val messages = remember { - mutableStateListOfString().apply { + mutableStateListOf().apply { for (i in 1..100) { add("Message #${i}") } } - > } + } ScalingLazyColumn( state = slcState, contentPadding = PaddingValues(horizontal = 16.dp, vertical = 20.dp), modifier = Modifier.background(Color.Black), ) { - items(items = messages, key = { it }) { message - + items(items = messages, key = { it }) { message -> SwipeToReveal( hasPartiallyRevealedState = false, primaryAction = { diff --git a/wear/wear-compose-m3/references/material3/TimeTextSample.kt.md b/wear/wear-compose-m3/references/material3/TimeTextSample.kt.md index fc15a6c..ba1e741 100644 --- a/wear/wear-compose-m3/references/material3/TimeTextSample.kt.md +++ b/wear/wear-compose-m3/references/material3/TimeTextSample.kt.md @@ -19,10 +19,14 @@ package androidx.wear.compose.material3.samples import androidx.annotation.Sampled import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember import androidx.compose.ui.text.style.TextOverflow import androidx.wear.compose.foundation.CurvedModifier import androidx.wear.compose.foundation.weight +import androidx.wear.compose.material3.LocalTimeSource import androidx.wear.compose.material3.MaterialTheme +import androidx.wear.compose.material3.TimeSource import androidx.wear.compose.material3.TimeText import androidx.wear.compose.material3.TimeTextDefaults import androidx.wear.compose.material3.curvedText @@ -62,4 +66,16 @@ fun TimeTextWithStatusEllipsized() { timeTextCurvedText(time) } } + +@Sampled +@Composable +fun TimeTextOverrideTimeSourceForTesting() { + val fixedTimeSource = remember { + object : TimeSource { + @Composable override fun currentTime(): String = "12:34" + } + } + + CompositionLocalProvider(LocalTimeSource provides fixedTimeSource) { TimeText() } +} ``` \ No newline at end of file