Why your CalDAV events show sixty shades of the same blue

Two colour reports landed on Calendula in the same week, and both are still open. One is a bug: the colour picker on a CalDAV calendar shows a full screen of colours, many of them near-duplicates (#22). The other is a request: make event titles readable on dark backgrounds so busy weeks don’t turn into a smear (#21). They look unrelated, but both come from how event colour flows through Android’s calendar provider, which is why I’m writing up the diagnosis before I write the fix.

Update: both are fixed now. The tidier picker shipped in Calendula 2.14.1 and the readable titles in 2.15.0. The notes below are the diagnosis as I wrote it, with the technical details corrected since.

Palette vs. free-for-all

Android’s CalendarContract has two ways to colour an event. It can carry a raw EVENT_COLOR, any value at all, or an EVENT_COLOR_KEY that points into a per-account palette in CalendarContract.Colors, where each swatch has a COLOR_KEY and a colour. Only the account’s sync adapter can write that palette, so what a picker has to offer depends on who syncs the account. Google’s adapter publishes a short, hand-picked set, about a dozen. A purely local calendar usually has none.

CalDAV accounts go through a different sync adapter, which for most people on Android means DAVx⁵. The iCalendar COLOR property on an event is defined as a CSS3 colour name, so with event colours switched on in the account settings, DAVx⁵ fills the account’s palette with every CSS3 named colour, 147 of them, and stores an event’s COLOR as the key of that name. (The X-APPLE-CALENDAR-COLOR you sometimes see on a CalDAV server is a colour for the whole calendar, not for single events, and has nothing to do with this palette.) So a CalDAV account does have an indexed palette. It’s just one that was written as a naming scheme for the web, not as a menu: alphabetical, with aqua and cyan being the same colour, fuchsia and magenta too, each grey spelled both ways, and a run of blues that differ by less than you can see. A picker that shows the account’s palette as it is puts all 147 on screen. That’s the “too many colours” bug. The fix is to show a bounded view of the real palette instead of inventing one of my own: keep the swatches that look distinct, order them, and fold the near-duplicates away, so whatever you pick is still a real key the sync adapter can carry back to the server.

The contrast problem is the same problem

Once an event can be one of 147 colours, or a raw value from anywhere, you can no longer assume the text on top is readable. A pale event with dark text is fine; a deep navy block with the same dark text is a smear. At the time, Calendula drew event titles in a fixed near-black. Google Calendar flips to white text on dark fills, and #21 asked for the same.

Averaging the channels doesn’t tell you whether a colour is dark, because the eye isn’t equally sensitive to red, green and blue. The WCAG relative luminance formula weights them the way perception does (green counts far more than blue), linearises each channel, and yields a single brightness figure. Pick a threshold, and below it the title renders white (or off-white), above it near-black. It’s a few lines of maths applied to the same event colour the picker just handed you. The picker needs to reduce the available colours to a readable set. The title needs to switch between light and dark text based on the chosen colour.

Standard first, taste second

Calendula leaves the event’s COLOR alone so the sync adapter can carry it to other clients. The picker now shows a bounded set of the account’s real colour keys, and the title switches to light text on dark fills. Neither fix stores a private colour that other calendar apps could not read.