============================== The Onyx/Boox platform layer ============================== What turns a Qualcomm ``lagoon`` reference build into a Boox e-reader is mostly implemented above the hardware bridge, in the Android platform itself. This page catalogues that customisation as far as it is statically visible: the system/vendor version split, the native Onyx SDK, the configuration and policy glue, and the preinstalled Onyx apps. Coverage of application logic here is shallow — the bulk of the Boox behaviour is compiled Java in odex/vdex'd framework and app archives, which are not decompiled in this documentation set — but the *shape* of the layer, and the one genuinely load-bearing structural fact (the API-level split), are clear from the partition metadata. Everything here is from the ``system``/``system_ext``/``product``/``vendor`` partition listings and the extracted build properties and init scripts. No code was executed. A GSI-style Android-15 system on an Android-11 vendor ===================================================== "The Android version" is not a single number on this device — three layers carry independent version stamps, and only the **system** stamp is the platform version the user sees: .. list-table:: :header-rows: 1 :widths: 34 32 34 * - Layer - Version (authoritative property) - Notes * - kernel - **Linux 4.19.157** - the SM6350 ``lagoon`` BSP kernel line; decoupled from the Android userspace version (GKI) — a 4.19 kernel under Android 15 is normal * - vendor / boot - **Android 11** (``ro.vendor.build.version.release=11``, ``sdk=30``) - the ``Onyx/NoteAir5C`` lagoon BSP + HALs; vendor sepolicy version ``30.0``; vendor security patch ``2025-10-01`` * - system / system_ext / product - **Android 15** (``ro.build.version.release=15``, ``sdk=35``) - the ``ONYX/TabBoox`` unified Onyx system; sepolicy ``202404`` [#sepolicy-infer]_; system security patch ``2026-06-01`` .. [#sepolicy-infer] Weaker evidence than the vendor row: vendor sepolicy ``30.0`` is a literal value read out of ``vendor/etc/selinux/plat_sepolicy_vers.txt``. The system-side ``202404`` has no equivalent version-property file — it's inferred from the ``etc/selinux/mapping/202404.cil`` filename under ``product``/ ``system_ext``, which is a strong signal but not the same kind of proof as reading a literal version string. So the **vendor** and **boot** images are the Android-11 ``lagoon`` BSP built for this specific device (``NoteAir5C``), while the **system / system_ext / product** images are a *generic Android-15 Onyx system* named ``TabBoox`` — inferred, from the generic naming and Treble/GSI-style layering, to be a single unified Boox tablet system shipped across many models on top of a per-device Android-11 vendor; no cross-device comparison was done to confirm this. This is the structural key to several observations elsewhere: it is why ``keystore2`` (an Android-12+ daemon) runs over Android-11 ``keymaster@4.1`` (:doc:`security`), and why the framework is four releases newer than the ``lagoon`` HALs it sits on. .. note:: The ``ro.build.fingerprint`` **string** reads ``ONYX/TabBoox/TabBoox:13/TKQ1.230615.001/…`` — a stale ``:13`` release token and an old Tiramisu build-ID left in the hand-built fingerprint. It is **not** the version: the authoritative ``ro.build.version.release`` / ``…sdk`` are ``15`` / ``35``, corroborated by the Android-15 SELinux policy version (``202404``) and the 2026-06 system patch. Read the version *properties*, not the fingerprint token. The native Onyx SDK =================== The Onyx-specific native libraries in ``/system/lib64`` (a few also 32-bit): .. list-table:: :header-rows: 1 :widths: 32 68 * - Library - Role (where determinable) * - ``libonyx_epd_listener.so`` - the EPD update service — the userspace end of ``/dev/ebc`` (:doc:`/display/stack`) * - ``libonyx_neo_dither.so`` / ``libonyx_cfa.so`` - e-ink dithering and colour-filter-array / RGBW colour (:doc:`/display/stack`) * - ``libonyx_pen_touch_reader.so`` - low-latency pen/touch input reader * - ``libonyx_stardict.so`` / ``libonyx_mdict.so`` - dictionary engines (StarDict / MDict formats — the reader's lookup) * - ``libonyx_lame.so`` - LAME MP3 encoder (audio note / recording export) * - ``libonyx_algorithm.so`` / ``libonyx_babylon.so`` / ``libonyx_dsl.so`` - Onyx algorithm helpers (image/text processing; ``dsl`` is another dictionary format) — internals not decompiled * - ``libmmkv-onyx.so`` - Onyx's build of Tencent **MMKV**, the key-value store backing ``/onyxconfig`` (below) * - ``libonyx_memory_test.so`` - a memory self-test helper The display and input libraries are documented in :doc:`/display/stack`; the rest are reader/app-support libraries. Note there is **no** separate Onyx framework ``.jar`` — the framework-level customisation is compiled into the standard ``framework.jar`` / ``services.jar`` of the TabBoox system image (odex'd, not examined here). Inside the native Onyx SDK: static RE ===================================== The libraries above were catalogued by name; disassembling the e-ink/pen-related ones (``nm -D`` / ``objdump -T`` symbols, ``strings``, and capstone on the exported functions) shows what each actually does and what it does not. All are ordinary AArch64 ELF shared objects that retain their dynamic symbols; the C++ is demangled below. One structural correction falls out immediately: **none of the ``libonyx_*`` libraries open ``/dev/ebc``**. The EPD listener talks to a FIFO, the pen reader talks to ``evdev``, and the two "colour" libraries are pure bitmap helpers. The actual ``/dev/ebc`` update client lives in the patched AOSP graphics stack (`The userspace to kernel EPD update path`_). The EPD event FIFO listener --------------------------- ``libonyx_epd_listener.so`` (JNI class ``android/onyx/optimization/EpdEventListener``, class ``EpdListener``, log tag ``lib_epd_listener``) is an **event forwarder, not an update driver**. Its dynamic-symbol imports are ``__open_2``, ``eventfd``, ``poll``, ``read``, ``write``, ``close`` — there is **no ``ioctl`` and no ``mmap``**. Disassembly of ``EpdListener::setup`` shows it ``__open_2(path, O_RDWR)`` a **FIFO** (the failure log reads ``Could not open fifo: %s %s``), creates an ``eventfd``, and fills a two-entry ``pollfd`` array (FIFO + eventfd). ``readEpdEventLoopImpl`` then loops ``poll(fds, 2, -1)``; on ``POLLIN`` it ``read``\ s up to 1024 bytes, wraps them as a ``std::string`` and hands them to a Java ``onEpdEvent(String)`` callback. The eventfd is the shutdown wakeup — ``closeDevice`` ``write``\ s an 8-byte token to it ("notify event fd"). The FIFO path is passed in from Java as a ``jstring`` (``nativeStart`` calls ``GetStringUTFChars``), so it is not a literal in the ``.so``; it is the ``/dev/onyx/listener`` FIFO that ``init.onyx.rc`` creates (:doc:`/display/stack`). So this library is a text-event channel *from* the framework/SurfaceFlinger *to* the app, not the pixel path. The raw pen/stylus reader ------------------------- ``libonyx_pen_touch_reader.so`` (JNI class ``com/onyx/android/sdk/pen/RawInputReader``, log tags ``lib_touch_reader`` / ``onyx_emp``) is the **low-latency handwriting input path** — it reads the pen digitiser straight from ``evdev``, bypassing the Android ``InputReader``. It carries a full C++ engine: ``TouchReader`` (device + read loop), ``PenManager`` (pressure, clip regions, erase state) and a per-mode reader family ``DrawReader`` / ``BtnReader`` / ``EraseReader`` / ``SideEraseReader``. ``TouchReader::findDevice`` iterates ``/dev/input/event0``–``event15`` and selects the node whose ``EVIOCGNAME`` string contains **``Wacom``**, **``hanvon``** or **``onyx_emp``** (the immediates are visible in the disassembly). ``openDevice`` then queries it with the standard evdev ioctls, recovered as request numbers: .. list-table:: :header-rows: 1 :widths: 34 22 44 * - ioctl - request - use * - ``EVIOCGVERSION`` - ``0x80044501`` - driver version * - ``EVIOCGID`` - ``0x80084502`` - bus/vendor/product id * - ``EVIOCGNAME(79)`` - ``0x804f4506`` - device name (matched above) * - ``EVIOCGPHYS(79)`` / ``EVIOCGUNIQ(79)`` - ``0x804f4507`` / ``0x804f4508`` - physical location / unique id * - ``EVIOCGABS(ABS_PRESSURE)`` - ``0x80184558`` - ``input_absinfo.maximum`` → the pen's max pressure, stored for ``PenManager::normalizePressure`` The read loop ``poll``\ s (same FIFO+eventfd wakeup pattern as the EPD listener) and ``read``\ s **24-byte ``struct input_event``** records (``type`` at +0x10, ``code`` at +0x12, ``value`` at +0x14), timestamps them, and feeds ``TouchConsumer::processTouchEvent`` → the Java ``onTouchPointReceived`` callback. This is the reader that fills the ION handwriting buffer (``CONFIG_ONYX_EPDC_HANDWRITE_BUF_MALLOC_FROM_ION``, :doc:`/display/stack`). Bitmap pre-processors: dither and RGBW -------------------------------------- ``libonyx_neo_dither.so`` and ``libonyx_cfa.so`` are **pure JNI bitmap helpers** over ``libjnigraphics`` — neither opens ``/dev`` or calls ``ioctl``; they transform pixels *before* the framework submits the buffer, and are **not** the "userspace end of ``/dev/ebc``" the name might suggest. - ``libonyx_neo_dither.so`` (log tag ``neo_color_filter``) exports ``DitherUtils.dither`` / ``DitherUtils.ditherColor`` and the classes ``imgfilter::ImageFilter`` / ``ImageColorFilter`` / ``ImageFilterRGB565``, each a ``doFilterInPlace(AndroidBitmapFormat, ptr, w, h)`` that dithers the locked bitmap in place (``RGBA_8888`` and ``RGB_565``). - ``libonyx_cfa.so`` (log tag ``onyx_cfa``) exports ``QRCodeUtil.toRgbwBitmap`` and a ``ColorUtils`` with ``red/green/blue/gray/white`` + ``toRed/toGreen/toBlue/toWhite`` — it packs an ``RGBA_8888`` bitmap into the panel's **RGBW** sub-pixel layout (the colour filter array for the Kaleido-class panel, :doc:`/display/stack`). The two smallest e-ink-adjacent libraries are ``libonyx_algorithm.so`` (a single ``scribble.utils.Algorithm.distance`` geometry helper) and ``libonyx_dsl.so`` (an ABBYY-Lingvo *DSL dictionary* provider — not e-ink at all). Several of these libraries also embed a shared ``DeviceUtils::isValid`` helper that calls back into Java ``android/hardware/DeviceController.systemIntegrityCheck()Z`` — a soft anti-tamper gate; it is orthogonal to the display path. Deeper static RE: the small helper libs, the pen pressure curve, and libneo_pen.so ------------------------------------------------------------------------------------ The subsections above catalogue ``libonyx_cfa.so`` / ``libonyx_neo_dither.so`` / ``libonyx_pen_touch_reader.so`` by exported JNI class and general shape. This section goes one level deeper — actual disassembly of the exported functions' bodies, not just their names — for those three plus the two libraries that were previously only name/size-catalogued: the 4 KB ``libonyx_algorithm.so`` and the 3.8 MB handwriting engine ``libneo_pen.so``. Full instruction transcripts, file offsets and the literal constant bytes for every claim below are in ``artifacts/re_static/item_onyx_native_deep.txt``; only the findings are summarised here. **Confirmed for all five: none of them issues any ``ioctl`` at all** (``artifacts/re_static/ioctlscan.py`` reports 0 callsites in four of the five, and exactly the nine evdev ``EVIOCG*`` calls already known in ``libonyx_pen_touch_reader.so`` — never ``/dev/ebc`` or ``/dev/onyx/*``), so the "outside the ``/dev/ebc`` path" judgement holds for every one of these libraries as a *file*. One of them has a subtler qualification, noted below. ``libonyx_algorithm.so`` (4104 bytes total) has exactly one export, ``Java_com_onyx_android_sdk_scribble_utils_Algorithm_distance``, and its entire 128-byte body is a textbook **point-to-line-segment distance**: project the third point onto the line through the first two, clamp the projection parameter to ``[0, 1]``, and return the Euclidean distance to the clamped point. That is the whole library — a single geometry primitive for the scribble/stroke code (most likely stroke simplification or hit-testing, though unconfirmed which one) — fully covered, no device access of any kind. ``libonyx_cfa.so``'s ``ColorUtils`` channel extractors confirm the pixel format is plain ``0xAARRGGBB`` (``red()`` = ``>>0x10``, ``green()`` = ``>>8``, ``blue()`` = identity, ``alpha()`` = ``>>0x18``), and ``ColorUtils::gray()`` disassembles to a literal ``0.587*G + 0.299*R + 0.114*B`` — the exact ITU-R BT.601 luma weights, read directly out of the ``.so`` as IEEE-754 doubles at file offsets ``0x22a0``/``0x22a8``/``0x22b0``. ``ColorUtils::white(r,g,b)`` is the same formula's fixed-point twin: ``(299r + 587g + 114b) / 1000`` via a reciprocal-multiplication divide (magic constant ``0x10624DD3``). The sole JNI export, ``QRCodeUtil.toRgbwBitmap``, uses exactly this: for every source ARGB pixel it writes a **2x2 block of four destination pixels** — one red-replicated (``argb(a,r,r,r)``), one green-replicated, one blue-replicated, and one carrying the BT.601 luma replicated across all three channels (the "white"/K tap) — i.e. despite the QR-code-sounding class name, this is the generic RGB→RGBW colour-filter-array packer for the Kaleido-class colour panel, with the per-tap formula and luma weights now pinned by bytes rather than inferred from the function name. ``libonyx_neo_dither.so``'s grayscale ``ImageFilter::doFilterInPlace`` is a genuine **error-diffusion dither**, fully disassembled: it computes a per-pixel weighted grey level ``(11*R + 16*G + 5*B) / 32`` — a *different*, independently-authored weighting from the CFA library's BT.601 constants — quantizes it to a configurable ``quant_bits`` depth (visible in its own log string, ``"quant_bits: %d, quant_step: %d"``), and forward-diffuses half the quantization residual into a per-column error-line buffer for the next row. This is a simplified 2-tap forward/down variant of the Floyd–Steinberg family, not the textbook 7/16-3/16-5/16-1/16 four-neighbour kernel — no such coefficients appear anywhere in the disassembly. ``ImageColorFilter`` (the ``ditherColor`` export) runs the identical quantize/diffuse math but allocates **three independent error-line buffers**, one per R/G/B channel, confirming colour dithering is per-channel rather than luma-derived. ``libonyx_pen_touch_reader.so``'s ``PenManager::normalizePressure()`` turns out to implement a real, hand-tuned **piecewise pressure-response curve**, not a raw pass-through: it low-pass-filters consecutive raw samples (``smoothed = (0.5*prev + raw) / 1.5``), normalises against the device's ``EVIOCGABS(ABS_PRESSURE).maximum``, and then applies a 4x linear gain for the lightest 15% of the range (boosting faint initial pen contact) that hands off — continuously, verified by evaluating both branches at the ``0.15`` threshold and getting the same ``0.60`` — to a compressive power curve ``pow(frac-0.135, 0.2)+0.17`` for the rest, with all three constants (``-0.135``, ``0.2``, ``0.17``) read as literal doubles out of the binary. (There is also an unconditioned fallback branch, gated on an unrelated per-instance field, that returns the raw sample unmodified — its trigger condition was not traced further.) Button handling maps the evdev codes precisely: ``BTN_TOOL_RUBBER`` (physical eraser tip), ``BTN_TOUCH`` and ``BTN_STYLUS`` (side button) combine in ``isShortcutErasing()`` — named field by field in that function's own log string — to support a chorded "hold-side-button-to-erase" shortcut. Region clipping (``inLimitRegion``/``inValidRegion``/``inExcludeRegion``) expands each sample point into a tiny stroke-width-sized box and tests it against app-supplied allow/deny rectangle arrays (``setLimitRegion``/ ``setExcludeRegion``) — this is what the "coordinate transform" question resolves to in this library: **there is no calibration/rotation matrix anywhere in it**, only region clipping. Likewise ``moveTo()``/``quadTo()`` turn out to be two-line wrappers that just tag an event-type flag for ``report()`` — no Bézier/quadratic math happens in this library despite the Android-``Path``-style naming; if curve smoothing happens anywhere, it is either in the Java SDK layer or in ``libneo_pen.so`` (next). ``libneo_pen.so`` (3.8 MB) exports only **seven of its own symbols** — the JNI entry points ``nativeCreatePen``/``nativeDestroyPen``/``nativeOnPenDown``/ ``nativeOnPenMove``/``nativeOnPenUp``/``nativeSetBitmapColor``/ ``nativeSetLogLevel`` of class ``com.onyx.android.sdk.pen.NeoPenNative`` — plus roughly 971 more symbols (978 total defined dynamic symbols) that turn out to be the public API of **statically-linked spdlog and fmt** (783 and 45 symbols respectively) and the same small ``JNIUtils``/``JNIBitmap``/``ColorUtils``/``DeviceUtils`` glue shared with the other onyx libs. The actual handwriting engine is compiled with hidden visibility, so its own classes carry no dynamic symbols — but the compiler's C++ RTTI *typeinfo-name* strings for those classes are still present as literal ASCII in the binary (``strings -a`` finds them even though ``nm -D`` cannot), and they lay out the real architecture precisely: ``neo::pen::NeoStrokeRenderer``, a templated ``StrokeProcessor`` chaining ``TouchEvent → StrokePointBlob → StrokeSpline``, a ``SplineProducer``/``SplineInterpolator`` family specialised into ``SplineDistanceInterpolator`` (arc-length parameterised) and ``SplineCurvatureInterpolator`` (curvature-adaptive recursive subdivision — its mangled lambda names literally include ``subdivideRecursive``/``subdivideRecursiveXY``/``getTForCubic``, the same family of technique as AGG-style curve flattening), and a ``SmoothFilter`` (paired with a ``smoothLevel`` string). Together with the JNI method names and log strings ``drawStroke``/``"invalid empty stroke renderer!"``/``"alloc render points/result data failed!"``, this confirms **libneo_pen.so is the low-latency ink stroke-rendering engine**: it ingests raw pen samples, curve-fits/smooths them via curvature-adaptive spline subdivision, and rasterises the result directly into a locked Android ``Bitmap`` (``AndroidBitmap_lockPixels`` is imported; no ``ioctl``/``open``/``mmap`` is). Disassembly of the fully-transcribed entry points bears this out structurally — ``nativeOnPenDown``/``nativeOnPenUp`` marshal a Java touch-point object through a JNI vtable call into a native record carrying position, at least two extra float channels, and a 64-bit timestamp, then dispatch to internal handlers; ``nativeSetBitmapColor`` is a NEON-vectorised pass that recolours every opaque-white pixel of a bitmap to translucent red (``0x80FF0000``, literal constant) and clears every other pixel to transparent — a mark/highlight-region helper, not ink rendering itself. This library is also where the "outside the boot path" scope judgement needs a real qualification rather than a blanket reconfirmation. SurfaceFlinger's own strings (already recorded above: ``neopen_createPen``, ``createPen``, ``"invalid empty stroke renderer!"``) are **byte-for-byte identical** to strings found inside ``libneo_pen.so`` — but ``surfaceflinger``'s ``NEEDED`` list (56 entries, checked in full) does **not** include ``libneo_pen.so``. The two binaries therefore do not share this code at the dynamic-linking level; the more likely explanation (unconfirmed — no build system or source tree was available to check directly) is that the same ``neo::pen`` C++ source is compiled into both independently — once as the standalone, JNI-callable, device-free ``libneo_pen.so`` documented here, and once statically embedded directly inside SurfaceFlinger as its ``neopen_createPen``/``createPen`` direct compositor-side fast-draw path, which *does* go on to call ``ioctl(/dev/ebc, SET_EBC_SEND_UPDATE, ...)`` for the actual panel refresh. So: the ``libneo_pen.so`` *file* never touches a device — confirmed — but the stroke-rendering *algorithm* it implements has a sibling copy that sits squarely in the low-latency ink display path. That is a more precise finding than either "it's part of the EPD path" or "it's completely unrelated to the EPD path". The userspace to kernel EPD update path --------------------------------------- The panel-update ioctls on ``/dev/ebc`` are **not** issued by any ``libonyx_*`` library. Grepping the images, ``/dev/ebc`` appears in ``vendor`` only in ``init.onyx.rc`` (``chmod 0666``) and the sepolicy ``file_contexts`` (label ``ebc_device``), and in ``system`` only in ``bin/charger`` (the minui charge screen) and ``bin/surfaceflinger``. The real client is the **Onyx-patched SurfaceFlinger**, and the app-facing API is compiled into a **patched ``libgui.so``**: Onyx extended the core AOSP graphics classes with an e-ink update list that rides the normal buffer-queue/Parcel path. The exported symbols (defined in ``libgui.so``) are: .. list-table:: :header-rows: 1 :widths: 46 54 * - symbol (demangled) - role * - ``android::EpdcWrapper`` (``addEpdc(int,int,int,int,int)``, ``addEpdcList``, ``mergeByMode``, ``getEpdcList``, ``setBatch``, ``flatten`` / ``unflatten`` / ``flattenEpdcList``) - a wrapper around ``std::vector``; each element is **5×int32 = 20 bytes** (an update rectangle plus a ``waveform_mode`` — hence ``mergeByMode``) * - ``android::Surface::transferEpdc(vector&)`` / ``Surface::clearEpdcList`` - the app attaches its e-ink update regions to a ``Surface`` * - ``android::SurfaceComposerClient::Transaction::transferEpdc(...)`` - the same, through a ``Transaction`` * - ``android::BufferData::writeEpdc(Parcel*)`` / ``readEpdc(Parcel*)`` - the list is flattened into the buffer's Parcel and unflattened in SurfaceFlinger So the flow is: an app calls ``Surface::transferEpdc`` with a list of ``hwc_epdc_llist`` regions → ``libgui`` flattens it into the frame's Parcel (``writeEpdc``) → SurfaceFlinger unflattens (``readEpdc``), merges same-mode regions (``EpdcWrapper::mergeByMode``) and calls its ``onyx_epdc_screenRefresh``, which issues ``ioctl(/dev/ebc, SET_EBC_SEND_UPDATE, …)``. That request number was pinned by cross-referencing SurfaceFlinger's own error string (``onyx_epdc_screenRefresh(): error! SET_EBC_SEND_UPDATE retval = 0x%x``): the ``ioctl`` immediately preceding it loads ``w1 = 0x700c``, so: .. code-block:: text SET_EBC_SEND_UPDATE = ioctl request 0x700c The other EBC ioctls SurfaceFlinger issues sit in the same ``0x70xx`` family (e.g. ``0x701d``), i.e. the ``/dev/ebc`` ABI uses plain ``0x7000``-based magic numbers (the rockchip-style "ebc" numbering the Onyx software EPDC reuses); their individual names are the ``GET_EBC_*`` / ``SET_EBC_*`` set recovered kernel-side (:doc:`/display/ebc-interface`). SurfaceFlinger also carries a direct pen "stroke renderer" fast-draw path (``neopen_createPen`` / ``createPen``, ``### disable refresh``, ``/sys/onyx_misc/cytp_lo_filter``) for low-latency ink. Configuration, policy and the /onyxconfig store =============================================== - **Init glue** — ``init.onyx.sh``, ``init.onyx.misc.sh``, ``init.onyxconfig.sh`` (system) and ``init.onyx.rc`` (vendor, :doc:`/display/stack`). These set up device knobs, gate ``download_mode`` off on user builds unless a production-test tag file exists, and start the config store. - **The ``/onyxconfig`` partition** — a dedicated ext4 partition (:doc:`/partition-map`, raw image ``lun0/onyxconfig.bin``) holding an ``mmkv`` directory: Onyx's persistent settings/config store, separate from ``/data`` so it survives factory reset. On this unit it holds exactly four top-level entries: the panel VCOM value (``com.android.vcom`` = ``[REDACTED — unique per physical unit]``, :doc:`/display/tcon`) and two 4-byte factory-done markers, ``com.onyx.android.production.test`` and ``com.onyx.android.screenmanager``, both literally the ASCII string ``DONE`` — plus ``test_result_info`` below. - **``/onyxconfig/test_result_info``** — a 1 KB JSON record written by the factory production line itself, carried through to the shipped device rather than wiped: model ``NoteAir5C``, the unit's WiFi MAC (``[REDACTED — unique per physical unit]``), and three timestamped test-station passes — **SMT** (``[REDACTED — narrows production timeframe]``, board-level: charge, WiFi, BT, dual-speaker, dual-mic record, OTG, keys, ``SENSOR_TEST_TYPE_ACCELEROMETER``/``_GYROSCOPE``, fingerprint, "note" i.e. pen), **ASSEMBLE** (``[REDACTED — narrows production timeframe]``, an ``AGING_TEST`` burn-in pass), and **QA** (``[REDACTED — narrows production timeframe]``, final line: adds ``SCREEN_FRONTLIGHT_TEST``, ``SCRIBBLE_TEST``, ``VCOM_SETTING``, ``NATURAL_LIGHT_TEST``, ``ANALOG_EARPHONE_TEST``/``DIGITAL_EARPHONE_TEST``, ``SDCARD_TEST``) — all results ``1`` (pass). This independently corroborates, from the factory's own checklist rather than static code RE, the full sensor/peripheral inventory already recovered in :doc:`/display/wacom-pen` and :doc:`/sensors/misc` (Wacom EMR pen, fingerprint, IMU) and the front-light/VCOM path in :doc:`/display/tcon`. The ``fingerprint`` field in this JSON is a *build fingerprint* string (``ONYX/TabBoox/TabBoox:13/TKQ1.230615.001/…``), not a biometric fingerprint — most plausibly a stale/default value baked into the factory-test tool itself (unconfirmed; an Android-13 base string on a shipping Android-15 build, see the version split noted at the top of this page); either way, it is not a reliable version record. - **Permissions / policy** — ``privapp-permissions-onyx.xml`` (priv-app grants), ``onyx-default-permissions.xml`` (runtime-permission pre-grants), ``sysconfig/onyx_whitelist.xml`` (power/hibernation whitelist), and ``onyx_unavailable_features.xml`` (the "no NFC/GPS/camera/telephony" declaration, :doc:`/display/stack`, :doc:`/soc/absent-hardware`). - **Onyx properties** — ``vendor.onyx.tablet=true``, ``vendor.onyx.htcon=true``, ``sys.onyx.idledelay=2500``, ``ro.build.version.onyxincremental=225`` / ``ro.vendor.build.onyxid=…`` (the Onyx build-tracking IDs). Preinstalled Onyx apps ====================== The Boox application suite itself — the note app, reader, app store, cloud sync, and the rest — is catalogued in :doc:`app-inventory`; an ``onyx``-only search for them misses their naming convention, but does surface ``OnyxLatinIME`` (keyboard) and ``OnyxOtaService`` (the OTA service, with ``libota_jni.so``, driving the A/B updater in :doc:`platform-boot`). This page covers the platform *scaffolding* those apps run on (the SDK libraries, the EPD/pen native path, the config store, the permissions); the apps' own compiled Java is not decompiled. Scope boundary ============== The compiled Java of the TabBoox framework, the two Onyx system services, and the Boox app suite itself (odex/vdex, all preinstalled per :doc:`app-inventory`) is not decompiled here; nor is the ``product`` partition's Google/AOSP payload, covered separately in :doc:`app-inventory`. This page is the map of the Onyx *platform scaffolding*, not a reverse of the Boox applications. Provenance ========== :Source: ``artifacts/notes/props/*.build.prop`` (the split fingerprints, Onyx properties), ``system_a.img`` (``lib64/libonyx_*.so``, ``lib64/libmmkv-onyx.so``, ``bin/init.onyx*.sh``, ``etc/permissions``/``sysconfig`` Onyx XML, ``app/OnyxLatinIME``, ``priv-app/OnyxOtaService``, ``framework/*.jar``), partition listings for the app census. The *"Inside the native Onyx SDK"* section additionally uses the extracted ``.so`` binaries themselves — ``lib64/libonyx_epd_listener.so``, ``libonyx_pen_touch_reader.so``, ``libonyx_cfa.so``, ``libonyx_neo_dither.so``, ``libgui.so`` and ``bin/surfaceflinger`` — with the full symbol dumps, ioctl/dev/property strings and disassembly recorded in ``artifacts/re_static/item4_onyx_native.txt``. The *"Deeper static RE"* subsection adds a second pass over ``libonyx_algorithm.so``, ``libonyx_cfa.so``, ``libonyx_neo_dither.so``, ``libonyx_pen_touch_reader.so`` and ``libneo_pen.so`` — full instruction transcripts, extracted floating-point/fixed-point constants, and the ``ioctlscan.py`` per-library ioctl-callsite counts are in ``artifacts/re_static/item_onyx_native_deep.txt``. :Method: ext4 images listed/extracted read-only with ``7z``; properties and init scripts read as text. Dynamic symbols read with ``objdump -T`` / ``nm -D`` and demangled with ``c++filt``; exported functions disassembled with capstone (helper scripts ``artifacts/re_static/sodis.py`` / ``ioctlscan.py``); ioctl request numbers decoded from the ``movz``/``movk`` immediates and pinned to names by error-string cross-reference. Compiled Java not decompiled. No code executed. :Cross-refs: :doc:`/display/stack` (EPD/pen native libraries), :doc:`/display/tcon` (kernel-side ``/dev/ebc`` ioctl set), :doc:`security` (keystore2-on-keymaster4.1, explained by the 13-on-11 split), :doc:`platform-boot` (A/B + OTA), :doc:`app-inventory` (GMS/AOSP apps), :doc:`/partition-map` (the ``onyxconfig`` partition), :doc:`/soc/absent-hardware` (unavailable features).