diff --git a/custom_components/nvidia_shield/media_player.py b/custom_components/nvidia_shield/media_player.py index ee1fdcf..b07224a 100644 --- a/custom_components/nvidia_shield/media_player.py +++ b/custom_components/nvidia_shield/media_player.py @@ -652,14 +652,16 @@ class NvidiaShieldMediaPlayer(MediaPlayerEntity): state = "playing" if info.playing else "paused" # Privacy mode: identity stays the app, never the media title title = None if not self._adb_store_info else info.title + if title is None and not self._adb_store_info: + # F3: privacy mode first — identity stays the app, so neither the + # channel number nor the media title can leak as the identity. + title = self.app_name or self.app_id or foreground if title is None and self._last_channel and ( # F1: the channel number belongs to the channel app only — never # leak it to the launcher or another foreground app. foreground == TIVIMATE_PACKAGE or foreground in manager.audio_playback ): title = self._last_channel - if title is None and not self._adb_store_info: - title = self.app_name or self.app_id or foreground if title is None and foreground in manager.audio_playback: # E hint (zero round-trip cost): audio was recently played title = "Media recently active" diff --git a/tests/test_command_routing.py b/tests/test_command_routing.py index 271264a..8ea865b 100644 --- a/tests/test_command_routing.py +++ b/tests/test_command_routing.py @@ -517,6 +517,37 @@ class TestChannelLeakGuard: assert np["title"] == "205" +class TestPrivacyOrdering: + """F3: privacy mode outranks the channel fallback — identity stays the app.""" + + async def test_channel_number_never_leaks_in_privacy_mode(self, modules): + player, manager = _ladder_player(modules, foreground=TIVIMATE_PACKAGE) + player._entry.options["adb_store_app_info"] = False # privacy on + + player._last_channel = "101" + np = player._get_app_now_playing() + + assert np["title"] == TIVIMATE_PACKAGE # app identity, not the channel + assert np["title"] != "101" + + async def test_channel_number_never_leaks_in_privacy_mode_with_last_known(self, modules): + from nsh.ha_adb_common import MediaSessionInfo + + player, manager = _ladder_player(modules, foreground=TIVIMATE_PACKAGE) + player._entry.options["adb_store_app_info"] = False + manager._clock = lambda: 0.0 + info = MediaSessionInfo( + package=TIVIMATE_PACKAGE, playing=True, title="Sky News" + ) + manager._last_known[TIVIMATE_PACKAGE] = (info, 0.0) + + player._last_channel = "101" + np = player._get_app_now_playing() + + assert np["state"] == "playing" # playback truth survives privacy + assert np["title"] == TIVIMATE_PACKAGE # but identity stays the app + + class TestVolumeAndPowerLadder: async def test_volume_readout_from_protocol_push(self, modules): player, served, _intents = make_player(modules)