Skip to content

Repository files navigation

LinkLab

A URL compressor and QR-code generator that runs entirely on the device. It squeezes a link into a short payload with no server and no database — expanding it back is arithmetic, not a lookup — and it draws the QR code that carries it in the smallest symbol the standard allows.

Download the APK

minSdk 26 targetSdk 34 Kotlin 2.0.21 Jetpack Compose, Material 3 One runtime permission No network access


What it does

Paste or share a link into LinkLab and it gives you back two things: a short link you can copy, and a QR code you can show or save. Scan or import either one — on this device or someone else's — and it expands back to the original.

The compression is the interesting part, and it is not a shortener. A shortener hands your URL to a server, which stores it under a random key and hands the key back; the short link only works for as long as that server keeps the row. LinkLab has no server and stores nothing. The payload is the link, run through a compressor: a bijective base-N encoding over four Huffman dictionaries of the web's most common domains, TLDs and path fragments. Everything needed to reverse it travels inside the payload, so a link made on an aeroplane resolves in a browser that has never heard of this app.

Two payload alphabets come out of every compression, because the best string and the best QR code are not the same string:

  • The link puts the payload in the URL fragment — https://ha.mr#<payload> — using the full character set a URL permits, for the shortest text. The fragment never reaches the resolver's server, so even if you use the hosted resolver, it never learns which link you shared.
  • The QR code uses only the 45 symbols of QR's alphanumeric mode and shouts the whole thing — HTTP://HA.MR/<PAYLOAD> — because a single lower-case letter would drop the symbol into byte mode, which spends 16 bits per character where alphanumeric spends 11 for two. Same payload, deliberately different encoding, so the printed code is as small as it can be.

Features

  • Compress — a link in, a short link and a QR code out, with the size saved shown as a percentage. Copy the link, share it, or save the QR code.
  • Scan — a live camera scanner, and decode from an image for a QR code that is already in your gallery or came in over chat. Both feed the same expander.
  • Save & share the QR — as a PNG. PNG, not JPEG, because a QR code is nothing but high-frequency detail and JPEG throws exactly that away. Saving uses the system document picker, so the app needs no storage permission and you choose where it lands.
  • History — every link you compressed or expanded, newest first, ordered by arrival rather than by the clock so a device correcting its time over NTP cannot shuffle the list. Delete a single entry, with undo; clear the lot behind a confirmation; or turn recording off in Settings.
  • Settings — the resolver domain (so a self-hosted resolver works), the minimum QR error-correction level, the theme, and Material You dynamic colour where the platform offers it.
  • Share target — LinkLab registers as a share target for plain text, so a link shared from a browser or any other app arrives ready to compress. It also opens ha.mr links, so tapping a compressed link expands it here.

The compression engine

The format is a port of ha.mr by p2r3, MIT-licensed. The port is not a reimplementation-in-spirit — it is byte-for-byte identical to the reference, and that identity is the whole point: a payload made by this app expands on the reference's own web page, and vice versa. Two things pin it there:

  • The four Huffman dictionaries shipped with the app are the exact tables the reference generates from its own source, verified byte-for-byte.
  • The codec is checked against 2,760 golden vectors generated by running the reference implementation itself over a mix of curated and deterministically-fuzzed URLs. Every vector asserts the payload matches the reference exactly, in both alphabets, and that decompression reproduces the original. A single byte of divergence fails the build.

The one genuinely hard part is URL parsing. The reference runs on the browser's new URL(), and java.net.URI does not agree with it — so the engine carries a hand-written WHATWG-subset parser that replicates the behaviours the vectors exercise: default-port dropping, ./.. path collapsing, IPv4 and IPv6 canonicalisation, IDN-to-punycode, and the exact percent-encode sets for the path, query and fragment.

The engine is a plain Kotlin/JVM module with no Android dependencies — enforced by applying only the kotlin-jvm plugin and declaring no dependencies beyond its test libraries, so reaching for a Context in there is a compile error rather than a code-review note. It runs its whole test suite on the JVM, no device required.

Privacy

  • No INTERNET permission. The app is structurally incapable of reaching the network — compression and expansion are arithmetic that happens on the device.
  • One runtime permission: CAMERA, and only for the live scanner. Decoding from an image needs no permission at all, and neither does saving a QR code — that goes through the system document picker.
  • No account, no analytics, no tracking. History lives in a local database you can clear or switch off. Nothing leaves the device unless you share it yourself.

Install

Android 8.0 (API 26) or newer, any ABI.

⬇ Download the APK

Android will ask you to allow installs from your browser or file manager the first time, because the APK is signed with a developer key rather than distributed through a store.

The attached build is the debug variant, signed with Android's standard debug key (the CN=Android Debug certificate every SDK install shares, not a key unique to this project). It is the one that installs, because the release variant is built unsigned — see Build — and an unsigned APK is rejected by the package installer. Everything works identically; it is larger and marked debuggable, so build and sign a release variant yourself before deploying this to anyone else.

Build

git clone https://github.com/Dreamucxe/LinkLab.git
cd LinkLab
echo "sdk.dir=$ANDROID_HOME" > local.properties
chmod +x gradlew
./gradlew assembleRelease

The APK lands in app/build/outputs/apk/release/. Without a keystore.properties it is unsigned — add one with storeFile, storePassword, keyAlias and keyPassword to have Gradle sign it.

Run the engine's golden-vector suite — the gate the whole port rests on — with:

./gradlew :core:compression:test

Architecture

Kotlin, Jetpack Compose, Material 3, MVVM, Hilt, Room, coroutines and StateFlow throughout, split so the engine can be tested without a device and the QR arithmetic without a screen.

core/compression   The ha.mr port — codec, dictionaries, WHATWG URL parser. Pure JVM,
                   no dependencies, 2,760 golden vectors.
core/qr            QR encoding and decoding over ZXing. Chooses the smallest version, then
                   the highest error-correction level that still fits it. Pure JVM.
app/data           Room history, DataStore settings, QR rendering, image decode, PNG export
app/domain         Compress and resolve use cases
app/ui             Four screens — Compress, Scan, History, Settings — the theme and nav

core/qr earns its own module by doing one thing ZXing will not: after encoding, it raises the error-correction level as high as the chosen symbol version has room for, because that redundancy lands where padding bits would otherwise go. Free robustness, and unit-testable without a device.

The instrumented suite covers exactly the seams the JVM tests cannot: that the Huffman dictionary — a classpath resource in a pure-JVM library — survives packaging into the APK and loads through Android's class loader; that the Room trimTo correlated subquery runs on real SQLite; and that a rendered Bitmap decodes back through getPixels, where a transposed row looks right and does not scan.

Credits

The compression format, its dictionaries and the arithmetic that reads them are the work of p2r3, published as ha.mr under the MIT licence. LinkLab is an Android port of that work; see NOTICE for the upstream copyright and permission notice, which its licence requires be carried along.

QR encoding and decoding use ZXing.

Licence

MIT — see LICENSE. The bundled ha.mr work is also MIT; its notice is in NOTICE.

About

On-device URL compressor and QR generator for Android. No server, no database — expanding a link is arithmetic, not a lookup.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages