Goo.Android 0.28.6

There is a newer version of this package available.
See the version list below for details.
dotnet add package Goo.Android --version 0.28.6
                    
NuGet\Install-Package Goo.Android -Version 0.28.6
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Goo.Android" Version="0.28.6" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Goo.Android" Version="0.28.6" />
                    
Directory.Packages.props
<PackageReference Include="Goo.Android" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Goo.Android --version 0.28.6
                    
#r "nuget: Goo.Android, 0.28.6"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Goo.Android@0.28.6
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Goo.Android&version=0.28.6
                    
Install as a Cake Addin
#tool nuget:?package=Goo.Android&version=0.28.6
                    
Install as a Cake Tool

Android integration

Android starter

dotnet new install Goo.Templates
dotnet new goo-android --name HelloGoo --application-id com.example.hellogoo
dotnet build HelloGoo/HelloGoo.gsproj -c Release
  • Requires the .NET Android workload, its supported Android SDK and JDK.
  • Includes an Activity, a shared G# library, Vulkan manifest rules and ARM64/x64 RIDs.
  • Requires API 33 and a compatible Vulkan 1.3 device.
  • Use --goo-version to select the matching Goo and Goo.Android release.
  • Set AndroidSdkDirectory and JavaSdkDirectory if tool discovery fails.

API reference.

Shared application

Put application Cells and a Window factory in a shared net10.0 G# library. Desktop calls window.Run(). Android returns the same Window from GooActivity.CreateWindow():

using Android.App;
using Android.Content.PM;
using Goo.Android;

[Activity(MainLauncher = true, Exported = true,
    ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize
        | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize
        | ConfigChanges.Density | ConfigChanges.KeyboardHidden)]
public sealed class MainActivity : GooActivity
{
    protected override Goo.Window CreateWindow() => MyApplication.CreateWindow();
}

Reference Goo.Android from a net10.0-android application and set SupportedOSPlatformVersion to 33. Use Android RIDs android-arm64 and android-x64. Both hosts use the same control tree, editor, renderer, and asset API through EmbeddedWindowHost and PlatformInput. The sample keeps Cells mounted during orientation and size changes and resizes the viewport.

Launch and document activation

private static bool subscribed;

protected override Goo.Window CreateWindow()
{
    if (!subscribed)
    {
        Goo.Window.ApplicationActivated += activation =>
            Android.Util.Log.Info("Goo", activation.Kind.ToString());
        subscribed = true;
    }
    return MyApplication.CreateWindow();
}
Android input Goo activation Data
Goo.Android.Notification action Notification Goo.NotificationId, Goo.ActionId, Goo.Payload extras
Launcher Launch None
Data URI other than content: or file: OpenUri Target
content:, file:, or ClipData URIs OpenDocuments Documents, in source order
  • Register once before CreateWindow() returns.
  • Cold launch and OnNewIntent use the same UI-thread event.
  • IsInitial marks the first process launch. Activity recreation does not replay the old Intent.
  • Override OnNewIntent only if needed. Call base, then read LastActivationStatus for queue or size failures.
  • The starter uses SingleTop. Add your app's URI filters and MIME types to its manifest.
  • Open document URIs with ContentResolver, not file paths.
  • Android owns URI grants. Use TakePersistableUriPermission only when the sender offers it.
  • Copy data before a temporary URI grant expires.

Device requirements

  • Android 13 / API 33 or newer, with a Vulkan 1.3 implementation and Goo's required Vulkan features and identity presentation transform. Android API level alone does not establish GPU support.
  • ARM64 phones and tablets use android-arm64. x64 emulators use android-x64.
  • A Vulkan-capable emulator is required. Query its actual device support with adb shell cmd gpu vkjson before testing.
  • Declare the required Vulkan version in the app manifest:
<uses-feature android:name="android.hardware.vulkan.version"
              android:version="0x00403000" android:required="true" />
<uses-feature android:name="android.hardware.vulkan.level"
              android:version="1" android:required="true" />

The API requirement follows the Android Vulkan support table. Native payloads support 16 KB page alignment.

View and lifecycle ownership

GooActivity owns a GooView and forwards pause, resume, and destruction. Native layout applies system bar, display cutout, and keyboard insets before sizing the SurfaceView. Framebuffer and input coordinates share that content rectangle.

In a native activity, construct new GooView(context, window) on the Android main thread. Add it to the native layout and call Pause(), Resume(), and Dispose() from the matching activity callbacks. Size it inside safe and keyboard insets. Do not call Window.Run() or Window.Pump() or attach the Window to another view.

