Register a typed Hook

July 29, 2026 · View on GitHub

简体中文 · Developer guide

A Hook is one function M3UAndroid can call. Its HookSpec fixes the request type, result type, and schema version. This example adds one setting based on the current UI surface.

1. Add the Hook to the manifest

Add the Hook and its capability to ExtensionManifest:

hooks = setOf(
    ExtensionHookDeclaration(
        hook = HostHookSpecs.SettingsSchema.hook,
        schemaVersion = HostHookSpecs.SettingsSchema.schemaVersion,
        requiredCapabilities = setOf(ExtensionCapabilityIds.SettingsContribute),
    )
),
capabilities = setOf(
    ExtensionCapabilityRequest(
        capability = ExtensionCapabilityIds.SettingsContribute,
        reason = "Add settings for the current device type",
    )
),

Use HostHookSpecs.SettingsSchema.hook and .schemaVersion directly so the declaration follows the selected contract.

2. Return the typed result

Register handlers in the TypedExtensionService initializer:

init {
    handle(HostHookSpecs.SettingsSchema) { request, _ ->
        val (label, defaultValue) = when (request.surface) {
            "phone" -> "Phone name" to "My phone"
            "tv" -> "TV name" to "My TV"
            else -> "Device name" to "My device"
        }
        SettingsSchemaResult(
            sections = listOf(
                ExtensionSettingSection(
                    id = "device",
                    title = "Device",
                    schema = ExtensionSettingSchema(
                        version = 1,
                        fields = listOf(
                            ExtensionSettingField(
                                key = "name",
                                label = label,
                                type = ExtensionSettingType.TEXT,
                                defaultValue = JsonPrimitive(defaultValue),
                            )
                        ),
                    ),
                )
            )
        )
    }
}

For this HookSpec, request is SettingsSchemaRequest and the returned value must be SettingsSchemaResult. The SDK handles serialization.

3. Use the call context only when needed

The second handler argument is ExtensionCallContext:

PropertyUse
invocationIdCorrelate diagnostics for one call.
grantedCapabilitiesCapabilities declared by this Hook and approved for this call. Capabilities from other Hooks are absent.
settings.valuesRead non-secret settings saved by the host.
settings.credentialHandlesRead opaque handles for secret settings.

If the second handler argument is named context, read a setting with its qualified key:

val key = ExtensionSettingKeys.qualified("manifest", "greeting")
val greeting = context.settings.values[key]

Use the section ID returned by a dynamic settings Hook instead of manifest. Secret settings use the same qualified key in credentialHandles; their plaintext is not available to the extension.

Use handleResult(...) if the Hook can return an expected validation or domain failure. Return HookResult.Failure(ExtensionError(...)) for that case. Do not catch coroutine cancellation.

Common contract errors

  • A handler is registered but the Hook is missing from the manifest.
  • The declaration does not use the selected HookSpec.schemaVersion.
  • A required capability is missing from manifest.capabilities.
  • The same Hook is registered twice.
  • The handler returns a result from another HookSpec.

After adding the handler, repeat the Hello acceptance check.

Next: choose another Hook. Provider extensions should continue with Build a subscription provider.