blog

Brightness Lock - Dimming a Pixel for China's Transit QR Gates

We were traveling through China, a group of us (Kai Barzen among them), all on Google Pixel phones. To ride the metro or a bus you open a transit QR code in Alipay or WeChat and hold it to the scanner on the fare gate. In some cities this just worked. In others - Beijing and Shanghai among them - the gate would flat-out refuse, over and over, while the queue stacked up behind us and we did the slightly panicked “tap it again, hold it flatter, try the other app” dance that everyone behind you has clearly seen before.

It took an embarrassingly long time to find the pattern, because it was none of the obvious things. Not the city, not the app, not the account, not the network. It was screen brightness.

The cause

When you open the transit QR, Alipay and WeChat slam the screen to full brightness to “help” the scanner. The intention is reasonable - a brighter code is easier to read in daylight. But on a Pixel’s OLED panel at 100% the code blows out: the whites bloom, the contrast the reader needs collapses, and the fare gate can’t lock onto the pattern. Phones sold in China are apparently tuned so this doesn’t happen. Ours weren’t, and there is no setting for it.

The maddening part is that you can’t fix it by hand. Turn system brightness down and the moment you reopen the QR the app cranks it straight back to max. Those apps set their own window brightness, which takes precedence over the system slider, so you are not allowed to win that fight from the settings screen. You can hold the phone at an angle and pray, which is roughly what we did for a week.

Why you can’t just turn it down

An Android app can set screenBrightness on its own window, and while that window is in front it overrides whatever the system brightness is. There is no public API to say “ignore this app’s brightness preference”. So if the QR app insists on 1.0, the only thing that still sits above it on the screen is another window drawn on top.

That’s the whole trick: you can’t lower the app’s brightness, but you can draw a translucent black overlay over it. The panel is still blasting photons at full tilt; you’re just absorbing some of them before they leave the glass. To the fare-gate reader the QR is dimmer and the contrast comes back, and it scans.

The fix

Brightness Lock does exactly that, with one important constraint: it only dims while a watchlisted app is in the foreground. You don’t want a phone that’s dark all day; you want it dark for the four seconds you’re at the turnstile and normal everywhere else.

flowchart LR
  FG[Foreground app changes] --> Q{On the watchlist?}
  Q -- "Alipay / WeChat" --> On[Attach dim overlay]
  Q -- anything else --> Off[Remove overlay]
  On --> Gate[QR scans, gate opens]

The pieces:

  • Usage access (PACKAGE_USAGE_STATS) lets a small foreground service see which app is currently on top.
  • When that app is on the watchlist, it draws a dim overlay via the display over other apps permission (SYSTEM_ALERT_WINDOW), and tears it down the instant you leave the app.
  • The whole thing runs as a foreground service so Android keeps the watcher alive, and it re-arms itself after a reboot.

Crucially, nothing ever touches the actual system brightness setting. The effect is purely an overlay scoped to the apps you pick, so your everyday brightness is never changed and there’s nothing to undo when you leave the watched app.

The default watchlist is Alipay, AlipayHK, and WeChat, and you can add or remove apps with a picker. Dim strength defaults to 70%, which cleared the Pixel gates in practice, and there’s a live slider so you can preview the dim level on the couch instead of discovering it’s wrong while a Beijing turnstile beeps at you.

Getting it

There’s a signed APK on the GitHub releases page - grab the latest and sideload it. A Play Store listing might happen, or might not; this is a sharp tool for a narrow problem and I’m in no rush.

If you’d rather build it yourself, you need JDK 17 and the Android SDK (compileSdk 34):

./gradlew :app:assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apk

It’s open source. Code, the full README, and the rationale for each permission are on GitHub.