- Shell 99.4%
- Dockerfile 0.6%
| .forgejo/workflows | ||
| build-patches | ||
| overlay/system | ||
| ramdisk-overlay/scripts | ||
| ramdisk-recovery-overlay | ||
| scripts | ||
| vendor-ramdisk-overlay/placeholder | ||
| .gitignore | ||
| .gitlab-ci.yml | ||
| bootconfig | ||
| build.sh | ||
| deviceinfo | ||
| Dockerfile | ||
| google-tegu.dtb | ||
| README.md | ||
Ubuntu Touch for Google Pixel 9a (tegu)
This is an experiment of porting Ubuntu Touch to the Google Pixel 9a (codename tegu). It is very much a work in progress and I will say this upfront: I'm not experienced with Android development. AI (Claude) did a lot of work, so do expect slop in places.
Status
This is not a port that is ready for general use. OTA updates probably don't work (untested).
| Actors | ✅ 24+ hours battery lifetime | Network (cont.) |
| ✅ Manual brightness | ✅ 7+ days stability | ❓ Hotspot |
| ❌ Torchlight | ❓ NFC | |
| ✅ Vibration | GPU | ✅ WiFi |
| ✅ Boot into UI | ||
| Camera | ⚠️ Hardware video playback^3 | Sensors |
| ✅ Flashlight | ❓ Automatic brightness | |
| ✅ Photo | Misc | ❌ Fingerprint reader |
| ⚠️ Video^1 | ✅ AppArmor patches | ❓ GPS |
| ✅ Switching between cameras^2 | ✅ Battery percentage | ❓ Proximity |
| ❓ Offline charging | ✅ Rotation | |
| Cellular | ✅ Online charging | ✅ Touchscreen |
| ✅ Carrier info, signal strength | ✅ Wireless Charging | ❌ Double touch to wake |
| ✅ Data connection | ❓ Recovery image | |
| ❓ Dual SIM functionality | ❓ Reset to factory defaults | Sound |
| ⚠️ Incoming, outgoing calls | ❓ SD card storage | ❓ Earphones |
| ❓ LTE calling (VoLTE) | ❓ RTC time | ✅ Loudspeaker |
| ❓ MMS in, out | ⚠️ Shutdown / Reboot^4 | ✅ Microphone |
| ❓ PIN unlock | ❓ Wireless External monitor | ⚠️ Volume control^7 |
| ❓ SMS in, out | ❌ Waydroid^5 | |
| ❓ Change audio routings | USB | |
| ❌ Voice in calls | Network | ❓ MTP access |
| ❓ Volume control in calls | ❌ Bluetooth^6 | ❓ ADB access |
| ❓ Flight mode | ❓ Wired External monitor |
✅ Working ❌ Not Working ⚠️ Partial ❓ Untested / Unknown
^1: When starting the first recording, the Camera app hangs for ~1 minute until the audio permission dialog pops up. Afterwards, video records fine in 4k, but the recording is missing audio.
^2: Switching between front and main works, ultrawide not accessible because it is exposed as a single camera and the Ubuntu Touch camera doesn't support 0.x zoom ratios.
^3: Hardware decoding seems to work, but CPU usage for rendering itself seems quite high.
^4: I've seen the userdata partition get corrupted on reboots. I think this was caused by me formatting it in LineageOS recovery with newer ext4 features that the initramfs's tools don't support. Marking as partial until I verified it doesn't happen on a clean flash.
^5: Waydroid starts with a custom build and patches to the Waydroid init script. Those are not included in the current overlay.
^6: I did not look into it yet, maybe easy to fix.
^7: The custom AIDL audio module does not implement volume control, so it currently happens on the pulseaudio side in software.
Also see the issues for known problems.
Build caveats
This port does NOT use the blessed way of building Ubuntu Touch. The reason is that the Ubuntu Touch build tools don't currently support Android 16 and we need to do some adjustments to the kernel build itself and include custom halium patches.
Instead, we manually build the kernel
for tegu based on LineageOS sources and some patches.
This gives us a zip containing Image.lz4, boot.img, dtb.img, dtbo.img, system_dlkm.img,
vendor_dlkm.flatten.img and vendor_kernel_boot.img.
Of those, dtbo.img, system_dlkm.img, vendor_dlkm.flatten.img and vendor_kernel_boot.img are flashed
verbatim. dtb.img is not flashed (the device has no standalone dtb partition); it's used as a source
and embedded into vendor_boot.img below. The kernel's own boot.img is discarded - Android 16 uses
boot-image header v4, which splits the kernel, the DTB and the ramdisk across separate partitions, so we
repack them ourselves:
boot.imgis rebuilt from the extractedImage.lz4only (kernel, no ramdisk, no DTB).init_boot.imgcarries the ramdisk: Halium's genericinitrd.img-touch-arm64with this repo'sramdisk-overlay/cpio-appended and re-LZ4-compressed.vendor_boot.imgis rebuilt locally so we can embed the DTB (the freshly-builtdtb.imgfrom the kernel zip), inject bootconfig, and add the contents ofvendor-ramdisk-overlay/. Thegoogle-tegu.dtbin this repo is currently unused (it was copied from LineageOS).
All three are produced by make-bootimage.sh in halium-generic-adaptation-build-tools.
The local patch in build-patches/halium-generic-adaptation-build-tools/ only teaches the surrounding scripts about our patches
(local GSI tarball, libhybris overlay, clang revision).
Finally, CI downloads a LineageOS 23.2 zip and extracts vendor, system_ext and product from its
payload.bin. Those, together with the Ubuntu Touch system.img, system_dlkm.img and
vendor_dlkm.flatten.img, are fused into a single flashable super.img via lpmake
(see scripts/build-super-image.sh).
Patches
The system includes a bunch of patches layered on top of the latest android9plus rootfs:
- Halium 16 patches: these are currently fetched from a zip file, because I deleted the temporay cloud VM I used when building the patches. I need to revisit the build.
- Device specific overlays:
libgbinder-radio.so.1patched to relax type checkslibgbinder.sooverlaid with v1.1.45 (most likely unnecessary now, since 1.1.45 is already included in the rootfs)- pulseaudio
module-droid-aidl.socustom AIDL pulseaudio module (I think it leaks memory) - I wasn't able to create a workingaudio_policy_configuration.xmlfor tegu. Tegu only seems to support AIDL for audio. - sensorfwd 0.15.1 ships the AIDL backend patch but does not include commit
16b56bc(backend split that drops the blockinggbinder_servicemanager_waiton hwbinder). On A16 there is nohwservicemanager, so the wait blocked forever. Rebuiltlibhidlsensorfw-qt5.so.1.0.0with sailfishos/sensorfw PR #31 backends, overlaid into/usr/lib/aarch64-linux-gnu/. - Mali TLS - Vendor
.sos use ELF TLS (initial-exec), readingtpidr_el0directly. In a libhybris environment that's glibc's TCB, not bionic's — segfault. Worked around viaLD_PRELOAD. This should be re-visited, as there's already alibtls-padding.so, which does something similar. Right now, we overlaylibhybris-common.so.1overlibtls-padding.so. Patches from upstream PR.
Device quirks
Some notes on device specific quirks and their workarounds in this port.
Kernel / cmdline
- Mali GPU PID-namespace crash -
gpu_dvfs_kctx_initinpixel_gpu_dvfs_metrics.ccallsfind_get_pid()+get_pid_task()to attribute DVFS power to a UID. GPU clients in the Halium LXC live in a separate PID namespace, soget_pid_task()returns NULL and the nexttask->cred->uidderef oopses. Patched the Pixel Mali platform code to fall back toGLOBAL_ROOT_UIDwhen the task lookup fails — the UID is only used for per-UID power metrics, so it's cosmetic. vendor_dlkm.flatten.imgwas missingetc/- Withoutetc/init.insmod.tegu.cfg,insmod.shnever setvendor.all.modules.ready=1, the cs40l26 vibrator HAL stayed disabled and there was no haptic feedback. Patched kleaf'sbuild_flattened_dlkm_image()to also copyetc/into the flatten staging dir.- pKVM disabled (
kvm-arm.mode=none) - I think there was an issue loading some vendor modules with pKVM enabled, so it is disabled in the cmdline. Need to revisit that. - Kernel binder patched for AppArmor - Vendor binaries (HWC3, etc.) link the stock vendor libbinder, which sets
FLAT_BINDER_FLAG_TXN_SECURITY_CTXon parceled binder objects. With AppArmor as the active LSM,security_secid_to_secctx()returns-EINVAL, the kernel returnsBR_FAILED_REPLYfor every transaction, and HWC3 crash-loops. Patcheddrivers/android/binder.cto setsecctx = NULL; secctx_sz = 0; ret = 0instead ofgoto err_get_secctx_failed. This may not be necessary after some more SELinux patches.
See https://git.deusch.me/ubports/kernel-patches-tegu.
Hardware / userspace
- Touch co-processor edge filtering - Google's GTI touch-offload pipeline (Synaptics TCM → co-processor → twoshay → v4l2) suppressed mid-edge swipes, breaking Lomiri's app drawer / app switcher gestures. Disabling
offload_enabledandv4l2_enabledin the GTI sysfs routes raw events straight to/dev/input/event3. Persisted via thedevice-hacksscript in the overlay. - WiFi works but NetworkManager has to ignore an aware interface - A
aware_nmi*interface gets exposed by the driver and causes a kernel panic when NetworkManager touches it. We tell NM to leave it alone viaoverlay/system/etc/NetworkManager/conf.d/10-ignore-aware-nmi.conf(unmanaged-devices=interface-name:aware_nmi*).
Flashing
I flashed the latest Android 16 from Google before doing this (CP1A.260405.005 (15001963)).
Download the latest images CI artifact from the releases.
Extract the zip, cd into the folder. Then, from bootloader, flash via fastboot:
$ fastboot flash boot boot.img
Sending 'boot_a' (65536 KB) OKAY [ 1.522s]
Writing 'boot_a' OKAY [ 0.282s]
Finished. Total time: 1.830s
$ fastboot flash dtbo dtbo.img
Sending 'dtbo_a' (1573 KB) OKAY [ 0.049s]
Writing 'dtbo_a' OKAY [ 0.049s]
Finished. Total time: 0.125s
$ fastboot flash init_boot init_boot.img
Sending 'init_boot_a' (8192 KB) OKAY [ 0.201s]
Writing 'init_boot_a' OKAY [ 0.071s]
Finished. Total time: 0.300s
$ fastboot flash vendor_boot vendor_boot.img
Sending 'vendor_boot_a' (17112 KB) OKAY [ 0.395s]
Writing 'vendor_boot_a' OKAY [ 0.102s]
Finished. Total time: 0.551s
$ fastboot flash vendor_kernel_boot vendor_kernel_boot.img
Sending 'vendor_kernel_boot_a' (7936 KB) OKAY [ 0.190s]
Writing 'vendor_kernel_boot_a' OKAY [ 0.071s]
Finished. Total time: 0.296s
$ fastboot flash super super.img
Sending sparse 'super' 1/22 (252321 KB) OKAY [ 5.948s]
Writing 'super' OKAY [ 1.635s]
Sending sparse 'super' 2/22 (254732 KB) OKAY [ 6.152s]
Writing 'super' OKAY [ 1.721s]
Sending sparse 'super' 3/22 (252687 KB) OKAY [ 6.010s]
Writing 'super' OKAY [ 1.537s]
Sending sparse 'super' 4/22 (252909 KB) OKAY [ 6.106s]
Writing 'super' OKAY [ 2.064s]
Sending sparse 'super' 5/22 (253817 KB) OKAY [ 6.286s]
Writing 'super' OKAY [ 2.130s]
Sending sparse 'super' 6/22 (254745 KB) OKAY [ 5.876s]
Writing 'super' OKAY [ 1.082s]
Sending sparse 'super' 7/22 (233973 KB) OKAY [ 5.408s]
Writing 'super' OKAY [ 0.951s]
Sending sparse 'super' 8/22 (254936 KB) OKAY [ 5.855s]
Writing 'super' OKAY [ 1.203s]
Sending sparse 'super' 9/22 (254126 KB) OKAY [ 5.952s]
Writing 'super' OKAY [ 1.262s]
Sending sparse 'super' 10/22 (254972 KB) OKAY [ 6.038s]
Writing 'super' OKAY [ 1.592s]
Sending sparse 'super' 11/22 (254972 KB) OKAY [ 5.850s]
Writing 'super' OKAY [ 1.005s]
Sending sparse 'super' 12/22 (244276 KB) OKAY [ 5.592s]
Writing 'super' OKAY [ 1.006s]
Sending sparse 'super' 13/22 (254972 KB) OKAY [ 5.855s]
Writing 'super' OKAY [ 1.663s]
Sending sparse 'super' 14/22 (242440 KB) OKAY [ 5.559s]
Writing 'super' OKAY [ 1.372s]
Sending sparse 'super' 15/22 (226328 KB) OKAY [ 5.219s]
Writing 'super' OKAY [ 0.857s]
Sending sparse 'super' 16/22 (223292 KB) OKAY [ 5.108s]
Writing 'super' OKAY [ 0.882s]
Sending sparse 'super' 17/22 (237301 KB) OKAY [ 5.428s]
Writing 'super' OKAY [ 0.935s]
Sending sparse 'super' 18/22 (240952 KB) OKAY [ 5.519s]
Writing 'super' OKAY [ 5.447s]
Sending sparse 'super' 19/22 (254972 KB) OKAY [ 5.827s]
Writing 'super' OKAY [ 0.986s]
Sending sparse 'super' 20/22 (231796 KB) OKAY [ 5.357s]
Writing 'super' OKAY [ 0.906s]
Sending sparse 'super' 21/22 (240340 KB) OKAY [ 5.521s]
Writing 'super' OKAY [ 0.940s]
Sending sparse 'super' 22/22 (98892 KB) OKAY [ 2.260s]
Writing 'super' OKAY [ 0.447s]
Finished. Total time: 155.052s
$ fastboot erase userdata
Erasing 'userdata' OKAY [ 0.189s]
Finished. Total time: 0.190s
$ fastboot reboot
Rebooting OKAY [ 0.000s]
Finished. Total time: 0.051s