Skip to content

Nook GL4 Plus: make warmth survive B&N's colour temperature service - #620

Merged
hugleo merged 2 commits into
koreader:masterfrom
backcountrymountains:nook-gl4plus-ctm-warmth
Sep 5, 2026
Merged

hugleo merged 2 commits into
koreader:masterfrom
backcountrymountains:nook-gl4plus-ctm-warmth

Conversation

@backcountrymountains

@backcountrymountains backcountrymountains commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

The symptom

On a Nook Glowlight 4 Plus (BNRV1300), frontlight warmth resets to cold on every unlock. Moving the warmth slider works until the screen goes off; on the next wake the light is cold again, and no amount of re-adjusting makes it stick. The device I hit this on lost its warmth after an unclean shutdown and never recovered it.

Cause

GlowLightService — the service this controller already uses for warmth — also hosts B&N's Color Temperature Management (CTM), which re-applies a warmth value on every SCREEN_ON, after the framework has restored screen_brightness_color. So it wins over anything we write.

CTM's mode is persisted only in the service's own shared prefs (ctm_preference.xml, key ctm_mode), and getCTMMode() returns -1 (CTM_MODE_DISABLE) when that key is missing. An unclean shutdown loses the file; the service then recreates it containing only color_temperature — with no ctm_mode key at all. In mode -1, setupCTM() forces COLD_LIGHT (0 on Emperor), and action_set_color_temperature does not persist either, because saveColorTemperature() routes to a ScheduleMode whose save is a no-op with null sunrise/sunset.

Reproduced end to end — both by killing the service with the prefs file removed, and by a real reboot:

CTMService: setupCTM:-1
LightsService: kk-1-brightness(color):0

Removing just that key reproduces the symptom exactly, and nothing in the system ever restores it.

The fix

Assert CTM_MODE_MANUAL once per process, immediately before the first warmth write. In manual mode the same warmth intent also writes manual_color_temperature, so the value we set is the one re-applied at the next unlock.

The mode intent must always be followed by a warmth write — asserting the mode makes the service re-apply its own stored value — which is why the first call deliberately skips the redundant-write early return.

After the change:

Lights  : Setting warmth to 10 of 10
Lights  : CTM mode set to manual
CTMService: setupCTM:0
LightsService: kk-1-brightness(color):10

Verified on hardware (BNRV1300)

  • Repairs a genuinely broken device; ctm_mode=0 reappears in the prefs.
  • Survives a screen cycle and a full reboot.
  • The assert fires exactly once per process — subsequent slider steps log no further mode intent.

Notes for review

  • Why the assert is unconditional. Running KOReader on this device requires disabling bn.ereader, which otherwise takes over the whole device. CTM's AUTO and SCHEDULE modes source their schedule from bn.ereader's location service, so with it disabled they cannot compute anything and degrade to re-applying the stored value — behaviourally identical to manual. I measured that: in modes 1 and 2 a warmth write survives a screen cycle exactly as it does in mode 0. Only mode -1 is broken.
  • One visible transient. Asserting the mode makes the service re-apply its stored value ~40 ms before ours lands, so there is a brief flash to the old warmth on the first write. On a device in the broken state that stored value is 0, so it reads as a momentary cold flicker at startup.
  • ctmModeAsserted is a plain @Volatile flag rather than an AtomicBoolean. Two threads racing it would send a duplicate mode intent, which is idempotent — happy to switch it if you'd prefer.
  • On its own this makes a warmth write stick; it does not restore warmth by itself, because nothing calls setWarmth() at startup. Its companion is android: restore saved warmth at startup koreader#16004, which does exactly that.

🤖 Generated with Claude Code


This change is Reviewable

GlowLightService also owns B&N's Color Temperature Management, which re-applies
a warmth value on every SCREEN_ON -- after the framework has restored
screen_brightness_color, so it wins over anything we set. Its mode is persisted
only in the service's own shared prefs, and getCTMMode() defaults to -1
(CTM_MODE_DISABLE) when that key is missing, which is what an unclean shutdown
leaves behind. In that mode setupCTM() forces COLD_LIGHT -- 0 on Emperor -- and
action_set_color_temperature does not persist either, because
saveColorTemperature() routes to a ScheduleMode whose save is a no-op with null
sunrise/sunset. Warmth therefore resets to cold on every unlock, and no amount
of moving the slider makes it stick.

