Goo.Android
0.29.0
See the version list below for details.
dotnet add package Goo.Android --version 0.29.0
NuGet\Install-Package Goo.Android -Version 0.29.0
<PackageReference Include="Goo.Android" Version="0.29.0" />
<PackageVersion Include="Goo.Android" Version="0.29.0" />
<PackageReference Include="Goo.Android" />
paket add Goo.Android --version 0.29.0
#r "nuget: Goo.Android, 0.29.0"
#:package Goo.Android@0.29.0
#addin nuget:?package=Goo.Android&version=0.29.0
#tool nuget:?package=Goo.Android&version=0.29.0
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-versionto select the matching Goo and Goo.Android release. - Set
AndroidSdkDirectoryandJavaSdkDirectoryif tool discovery fails.
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
OnNewIntentuse the same UI-thread event. IsInitialmarks the first process launch. Activity recreation does not replay the old Intent.- Override
OnNewIntentonly if needed. Call base, then readLastActivationStatusfor 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
TakePersistableUriPermissiononly 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 useandroid-x64. - A Vulkan-capable emulator is required. Query its actual device support with
adb shell cmd gpu vkjsonbefore 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);
}
SavedInstanceStateholds Android's Bundle duringCreateWindow(). 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.InputOptionsandTextEditor.InputOptions. - Android maps hints to
EditorInfoand 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.Passwordmasks TextEntry likePassword: 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" } }
GooViewexposes 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.
GooViewownsWindow.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.Livesets 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
UiAutomationon 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
PopupWindowwith 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 returnsFailed. - 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 | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0-android36.0 is compatible. |
-
net10.0-android36.0
- Goo (>= 0.29.0)
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 |