Android TV Does Not Sleep When Idle (Screen Goes Black but Device Stays On) #4

Open
opened 2026-07-04 22:24:48 +00:00 by k-nacion · 0 comments
Owner

Android TV Does Not Sleep When Idle (Screen Goes Black but Device Stays On)

Header

Field Value
Date 2026-07-05
Reporter User
Platform Android TV
Component Native Android — MediaSession lifecycle
Status Confirmed
Severity Medium (power usage / user annoyance, not a crash)
Primary suspected cause 534f5f4d — fix(android): remove insecure SSL bypass + proper MediaSession lifecycle (MediaSession never deactivates)
Secondary contributing change fix/firestick-black-screen-remove-visibility-toggle — removal of videoLayout.setVisibility(INVISIBLE) on idle (video surface stays VISIBLE)

Summary

When the user is idle in AnimeTV (not playing anything — e.g. sitting on the
homepage/browse screen or paused), the Android TV no longer goes to sleep as it
used to. Instead the screen turns black (display timeout) but the device stays
powered on. This is consistent with the app holding the system in an "active
media" state, which inhibits the Android TV screensaver/standby.

The root cause is an always-active MediaSession: once playback starts, the
MediaSession is activated and is never deactivated, because the only code
path that updates the media state can report STATE_PLAYING or STATE_PAUSED
but never a stopped/none state. Android TV treats an active media session as a
reason to keep the device awake.


Key Findings

1. The only screen-keep-on flag is correctly scoped to playback

In AnimeView.java, setKeepScreenOn is only applied to the video surface and
is cleared on pause/stop:

public void videoPlayerPlay(){
  videoPlayer.start();
  videoView.setKeepScreenOn(true);
}
public void videoPlayerPause(){
  videoView.setKeepScreenOn(false);
  videoPlayer.pause();
}
public void videoPlayerStop(){
  videoView.setKeepScreenOn(false);
  videoPlayer.stop();
}

There is no window-level FLAG_KEEP_SCREEN_ON and no PowerManager
wake lock, and ExoPlayer's setWakeMode is not used. So the keep-screen-on
behavior is not the cause.

2. MediaSession is activated based on state, but never receives a "stopped" state

MainActivity.mediaSetState() (added in commit 534f5f4d):

public void mediaSetState(int mediaState, long pos) {
  _metaState = mediaState;
  _metaPosition = pos;

  // Activate/deactivate MediaSession based on playback state
  if (mSession != null) {
    boolean shouldBeActive = (mediaState == PlaybackState.STATE_PLAYING
        || mediaState == PlaybackState.STATE_PAUSED
        || mediaState == PlaybackState.STATE_BUFFERING);
    if (shouldBeActive != mSession.isActive()) {
      mSession.setActive(shouldBeActive);
    }
  }

  updateMediaState();
}

The intent was: activate when PLAYING/PAUSED/BUFFERING, deactivate otherwise.

3. The caller can never produce a "deactivate" state

mediaSetState() is only ever called from one place, inside the ExoPlayer
AnalyticsListener in AnimeView.java:

me().mediaSetState(isPlaying ? PlaybackState.STATE_PLAYING
                             : PlaybackState.STATE_PAUSED,
                   eventTime.currentPlaybackPositionMs);

This callback (onIsPlayingChanged) can only ever emit STATE_PLAYING or
STATE_PAUSED. There is no code path that passes STATE_NONE,
STATE_STOPPED, or STATE_IDLE.

Consequence:

  • User starts a video → STATE_PLAYING → mSession.setActive(true).
  • User pauses, stops the video, or exits back to the homepage → STATE_PAUSED
    → shouldBeActive is still true → the session stays active.
  • The session is never deactivated while the app is running. It is only released
    in onDestroy().

4. Why an active MediaSession keeps Android TV awake

Android TV's standby/screensaver (Daydream) is inhibited while an app holds an
active media session, because the system assumes media playback (or paused
media that may resume) is in progress. With the session stuck active in
STATE_PAUSED, the OS blanks the display on its normal timeout but does not put
the device to sleep — exactly the reported symptom.


Comparison (Before / After the regression)

Scenario Before 534f5f4d After 534f5f4d (current)
Idle on homepage, never played Session inactive → TV sleeps Session inactive (never played) → TV sleeps
Watched a video, then paused/stopped/idle No setActive management → TV sleeps Session stuck active (PAUSED) → TV does not sleep, screen goes black only

This matches the user's phrasing: "but now" — the behavior changed after the
MediaSession activation logic was introduced.

Timing caveat: The MediaSession activation logic (534f5f4d) predates
v5.14.4. If the user is certain the regression appeared strictly after
v5.14.4, the MediaSession bug may be a long-standing latent issue that the
user only recently noticed, rather than a brand-new change. The bug is real
and matches the symptom regardless of exact version; on-device confirmation
(dumpsys media_session) will settle whether it is the active cause.


