sixty dollars

A tablet the manufacturer stopped updating, a bootloader the internet said was welded shut, and a toggle sitting in Developer options that nobody checked.

Sixty dollars buys an 8-inch Walmart tablet that onn shipped, patched to Android 15, and then walked away from. Its last security patch is dated 2025-11-05. Every source I could find said the bootloader on this generation could not be unlocked, so the plan was built around exploiting the MediaTek BootROM to get around it.

The plan was unnecessary. The toggle was in Developer options and it wasn’t greyed out.

Before and after

Same hardware. Same 2.9 GB of RAM. Everything below changed in one evening, and only one step was irreversible.

Stock — onn TTA.23LineageOS 23.2 GSI
Android15 · SDK 3516 · SDK 36
Patch level2025-11-052026-05-01
Google pkgs~10022
Walmart pkgs20
Storage used6.2 GB1.1 GB
Bootloaderlockedunlocked
Future updatesnoneongoing

Security patches six months newer than anything the manufacturer ever shipped, on a device the manufacturer considers finished.

The thing underneath is an MT8786V/N — platform mt6768, Helio G80 class, eight cores at 1.80 GHz, arm64-v8a. 2,927,424 kB of RAM. 21 GB of usable eMMC. An 800 × 1280 panel at about 189 ppi. A/B slots, dynamic partitions, virtual A/B, and ro.treble.enabled = true — which is the row that makes the rest of this possible.

The closed box

Before any of that, the tablet had to be found.

A full scan of the top 200 ports came back with every one refused. No ADB. No services. Nothing on the network to talk to at all. Identification came sideways: the Wi-Fi controller’s client list gave up a DHCP hostname, onn-8-Tablet. The MAC address would never have told me — Android randomizes it per network by default.

Getting a shell is easier than it sounds once you know the shape. Wireless debugging opens two high ports, one to pair against and one to connect to. A port scan finds them; a six-digit code off the tablet’s own screen completes the pairing. That was enough to read every property that decides what happens next.

ro.build.version.release   = 15
ro.product.model           = 100135923
ro.board.platform          = mt6768
sys.oem_unlock_allowed     = 0
ro.oem_unlock_supported    =            ← empty
ro.boot.flash.locked       = 1
ro.boot.verifiedbootstate  = green

Read that block the way I read it and it says: locked, verified, and not offering.

The unlock nobody thought existed

Every source pointed the same direction. ro.oem_unlock_supported came back empty. fastboot flashing unlock was reported as rejected on this exact model, by people who owned one. The plan pivoted toward MediaTek BootROM exploitation, which is the serious version of this problem — you talk to the chip before the operating system exists and you do not get a second chance to be careless.

mtkclient never completed a handshake. The preloader was detected over and over and the handshake failed every single time.

None of that mattered, because the actual answer was in the settings menu. The OEM unlocking toggle was present in Developer options and it was not greyed out. Flipping it moved sys.oem_unlock_allowed from 0 to 1.

That is the property that counts. The empty one is a red herring — plenty of devices leave ro.oem_unlock_supported unset and unlock fine. It proves nothing, and an entire forum consensus was built on it proving something.

$ fastboot flashing unlock
(bootloader) Start unlock flow
OKAY [ 31.295s]
$ fastboot getvar unlocked
unlocked: yes

Thirty-one seconds.

The on-device unlock warning: red text, volume-key confirmation, and the word "phone" on an 8-inch tablet

The warning screen deserves its own note. It is the one screen in the entire boot process that is legally supposed to be serious — the red text, the volume-key confirmation, the point past which the warranty story changes. And it calls this 8-inch tablet a “phone.” Twice. “Install custom operating system software on this phone.” “Delete all personal data from your phone.” Nobody at onn customised the screen, on the device whose bootloader everybody said onn had locked down on purpose.

The same evening retired a second myth. This tablet shipped on Android 14 and was running 15 when I bought it. “onn never ships major upgrades” was wrong too.

Distrust the backup

