Sticker delivery, incremental import, a webp to GIF pipeline, and keyboard UX - #91
Open
patildhruv wants to merge 1 commit into
Open
Sticker delivery, incremental import, a webp to GIF pipeline, and keyboard UX#91patildhruv wants to merge 1 commit into
patildhruv wants to merge 1 commit into
Conversation
…board UX Work from a personal fork, offered upstream. Every behavioural claim below came from adb logcat, a screenshot, or a measurement on a physical device rather than from reasoning. See docs/ for the details and the numbers. Sending - WhatsApp advertises image/webp.wasticker and never plain image/webp, so webp stickers failed the supported-mimetype check and fell through to the png fallback, arriving as flattened photos with any animation lost. Vendor mimetypes are now matched by image/webp. prefix rather than hardcoding wasticker, so another app's equivalent works too. - A wildcard counts as accepting the original, so an app advertising both image/* and image/gif is no longer handed a downscaled GIF. - The png fallback moved off the thread that draws the keyboard, and a decode failure no longer returns a phantom path that commitContent reports as success. A zero-length file no longer satisfies the "already converted" check. webp to GIF pipeline (new) - For apps that advertise image/gif and no webp mimetype at all. WebpAnimation demuxes an animated webp and composites frames; verified byte-exact against the reference decoder. GifEncoder writes GIF89a with median cut and Floyd-Steinberg dithering. ImageResampler does Lanczos-3, because createScaledBitmap is bilinear only and loses against the reference. - Output is within 0.13 dB and 6% of tools/gif-variants.py, the desktop reference. A counting sort replaced an insertion sort in median cut for byte-identical output about 10x faster: regenerating 53 stickers went from 247s to 32s on device. - GifEncoder, ImageResampler and the reader are free of Android imports, so they are unit-testable on the JVM. Tests included. Import - Incremental and non-destructive, driven by a manifest of size and mtime. A no-change reload went from 32s to about 0.3s; adding one pack copies that pack rather than rebuilding the library and every generated GIF. - The SAF tree is listed with one cursor per directory instead of thousands of Binder round trips, which also yields the metadata the manifest needs. - Pruning refuses to act on a listing it cannot corroborate, and never infers staleness from timestamps, because copying rewrites the destination's mtime. Keyboard - Settings and library changes apply to a running keyboard; both "reload the keyboard" strings are gone, in every locale. - All packs in one continuous list with a heading per pack, and a pack bar that follows the scroll. Favourites, and deleting a sticker. Drag-resizable keyboard height, stored per orientation, replacing a hard-coded 800 pixels. - Material You on Android 12+, with selection drawn as a container rather than a colour filter over the sticker artwork. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is a large PR and I don't expect it merged as-is. I'm opening it because the work is verified on a physical device (Galaxy S25, One UI) and may be useful to you whole or in pieces. Happy to split it into separate reviewable PRs, or to drop anything you don't want.
Sending
WhatsApp advertises
image/webp.wastickerand never plainimage/webp, so a webp sticker failed the supported-mimetype check and fell through to the png fallback, arriving as a flattened photo with any animation lost. Vendor mimetypes are now matched on theimage/webp.prefix rather than hardcodingwasticker, so another app's equivalent works too.Also: a wildcard now counts as accepting the original, so an app advertising both
image/*andimage/gifis no longer handed a downscaled GIF. The png fallback moved off the thread that draws the keyboard, and a decode failure no longer returns a phantom path thatcommitContentreports as success.webp to GIF pipeline (new)
For apps that advertise
image/gifand no webp mimetype at all, where a GIF is the only way to keep an animated sticker moving. Hand-written demuxer verified byte-exact against the reference decoder, GIF89a encoder with median cut and Floyd-Steinberg dithering, and a Lanczos-3 resampler (createScaledBitmapis bilinear only and loses against the reference).Output is within 0.13 dB and 6% of
tools/gif-variants.py, the desktop reference. A counting sort replaced an insertion sort in median cut: byte-identical output about 10x faster, taking a 53-sticker regeneration from 247s to 32s on device. The encoder, resampler and reader carry no Android imports so they are unit-testable on the JVM; tests are included.Import
Incremental and non-destructive, driven by a manifest of size and mtime. A no-change reload went from 32s to about 0.3s, and adding one pack copies that pack rather than rebuilding the library and every generated GIF. The SAF tree is listed with one cursor per directory instead of thousands of Binder round trips, which also yields the metadata the manifest needs. Pruning refuses to act on a listing it cannot corroborate, and never infers staleness from timestamps, because copying rewrites the destination's mtime.
Keyboard
Settings and library changes apply to a running keyboard, so both "reload the keyboard" strings are gone in every locale. All packs in one continuous list with a heading per pack and a pack bar that follows the scroll, favourites, sticker deletion, Material You on Android 12+, and a drag-resizable keyboard height stored per orientation (replacing a hard-coded 800 pixels).
Notes
Docs for each area are under
docs/. Every behavioural claim came fromadb logcat, a screenshot, or a measurement rather than from reasoning.Testing: the branch is
patildhruv:feat/fork-workif you want to try it on a device --./gradlew assembleDebugand sideload the result. I'd rather point you at the source than hand over an APK signed with a debug key. Happy to answer anything about the changes here.The commit carries a
Co-Authored-By: Claudetrailer. The work was done with AI assistance and I would rather that be visible than not.