Secondary Finding — Idle video surface left VISIBLE (post-v5.14.4)

A change on the current branch
(fix/firestick-black-screen-remove-visibility-toggle) removed the
videoLayout.setVisibility(View.INVISIBLE) call from the idle/stop branch of
videoSetUrl in AnimeView.java. Previously, clearing the URL
(videoSetUrl("")) hid the video surface; now the surface is left attached and
VISIBLE between streams.

Evidence of the removal (current videoSetUrlWithPosition, the url.equals("")
branch now only calls videoPlayerStop() + videoSetSource(""), with no
visibility toggle), plus a now-stale comment near videoPlayerStop() that
still claims "The videoLayout.setVisibility(View.INVISIBLE) in
videoSetUrl("") already prevents the last-frame flash"
— that call no longer
exists.

Why this matters for standby:

  • A persistently-attached/visible hardware video Surface can, on some Fire TV
    / Shield devices, contribute to the system keeping the compositor/device
    active even after playback stops.
  • This is a genuine post-v5.14.4 change and therefore fits the "but now" timing
    better than the MediaSession bug alone.

Constraint (important): the visibility toggle was intentionally removed to
fix the Fire TV / Shield black-screen-audio-only bug documented in
2026-07-03_miruro-hls-black-screen-audio-only-firestick-shield.md. Any fix for
the standby issue must not re-introduce the surface-recreation bug. Prefer
addressing standby via the MediaSession deactivation path (Recommended Actions
below) rather than re-adding the visibility toggle.


Failure Scenarios

  • Power drain / heat: Device never enters standby after any playback session
    for the rest of the app's lifetime, wasting power and generating heat.
  • Burn-in risk: On OLED TVs, an indefinitely-on-but-black panel plus stuck UI
    increases burn-in exposure over long idle periods.
  • User confusion: TV appears "off" (black) but is actually on; remote presses
    may behave unexpectedly.

Immediate / short-term fix

Deactivate the MediaSession when playback is genuinely stopped, not merely
paused. Options (any one resolves the core issue):

  1. Add a stopped/none state path — call
    mediaSetState(PlaybackState.STATE_NONE, 0) (or STATE_STOPPED) from
    videoPlayerStop() / when the player transitions to IDLE/ENDED, and remove
    STATE_PAUSED from the shouldBeActive set so a paused-and-idle session can
    deactivate. (Note: keeping PAUSED active is standard for resumable media, so
    prefer option 2 for the idle case.)

  2. Deactivate the session when the app goes to background / stops — in
    MainActivity.onStop() (or AnimeView.onStartPause(false)), if not actively
    playing, call mSession.setActive(false). This ensures that once the user
    leaves playback / goes idle, the session no longer inhibits standby.

  3. Deactivate on player IDLE/ENDED — hook onPlaybackStateChanged (state
    1=IDLE / 4=ENDED) to report a stopped media state so the session
    deactivates when there is nothing to resume.

Recommended combination: report a stopped state from the player's IDLE/ENDED /
videoPlayerStop() path and deactivate on onStop() when not playing.

Long-term

  • Consider migrating to a single source of truth for playback state that always
    emits an explicit terminal state, so the MediaSession lifecycle cannot get
    "stuck".
  • Add a regression check/manual test step: after watching and stopping a video,
    confirm the TV enters standby within the OS timeout.

Files Involved

File Role
app/src/main/java/com/amarullz/androidtv/animetvjmto/MainActivity.java mediaSetState() MediaSession activation logic; onStop()/onPause() lifecycle
app/src/main/java/com/amarullz/androidtv/animetvjmto/AnimeView.java ExoPlayer AnalyticsListener (only caller of mediaSetState); videoPlayerPlay/Pause/Stop, onStartPause

Commit Description
534f5f4d fix(android): remove insecure SSL bypass + proper MediaSession lifecycle (introduced the always-active-session behavior)
63df455b Implement MediaSession for media remote control (original MediaSession)
7d886337 remove unused MediaSession actions

Status Notes

  • Confirmed by static analysis: the sole caller of mediaSetState cannot
    produce a deactivating state, so the session remains active after any playback.
  • Not yet verified on-device with dumpsys power / dumpsys media_session, but
    the code path is unambiguous. On-device confirmation recommended:
    • adb shell dumpsys media_session should show the AnimeTV session as
      active=true while idle after playback.
    • adb shell dumpsys power should show the display allowed to sleep but the
      device kept awake by media.