Assert CTM_MODE_MANUAL once per process, immediately before the first warmth
write. In manual mode the same warmth intent writes manual_color_temperature,
so the value we set is what gets re-applied at the next unlock. The mode intent
must always be followed by a warmth write, since asserting the mode makes the
service re-apply its own stored value -- hence the first call skips the
redundant-write early return.

Verified on a BNRV1300. With ctm_mode absent, the service logs setupCTM:-1 and
drives the light to colour 0 on every SCREEN_ON. After this change the first
warmth write logs "CTM mode set to manual", the service logs setupCTM:0, and
the value survives both a screen cycle and a reboot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Frenzie

Frenzie commented Sep 5, 2026

Copy link
Copy Markdown
Member

Tell Claude to make its added comments shorter and simpler

Per review: the rationale is in the commit message and the PR, it does not
need restating in the source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@backcountrymountains

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Claude would rather like paragraphs than quick notes, sorry. This code has gotten pretty far outside my understanding, but koreader has been working great on my nook. I only found this issue after the nook got hot reading outside in the sun and did a forced shutdown, which messed up the warmth setting flow. The claude fix has been working great for me and I haven't seen any more issues. Thanks again!

@hugleo
hugleo merged commit 330cb52 into koreader:master Sep 5, 2026
3 checks passed
Frenzie added a commit to koreader/koreader that referenced this pull request Sep 28, 2026
Closes #16134, closes #14423.

* Add Onyx Boox Faraday 2 support and switch Poke3 to ADB lights (koreader/android-luajit-launcher#609) @hugleo 
* Fix EPD full refresh not working and add test entries for Lenovo Smart Paper (koreader/android-luajit-launcher#608) @4285f4
* Add Onyx Boox Go 6 Gen 2 support (koreader/android-luajit-launcher#616) @afzafri 
* Onyx Boox Go 10.3 Gen II (Lumi) (koreader/android-luajit-launcher#613) @SirProdigle
* Add e-ink refresh support for Topjoy E602. (koreader/android-luajit-launcher#618) @meeraez64-rgb 
* Change lights for Onyx Leaf and Onyx Poke3 (koreader/android-luajit-launcher#617) @hugleo 
* Nook GL4 Plus: make warmth survive B&N's colour temperature service (<koreader/android-luajit-launcher#620>) @backcountrymountains
* Apply android.supports_size_changes to the main activity (koreader/android-luajit-launcher#615) @xeropresence 
* Hide frontlight controls on Xiaomi Reader and Moaan Mix7 (both RK3566) (koreader/android-luajit-launcher#619) @jkl16 
* Ridi Paper 3: switch EPD controller to Nook/NTX (koreader/android-luajit-launcher#621) @nar-io
benoit-pierre pushed a commit to benoit-pierre/koreader that referenced this pull request Sep 29, 2026
…oreader/android-luajit-launcher#620)

* Nook GL4 Plus: make warmth survive B&N's colour temperature service

GlowLightService also owns B&N's Color Temperature Management, which re-applies
a warmth value on every SCREEN_ON -- after the framework has restored
screen_brightness_color, so it wins over anything we set. Its mode is persisted
only in the service's own shared prefs, and getCTMMode() defaults to -1
(CTM_MODE_DISABLE) when that key is missing, which is what an unclean shutdown
leaves behind. In that mode setupCTM() forces COLD_LIGHT -- 0 on Emperor -- and
action_set_color_temperature does not persist either, because
saveColorTemperature() routes to a ScheduleMode whose save is a no-op with null
sunrise/sunset. Warmth therefore resets to cold on every unlock, and no amount
of moving the slider makes it stick.

Assert CTM_MODE_MANUAL once per process, immediately before the first warmth
write. In manual mode the same warmth intent writes manual_color_temperature,
so the value we set is what gets re-applied at the next unlock. The mode intent
must always be followed by a warmth write, since asserting the mode makes the
service re-apply its own stored value -- hence the first call skips the
redundant-write early return.

Verified on a BNRV1300. With ctm_mode absent, the service logs setupCTM:-1 and
drives the light to colour 0 on every SCREEN_ON. After this change the first
warmth write logs "CTM mode set to manual", the service logs setupCTM:0, and
the value survives both a screen cycle and a reboot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Nook GL4 Plus: shorten added comments

Per review: the rationale is in the commit message and the PR, it does not
need restating in the source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Justin <backcountrymountains@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

Repo: android
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants