Skip to content

Android

This page covers only the Android-specific integration delta. The general attach / detach / release contract (generation semantics, error codes, MigoSurfaceDescriptor field rules) is described in Surface Interface; for Session lifecycle see Lifecycle Concepts.

Availability: Released in 0.9 for arm64-v8a and x86_64 (emulator). Java/Kotlin hosts operate through the AAR (com.migo.runtime); pure C hosts use the C ABI described below directly.

#include <migo/platform/android.h>
#include <migo/capabilities.h>
typedef struct MigoAndroidNativeWindowDescriptor {
uint32_t struct_size;
uint32_t abi_version;
MigoPlatformKind platform_kind; /* MIGO_PLATFORM_ANDROID_NATIVE_WINDOW */
MigoPlatformDescriptorFlags flags; /* must be 0 */
void *native_window; /* ANativeWindow* */
} MigoAndroidNativeWindowDescriptor;

native_window is an ANativeWindow*. The engine acquires its own strong reference before attach returns successfully, and releases that reference before the release observer reaches MIGO_SURFACE_RELEASE_RELEASED. The host retains its own reference; no additional refcount management is needed after a successful attach.

When passing a MigoSurfaceDescriptor, point platform_descriptor at this struct, set platform_descriptor_size to sizeof, and ensure platform_kind is consistent in both the outer descriptor and the inner platform descriptor.

MigoResult migo_android_init_context(void *vm, void *activity);

Call this only when both of the following conditions hold:

  1. The host is a pure C / NDK host (no Java layer, not using the com.migo.runtime AAR).
  2. The Session will play audio.

vm and activity correspond to the vm and clazz fields of ANativeActivity. Repeated calls after the first are no-ops; calling again after an Activity recreation is safe without checking. Passing NULL for vm returns MIGO_ERROR_INVALID_ARGUMENT.

Omitting this call does not cause attach to fail, but the first audio playback will terminate the process instead of returning an error — call this before creating any Session that will play audio.

The generation Counter (Android-Specific Pitfall)

Section titled “The generation Counter (Android-Specific Pitfall)”

Android destroys and recreates ANativeWindow every time the app moves to the background and returns to the foreground. Each surfaceCreated / surfaceChanged callback delivers a new window; generation must strictly increment and must never be hard-coded to the constant 1.

/* host-owned counter, incremented across surfaceCreated/surfaceChanged */
static uint64_t g_surface_gen = 0;

A duplicate generation value returns MIGO_ERROR_STALE_SURFACE; passing 0 on the first call returns MIGO_ERROR_INVALID_ARGUMENT (generation must be ≥ 1).

static MigoSurfaceAttachment *s_attachment = NULL;
void on_surface_created(void *window, int32_t w, int32_t h, float density) {
/* availability check (optional, but recommended once before the first attach) */
MigoCapabilities caps;
caps.struct_size = (uint32_t)sizeof caps;
caps.abi_version = MIGO_ABI_VERSION_CURRENT;
if (migo_query_capabilities(&caps) != MIGO_OK ||
(caps.platform_kinds & (1ULL << MIGO_PLATFORM_ANDROID_NATIVE_WINDOW)) == 0)
return; /* this build cannot attach an Android surface */
MigoAndroidNativeWindowDescriptor plat;
memset(&plat, 0, sizeof plat);
plat.struct_size = (uint32_t)sizeof plat;
plat.abi_version = MIGO_ABI_VERSION_CURRENT;
plat.platform_kind = MIGO_PLATFORM_ANDROID_NATIVE_WINDOW;
plat.native_window = window;
MigoSurfaceDescriptor desc;
memset(&desc, 0, sizeof desc);
desc.struct_size = (uint32_t)sizeof desc;
desc.abi_version = MIGO_ABI_VERSION_CURRENT;
desc.generation = ++g_surface_gen; /* incremented; never a constant */
desc.platform_kind = MIGO_PLATFORM_ANDROID_NATIVE_WINDOW;
desc.width_pixels = (uint32_t)w;
desc.height_pixels = (uint32_t)h;
desc.scale_factor = density;
desc.color_space = MIGO_COLOR_SPACE_SRGB;
desc.alpha_mode = MIGO_ALPHA_MODE_OPAQUE;
desc.preferred_presentation_mode = MIGO_PRESENTATION_MODE_DEFAULT;
desc.capability_flags = MIGO_SURFACE_CAPABILITY_NONE; /* must be 0 in 0.9 */
desc.platform_descriptor_size = (uint32_t)sizeof plat;
desc.platform_descriptor = &plat;
migo_session_attach_surface(session, &desc, &s_attachment);
}

Do not destroy ANativeWindow immediately when surfaceDestroyed fires — the GPU may still hold a reference to it.

void on_surface_destroyed(void) {
MigoSurfaceRelease *rel = NULL;
migo_surface_begin_detach(s_attachment, &rel);
s_attachment = NULL;
/* poll until RELEASED; a real host can use the on_surface_released callback instead */
MigoSurfaceReleaseStatus status;
do {
status.struct_size = (uint32_t)sizeof status;
status.abi_version = MIGO_ABI_VERSION_CURRENT;
migo_surface_release_query(rel, &status);
} while (status.state != MIGO_SURFACE_RELEASE_RELEASED);
migo_surface_release_destroy(rel);
/* only now is it safe to destroy the ANativeWindow */
}

Once migo_surface_begin_detach returns MIGO_OK, the attachment pointer is consumed and must not be used again.