An unlocked bootloader only gets you out of a bad flash if you have something to flash back, and there is no official firmware for this model. What exists is a community SourceForge project hosting factory dumps pulled off these tablets with mtkclient. 100135923-TTA.33, 3.37 GB. It matched the publisher’s SHA-256 exactly.

That archive passed its integrity check. Here is what was inside it:

init_boot_a.bin   nonzero bytes: 0     ← entirely empty
vendor_boot_a.bin nonzero bytes: 0     ← entirely empty
boot_a.bin        magic: ANDROID!  21.7 MB
super.bin         magic@4096: gDla  6.78 GB

Two of the partitions in my restore point were zeros. Not corrupt — empty. Whoever captured the dump never captured them, and the archive checksummed perfectly anyway, because a CRC proves the bytes survived the download. It says nothing about whether anyone read anything off the device in the first place.

Flashing either of those images to recover from a bad flash would have written zeros across a working partition. The recovery step would have been the brick.

super.bin — the one the plan actually depended on — carried a valid logical-partition header and real data sampled all the way through. That is the check that made the rest of the evening safe to attempt. Verify magic numbers, then sample for non-zero data, before you trust anything as a way back.

Two related rules, cheap to state and expensive to learn: never restore nvram, nvdata, persist or proinfo out of somebody else’s dump — those carry per-device calibration and radio identifiers, and unlike the system images they are not generic. And MediaTek bootloaders do not implement fastboot boot, so there is no RAM-only dry run. A patched boot image cannot be tested without committing it to flash. Plan the recovery path before flashing, not after.

The flash, and the screen that looks like a brick

A GSI is not signed with the vendor’s keys, so verification comes off first. Then system turned out to be 1.07 GB against a 2.10 GB image, which is normal — deleting product freed the 2.73 GB needed, because a GSI brings its own.

$ fastboot --disable-verity --disable-verification flash vbmeta vbmeta.bin
$ fastboot reboot fastboot            # userspace fastboot
$ fastboot delete-logical-partition product_b
$ fastboot flash system gsi_system.img
Finished. Total time: 67.521s
$ fastboot -w && fastboot reboot
The tablet's own fastbootd screen, still reporting "Secure boot - yes" in yellow. The serial-number line is covered by an opaque bar.

Sixty-seven seconds to write a different operating system onto a tablet whose own fastbootd screen was still cheerfully reporting Secure boot - yes in yellow.

Then the first boot landed on “Can’t load Android system. Your data may be corrupt.”

It looks exactly like failure and it is not. fastboot -w had already told me what it did — “Erase successful, but not automatically formatting” — leaving userdata wiped but with no filesystem on it. Choosing Factory data reset on that scary screen formats it properly. It booted straight through afterwards.

The AOSP setup wizard's "Hi there" screen on the tablet, ten minutes after the flash

The photo timestamps tell the honest version of the story better than I can. Unlock warning at 20:15. Fastbootd at 21:00. The setup wizard at 21:10. Under an hour from the irreversible step to a running system.

What actually works

No GSI compatibility report existed for t302v. People are still asking on XDA whether this model unlocks at all. So this table is the give-back — everything in it measured on the running system after the flash, not inferred.

ComponentStateEvidence
Wi-Fiworksassociated, DHCP lease held
Bluetoothworksenabled: true · state ON
AudioworkscontrolC0 + 24 PCM nodes
Cameraenumerates2 devices — not yet shot-tested
Touchworksjadard-touchscreen
AccelerometerworksMTK, 5–200 Hz
Light sensorworksauto-brightness available
Batteryworkslevel + health reported
Gyroscopeabsentnot fitted — hardware, not the GSI
Magnetometerabsentnot fitted — hardware, not the GSI

A clean sweep. Every fitted component works, and the two that don’t were never soldered on. For a device with no prior GSI coverage that is an unusually good result — camera and sensors are normally the first casualties.

The rice, briefly

Nova Launcher, the answer to this question for a decade, has shut down; Lawnchair is the answer now. Chakra Petch across all five font slots, a 4×5 grid with a four-slot dock, Delta icons, true black.

The one setting worth writing down is density. At 800 px wide, wm density 214 puts the device at 598 dp — just under Android’s 600 dp threshold, which drops it out of tablet mode into phone mode. In tablet mode the nav bar is drawn by Launcher3’s Taskbar rather than SystemUI, so a third-party launcher’s dock and the system’s buttons fight over the same strip of glass. Below 600 dp, SystemUI draws a normal three-button bar on its own row and the fight stops. Stock ran 152; LineageOS defaults to 180.

The debloat had one genuinely funny find: me.phh.qcrilam, a Qualcomm radio-interface adapter riding along in the GSI base, sitting disabled-in-spirit and enabled-in-fact on a MediaTek device.

Two things on this tablet earn more than a line.

The first is the wallpaper. It is 103 lines of GLSL — a perspective grid scrolling toward the viewer, a banded sun, stars twinkling on their own hash function, scanlines, a vignette — running at 58–59 fps on a Helio G80. Shader Editor has no import function, so it went in one adb shell invocation per line, 103 of them, from the host. (A device-side while read loop returns Failure calling service input: Failed transaction for every iteration and silently produces nothing. Ask me how I know: 370 times.)

It costs 36% of one core while the home screen is visible, with surfaceflinger taking another 29% alongside it. Screen off, or any app in the foreground, and both drop to 0.0%. On eight cores, 65% of one core is about 8% of the machine, and only while you’re looking at it.

The second is the panel above the sun, which is not an app icon that leads to telemetry. It is telemetry. Home Assistant’s companion app ships a Template widget that renders arbitrary Jinja on the home screen, so the tablet displays WAN throughput, HA host memory and disk, Nextcloud load, printer nozzle and bed temperatures, office climate, and a running count of how many of 326 sensors are currently dead.

A live lab dashboard, on a sixty-dollar tablet, rendering server-side truth instead of a shortcut to it.

The optimisation that failed on a delay

Here is the part I want on record, because it is the only thing in this build log that fooled me completely.

The obvious optimisation for a fragment shader is dropping precision highp float to mediump. Mali-G52 runs mediump at roughly double rate. So it got changed.

It rendered pixel-identical. Measured 65.7% → 64.0% of one core, which is noise. Screenshot, benchmark, declare it fine, move on. Every signal available at the time said the change was safe and pointless, and “safe and pointless” is a conclusion you stop thinking about.

It is not fine. The scroll term is gz = z * 2.0 - time * 1.1, and time grows without bound. A mediump float carries about 11 bits of mantissa, so once time * 1.1 passes roughly 2048, representable values are a quarter apart and fract() can no longer resolve sub-unit positions. The grid lines go thick, snap between coarse steps, and flicker. gx = p.x * z * 12.0 degrades the same way once z gets large.

It looks perfect for the first few minutes. That is long enough to screenshot it, measure it, write it up and stop paying attention. The failure was reported later, by somebody actually looking at the tablet.

The measurement wasn’t wrong. The measurement was taken at time ≈ 30 on a bug that needs time ≈ 1860. Measure animated things after they have been running a while, not seconds after you apply them.

Reverting was a one-word edit.

Still open

Three loose ends, stated rather than tidied.

Five Quick Settings tiles never got added — Night Light, Reading mode, Location, System profiles, Extra dim. The removals landed; the additions didn’t. sysui_qs_tiles is a read-only mirror on Android 16, so writes to it are silently reverted and the tile set has to be edited in the UI.

Launcher3 is probably ~90 MB of reclaimable space, since density 214 puts this device in phone mode where SystemUI owns the nav bar. Untested, because getting it wrong removes the navigation bar entirely — which has already happened once — and undoing it needs a working adb session.

And Android 16 has real desktop windowing, gated at 600 dp. The density fix that stopped the taskbar colliding with the nav bar deliberately parks this device at 598. Saved window layouts and the nav bar you actually want are, for now, in direct conflict, and I have not resolved it.

Sixty dollars, one evening, and a toggle that was never greyed out.