# Android TV Does Not Sleep When Idle (Screen Goes Black but Device Stays On) ## Header | Field | Value | |-------|-------| | **Date** | 2026-07-05 | | **Reporter** | User | | **Platform** | Android TV | | **Component** | Native Android — MediaSession lifecycle | | **Status** | `Confirmed` | | **Severity** | Medium (power usage / user annoyance, not a crash) | | **Primary suspected cause** | `534f5f4d` — *fix(android): remove insecure SSL bypass + proper MediaSession lifecycle* (MediaSession never deactivates) | | **Secondary contributing change** | `fix/firestick-black-screen-remove-visibility-toggle` — removal of `videoLayout.setVisibility(INVISIBLE)` on idle (video surface stays VISIBLE) | --- ## Summary When the user is idle in AnimeTV (not playing anything — e.g. sitting on the homepage/browse screen or paused), the Android TV no longer goes to sleep as it used to. Instead the screen turns black (display timeout) but the device stays powered on. This is consistent with the app holding the system in an "active media" state, which inhibits the Android TV screensaver/standby. The root cause is an **always-active MediaSession**: once playback starts, the `MediaSession` is activated and is **never deactivated**, because the only code path that updates the media state can report `STATE_PLAYING` or `STATE_PAUSED` but never a stopped/none state. Android TV treats an active media session as a reason to keep the device awake. --- ## Key Findings ### 1. The only screen-keep-on flag is correctly scoped to playback In `AnimeView.java`, `setKeepScreenOn` is only applied to the video surface and is cleared on pause/stop: ```java public void videoPlayerPlay(){ videoPlayer.start(); videoView.setKeepScreenOn(true); } public void videoPlayerPause(){ videoView.setKeepScreenOn(false); videoPlayer.pause(); } public void videoPlayerStop(){ videoView.setKeepScreenOn(false); videoPlayer.stop(); } ``` There is **no** window-level `FLAG_KEEP_SCREEN_ON` and **no** `PowerManager` wake lock, and ExoPlayer's `setWakeMode` is not used. So the keep-screen-on behavior is not the cause. ### 2. MediaSession is activated based on state, but never receives a "stopped" state `MainActivity.mediaSetState()` (added in commit `534f5f4d`): ```java public void mediaSetState(int mediaState, long pos) { _metaState = mediaState; _metaPosition = pos; // Activate/deactivate MediaSession based on playback state if (mSession != null) { boolean shouldBeActive = (mediaState == PlaybackState.STATE_PLAYING || mediaState == PlaybackState.STATE_PAUSED || mediaState == PlaybackState.STATE_BUFFERING); if (shouldBeActive != mSession.isActive()) { mSession.setActive(shouldBeActive); } } updateMediaState(); } ``` The intent was: activate when PLAYING/PAUSED/BUFFERING, deactivate otherwise. ### 3. The caller can never produce a "deactivate" state `mediaSetState()` is only ever called from **one** place, inside the ExoPlayer `AnalyticsListener` in `AnimeView.java`: ```java me().mediaSetState(isPlaying ? PlaybackState.STATE_PLAYING : PlaybackState.STATE_PAUSED, eventTime.currentPlaybackPositionMs); ``` This callback (`onIsPlayingChanged`) can only ever emit `STATE_PLAYING` or `STATE_PAUSED`. There is **no code path** that passes `STATE_NONE`, `STATE_STOPPED`, or `STATE_IDLE`. **Consequence:** - User starts a video → `STATE_PLAYING` → `mSession.setActive(true)`. - User pauses, stops the video, or exits back to the homepage → `STATE_PAUSED` → `shouldBeActive` is still `true` → the session **stays active**. - The session is never deactivated while the app is running. It is only released in `onDestroy()`. ### 4. Why an active MediaSession keeps Android TV awake Android TV's standby/screensaver (Daydream) is inhibited while an app holds an **active media session**, because the system assumes media playback (or paused media that may resume) is in progress. With the session stuck active in `STATE_PAUSED`, the OS blanks the display on its normal timeout but does not put the device to sleep — exactly the reported symptom. --- ## Comparison (Before / After the regression) | Scenario | Before `534f5f4d` | After `534f5f4d` (current) | |----------|-------------------|----------------------------| | Idle on homepage, never played | Session inactive → TV sleeps | Session inactive (never played) → TV sleeps | | Watched a video, then paused/stopped/idle | No `setActive` management → TV sleeps | Session stuck `active` (PAUSED) → **TV does not sleep, screen goes black only** | This matches the user's phrasing: *"but now"* — the behavior changed after the MediaSession activation logic was introduced. > **Timing caveat:** The MediaSession activation logic (`534f5f4d`) predates > v5.14.4. If the user is certain the regression appeared strictly *after* > v5.14.4, the MediaSession bug may be a long-standing latent issue that the > user only recently noticed, rather than a brand-new change. The bug is real > and matches the symptom regardless of exact version; on-device confirmation > (`dumpsys media_session`) will settle whether it is the active cause. --- ## Secondary Finding — Idle video surface left VISIBLE (post-v5.14.4) A change on the current branch (`fix/firestick-black-screen-remove-visibility-toggle`) **removed** the `videoLayout.setVisibility(View.INVISIBLE)` call from the idle/stop branch of `videoSetUrl` in `AnimeView.java`. Previously, clearing the URL (`videoSetUrl("")`) hid the video surface; now the surface is left attached and VISIBLE between streams. Evidence of the removal (current `videoSetUrlWithPosition`, the `url.equals("")` branch now only calls `videoPlayerStop()` + `videoSetSource("")`, with no visibility toggle), plus a now-**stale comment** near `videoPlayerStop()` that still claims *"The `videoLayout.setVisibility(View.INVISIBLE)` in `videoSetUrl("")` already prevents the last-frame flash"* — that call no longer exists. Why this matters for standby: - A persistently-attached/visible hardware video **Surface** can, on some Fire TV / Shield devices, contribute to the system keeping the compositor/device active even after playback stops. - This is a genuine post-v5.14.4 change and therefore fits the "*but now*" timing better than the MediaSession bug alone. **Constraint (important):** the visibility toggle was intentionally removed to fix the Fire TV / Shield *black-screen-audio-only* bug documented in `2026-07-03_miruro-hls-black-screen-audio-only-firestick-shield.md`. Any fix for the standby issue **must not** re-introduce the surface-recreation bug. Prefer addressing standby via the MediaSession deactivation path (Recommended Actions below) rather than re-adding the visibility toggle. --- ## Failure Scenarios - **Power drain / heat:** Device never enters standby after any playback session for the rest of the app's lifetime, wasting power and generating heat. - **Burn-in risk:** On OLED TVs, an indefinitely-on-but-black panel plus stuck UI increases burn-in exposure over long idle periods. - **User confusion:** TV appears "off" (black) but is actually on; remote presses may behave unexpectedly. --- ## Recommended Actions ### Immediate / short-term fix Deactivate the MediaSession when playback is genuinely stopped, not merely paused. Options (any one resolves the core issue): 1. **Add a stopped/none state path** — call `mediaSetState(PlaybackState.STATE_NONE, 0)` (or `STATE_STOPPED`) from `videoPlayerStop()` / when the player transitions to IDLE/ENDED, and remove `STATE_PAUSED` from the `shouldBeActive` set so a paused-and-idle session can deactivate. (Note: keeping PAUSED active is standard for resumable media, so prefer option 2 for the idle case.) 2. **Deactivate the session when the app goes to background / stops** — in `MainActivity.onStop()` (or `AnimeView.onStartPause(false)`), if not actively playing, call `mSession.setActive(false)`. This ensures that once the user leaves playback / goes idle, the session no longer inhibits standby. 3. **Deactivate on player IDLE/ENDED** — hook `onPlaybackStateChanged` (state `1=IDLE` / `4=ENDED`) to report a stopped media state so the session deactivates when there is nothing to resume. Recommended combination: report a stopped state from the player's IDLE/ENDED / `videoPlayerStop()` path **and** deactivate on `onStop()` when not playing. ### Long-term - Consider migrating to a single source of truth for playback state that always emits an explicit terminal state, so the MediaSession lifecycle cannot get "stuck". - Add a regression check/manual test step: after watching and stopping a video, confirm the TV enters standby within the OS timeout. --- ## Files Involved | File | Role | |------|------| | `app/src/main/java/com/amarullz/androidtv/animetvjmto/MainActivity.java` | `mediaSetState()` MediaSession activation logic; `onStop()/onPause()` lifecycle | | `app/src/main/java/com/amarullz/androidtv/animetvjmto/AnimeView.java` | ExoPlayer `AnalyticsListener` (only caller of `mediaSetState`); `videoPlayerPlay/Pause/Stop`, `onStartPause` | --- ## Related Commits | Commit | Description | |--------|-------------| | `534f5f4d` | fix(android): remove insecure SSL bypass + **proper MediaSession lifecycle** (introduced the always-active-session behavior) | | `63df455b` | Implement MediaSession for media remote control (original MediaSession) | | `7d886337` | remove unused MediaSession actions | --- ## Status Notes - **Confirmed** by static analysis: the sole caller of `mediaSetState` cannot produce a deactivating state, so the session remains active after any playback. - Not yet verified on-device with `dumpsys power` / `dumpsys media_session`, but the code path is unambiguous. On-device confirmation recommended: - `adb shell dumpsys media_session` should show the AnimeTV session as `active=true` while idle after playback. - `adb shell dumpsys power` should show the display allowed to sleep but the device kept awake by media.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Anime-TV-Fork/AnimeTV#4
No description provided.