The view retains one ANativeWindow reference while its Surface exists. Surface destruction detaches and drains Vulkan presentation before releasing it. A replacement Surface reuses the Window and mounted Cells. Activity destruction closes the Window. Keep activity/process recreation data in application state.

Activity state restoration

private string draft = "";

protected override Goo.Window CreateWindow()
{
    draft = SavedInstanceState?.GetString("draft") ?? "";
    return new Goo.Window
    {
        Root = new Goo.TextEntry { Value = draft, OnChange = value => draft = value }
    };
}

protected override void OnSaveInstanceState(Android.OS.Bundle outState)
{
    outState.PutString("draft", draft);
    base.OnSaveInstanceState(outState);
}
  • SavedInstanceState holds Android's Bundle during CreateWindow(). It is null on a fresh launch.
  • The app owns keys, serialization, versioning, and state restoration.
  • Call base from OnSaveInstanceState. Goo does not serialize Cells or native resources.
  • Store large or durable data outside the Bundle.
  • Handled orientation and size changes keep the current tree.

Frame scheduling

Choreographer runs frames only on demand or at Goo's next animation/timer deadline. Pause cancels scheduling and transient input. Resume resets the frame clock.

An embedded host can call ServicePendingSubmission() on its owner thread at a frame wake, before scheduling the next timed frame. It presents a completed graphics submission without simulation or rendering. Presentation completion remains in the next normal frame pump. Goo's Android adapter does this automatically.

GooView.HasFrameDemand is true while a Choreographer frame is pending or the renderer needs an immediate frame. Read it, then use FrameDemandChanged on the Android UI thread to update frame-rate hints or other host policy. A future animation/timer deadline leaves demand false until scheduling. Pause, Surface destruction, view detachment, and disposal cancel pending frames and clear demand. The event reports demand transitions, not every presented frame.

Documents and URI streams

var picked = await Documents.ShowPickerAsync(Goo.FileDialogKind.OpenFile);
if (picked.Status != Goo.FileDialogStatus.Success) return;
var opened = await Documents.OpenReadAsync(picked.Uris[0]);
if (opened.Status != DocumentStatus.Success) return;
await using var stream = opened.Stream!;

Use GooActivity.Documents on the Activity's main thread to launch a picker. Documents stay as content:// URIs, not filesystem paths.

Operation Contract
Open MIME filters and optional multiple selection. The default requests Read access.
Save Creates one new document. Supply one concrete MIME type and Write access. It is not an overwrite dialog.
Folder Returns one tree URI. Open a child document URI to read its bytes.
Streams OpenReadAsync and OpenWriteAsync open off the UI thread. The caller owns each successful stream. Writes replace and truncate contents. Later I/O uses normal .NET stream semantics.
Grants GrantedAccess and CanPersist report returned flags. Call PersistAccess or ReleaseAccess on the main thread. Store URI values in app state. Persistence is never automatic.
Lifetime Pause and surface loss keep the picker open. Cancellation or Activity destruction completes it with Cancelled. A replacement Activity does not restore an old task.
Failures Stream and grant results distinguish Unsupported, Denied, NotFound, Cancelled and Failed. A failed stream result has no stream.

Input and keyboard policy

Touch IDs, mouse wheel, physical keys, focus, clipboard, UTF-16 selection, composition, and semantic editor commands use shared Goo input. Stale IME connections cannot edit a different focused field. Password fields suppress surrounding and extracted text returned to the IME.

Each Android move event updates all contact positions before gesture recognition. An accepted gesture receives one coherent update per move event.

TextEntry{
    InputOptions: TextInputOptions{
        Purpose: InputPurpose.Email,
        EnterAction: EnterAction.Next,
        AutofillHints: []string{"emailAddress"},
    },
}
  • Works with TextEntry.InputOptions and TextEditor.InputOptions.
  • Android maps hints to EditorInfo and focused-field autofill.
  • Autofill replaces the focused value. This is not a multi-field autofill form.
  • Hints do not validate input.
  • Without options, entries request Done and multiline editors request None.
  • InputPurpose.Password masks TextEntry like Password: true. Use TextEntry for protected values.
  • Desktop ignores other keyboard and autofill hints.

GooView.DismissKeyboardOnSubmit defaults to true. IME Done sends Submit and hides the keyboard. Set it to false to keep the keyboard open after Submit. Focus changes and lifecycle handling still control text input. Next and Previous move focus.

Android owns physical key repeat timing. GooView dispatches each native key-down, including repeats, and releases its mapped Goo key before returning. A missing key-up cannot leave Goo repeat active. Mapped key-up events are consumed without another edit. Each key-down commits printable characters unless Ctrl or Alt is pressed.

Accessibility

TextEntry{ Accessibility: Accessibility{ Name: "Name" } }
  • GooView exposes Goo semantics as Android virtual accessibility nodes.
  • The bridge runs when an Android accessibility service is active.
  • Give custom controls a role, name, state and supported actions through Accessibility.
  • Text, password masks, selection and editing use the shared Goo action path.
  • GooView owns Window.AccessibilityAdapter. Leave the slot empty before construction.
  • Enable TalkBack in Android settings. No app permission or extra package is needed.
  • Reading focus does not take keyboard focus or open the keyboard.
  • Touch exploration uses Goo stacking, transforms and clipping.
  • Surface replacement keeps virtual IDs and clears reading focus.
  • The virtual subtree is available after the replacement presentation updates.
  • Text, selection, scroll and state changes send events for the changed nodes.
  • Root, native geometry and Surface changes refresh the host subtree.
  • Accessibility.Live sets polite or assertive live-region behavior.
  • Atomic regions send their full current text. Resume does not replay old announcements.

Accessibility checks

.github/scripts/run-android-accessibility.sh path/to/org.goo.androidsmoke-Signed.apk
  • CI runs the real smoke app through Android UiAutomation on API 36.
  • The runner uses pinned emulator and Google APIs image builds. It needs Linux KVM.
  • The checks cover native nodes, hover, actions, text events, password masks, live regions, Surface replacement and Activity replacement.
  • Logs, guest Vulkan details and a screenshot are saved under artifacts/android-accessibility-36/logs.
  • These checks do not test TalkBack speech.

Before release, record the Android and TalkBack versions and check:

Check Expected result
Explore by touch and swipe Correct control name, role, state and order. No canvas stop.
Double-tap controls One action. Text entry opens the keyboard.
Read and select mixed-direction text Whole characters and correct selection.
Edit a password No document text in speech.
Change a live region Correct polite or assertive speech. Atomic regions read the whole update.
Scroll, rotate, background and resume Correct bounds. No stale focus or repeated live update.
Disable or remove controls, then replace the Surface No stale nodes or focus. Navigation, editing, and activation still work.

Native popup surfaces

using let popup = PopupWindow(window, TooltipCell{}, PopupKind.Tooltip)
let result = popup.Open(anchor.BorderBox, 240, 120, PortalPlacement.BottomStart)
  • Uses Android PopupWindow with a separate GooView and Vulkan surface.
  • A menu receives focus and dismisses on Back or outside input. A tooltip receives no input.
  • Coordinates use owner-content layout units. Size follows the owner's UiScale and display density.
  • The native window may extend beyond its owner view. Android clips it to the app's display bounds.
  • Pause, surface loss, view detach and owner disposal dismiss the popup chain.
  • A missing live owner view returns Unsupported. Native window failure returns Failed.
  • Portal and widgets Menu keep their in-window behavior.

Transparent surfaces

Set Window.Transparent before constructing GooView. It places a translucent SurfaceView above native views, which show through transparent pixels.

Vulkan prefers premultiplied composite alpha. Android also allows inherited alpha when exposed, because GooView configures premultiplied transparency. Other embedded hosts default EmbeddedWindowHost.AllowInheritedCompositeAlpha() to false. Override it only for the same compositor contract. Swapchain creation fails without premultiplied or explicitly permitted inherited alpha.

Assets and native payloads

Use FontSource and premultiplied RGBA ImageSource. Shared libraries can embed font bytes with EmbeddedResource and register them on either platform. Android sans-serif uses Roboto with generic Noto Sans fallback. Goo.dll embeds Vulkan shaders, including in APKs. No extraction directory is needed.

Goo contains HarfBuzz and hb-gpu for both Android RIDs. Goo.Android source builds create a native archive for project references. NuGet omits it and uses Goo's payloads to avoid duplicate libraries. The renderer adds no SDL, Skia, OpenGL, FreeType, or C++ runtime payload.

Build the pinned HarfBuzz 15.0.0 libraries on Linux with Android NDK 27.2.12479018, Meson, Ninja, Python 3, and the normal archive tools:

bash .github/scripts/build-text-native-android.sh android-arm64 Goo/Runtime/Vulkan/android-arm64 "$ANDROID_NDK_HOME"
bash .github/scripts/build-text-native-android.sh android-x64 Goo/Runtime/Vulkan/android-x64 "$ANDROID_NDK_HOME"

The script verifies the source archive and patch hashes, target ABI, required exports, private SONAMEs, dependencies, and page alignment. It writes text-native-build.json beside each payload. CI rebuilds each ABI twice and compares all bytes before building the Release smoke packages.

Shared smoke app

tests/Goo.AndroidSmoke.Shared contains one G# Cell scene with a packaged font, system text, retained counter, image, text entry, password entry, multiline editor, and scroll container. Both entry projects reference that library.

dotnet run --project tests/Goo.AndroidSmoke.Desktop/Goo.AndroidSmoke.Desktop.gsproj -c Release
dotnet build tests/Goo.AndroidSmoke/Goo.AndroidSmoke.gsproj -c Release -p:AndroidSdkDirectory="$ANDROID_HOME" -p:JavaSdkDirectory="$JAVA_HOME"
dotnet run --project tools/Goo.RepositoryChecks/Goo.RepositoryChecks.gsproj -c Release -- android tests/Goo.AndroidSmoke/bin/Release/net10.0-android/org.goo.androidsmoke-Signed.apk
dotnet run --project tools/Goo.RepositoryChecks/Goo.RepositoryChecks.gsproj -c Release -- android tests/Goo.AndroidSmoke/bin/Release/net10.0-android/org.goo.androidsmoke.aab
"$ANDROID_HOME/build-tools/36.0.0/zipalign" -c -P 16 -v 4 tests/Goo.AndroidSmoke/bin/Release/net10.0-android/org.goo.androidsmoke-Signed.apk
adb install -r tests/Goo.AndroidSmoke/bin/Release/net10.0-android/org.goo.androidsmoke-Signed.apk
adb shell monkey -p org.goo.androidsmoke 1

Run the deterministic input-connection checks after installation:

adb shell am start -S -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -p org.goo.androidsmoke --ez goo.input_smoke true
adb logcat -d -s GooInputSmoke:I

Wait for the final PASS log after exercising text and composition cursors, surrounding deletion, code points, context queries, batches, password privacy, stale focus, and multiline input through the Android input connection. The last checks leave a Backspace key-up absent and deliver explicit native repeat events, then wait to verify that no additional deletion occurs.

adb shell am instrument -w -r org.goo.androidsmoke/org.goo.androidsmoke.GestureChecks

GOO_GESTURES_PASS confirms coherent two-contact movement, rotation and native cancellation.

adb shell am start -S -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -p org.goo.androidsmoke --ez goo.popup_smoke true
adb logcat -d -s GooPopupSmoke:I

The PASS log covers popup rendering, scale, placement updates, nested teardown, menu focus and owner pause.

The package checker verifies both ABI payloads against build provenance, checks dependencies and page alignment, and rejects legacy native renderer libraries. For a single-ABI build pass --abi arm64-v8a or --abi x86_64. The separate zipalign check verifies 16 KB alignment within the installable APK.

Device checks

Record OS, device, driver, package version, actions, and results. Report untested combinations.

On a compatible device, check first-frame text and image rendering, taps, touch scrolling, keyboard entry, selection and composition, password privacy, rotation, background/resume, and Surface destruction/recreation. Increment the counter before recreating only the Surface and confirm that its value remains. API/ABI and archive checks do not establish device rendering or input correctness.

Product Compatible and additional computed target framework versions.
.NET net10.0-android36.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0-android36.0

    • Goo (>= 0.28.6)

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Goo.Android:

Package Downloads
Goo.NativeMenus

Optional operating-system menu bars and context menus for Goo.

Goo.NativeDialogs

Owned native message boxes for Goo windows.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.31.0 0 11/14/2026
0.30.0 99 11/11/2026
0.29.1 95 11/8/2026
0.29.0 89 11/8/2026
0.28.7 95 11/7/2026
0.28.6 96 11/7/2026
0.28.5 92 11/7/2026
0.28.4 94 11/7/2026
0.28.3 98 11/6/2026
0.28.2 96 11/6/2026
0.28.1 93 11/5/2026
0.27.11 100 11/5/2026
0.27.10 97 11/4/2026
0.27.9 101 11/4/2026
0.27.8 98 11/4/2026
0.27.7 102 11/3/2026
0.27.6 93 11/3/2026
0.27.5 91 11/3/2026
0.27.4 94 11/3/2026
0.27.3 94 11/3/2026
Loading failed