mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 06:07:17 +00:00
Allow filtering of sources for Android TV (#30994)
This commit is contained in:
parent
61e41f0ddc
commit
31dc2ad284
@ -82,6 +82,7 @@ CONF_ADBKEY = "adbkey"
|
|||||||
CONF_ADB_SERVER_IP = "adb_server_ip"
|
CONF_ADB_SERVER_IP = "adb_server_ip"
|
||||||
CONF_ADB_SERVER_PORT = "adb_server_port"
|
CONF_ADB_SERVER_PORT = "adb_server_port"
|
||||||
CONF_APPS = "apps"
|
CONF_APPS = "apps"
|
||||||
|
CONF_EXCLUDE_UNNAMED_APPS = "exclude_unnamed_apps"
|
||||||
CONF_GET_SOURCES = "get_sources"
|
CONF_GET_SOURCES = "get_sources"
|
||||||
CONF_STATE_DETECTION_RULES = "state_detection_rules"
|
CONF_STATE_DETECTION_RULES = "state_detection_rules"
|
||||||
CONF_TURN_ON_COMMAND = "turn_on_command"
|
CONF_TURN_ON_COMMAND = "turn_on_command"
|
||||||
@ -134,12 +135,15 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||||||
vol.Optional(CONF_ADB_SERVER_IP): cv.string,
|
vol.Optional(CONF_ADB_SERVER_IP): cv.string,
|
||||||
vol.Optional(CONF_ADB_SERVER_PORT, default=DEFAULT_ADB_SERVER_PORT): cv.port,
|
vol.Optional(CONF_ADB_SERVER_PORT, default=DEFAULT_ADB_SERVER_PORT): cv.port,
|
||||||
vol.Optional(CONF_GET_SOURCES, default=DEFAULT_GET_SOURCES): cv.boolean,
|
vol.Optional(CONF_GET_SOURCES, default=DEFAULT_GET_SOURCES): cv.boolean,
|
||||||
vol.Optional(CONF_APPS, default=dict()): vol.Schema({cv.string: cv.string}),
|
vol.Optional(CONF_APPS, default=dict()): vol.Schema(
|
||||||
|
{cv.string: vol.Any(cv.string, None)}
|
||||||
|
),
|
||||||
vol.Optional(CONF_TURN_ON_COMMAND): cv.string,
|
vol.Optional(CONF_TURN_ON_COMMAND): cv.string,
|
||||||
vol.Optional(CONF_TURN_OFF_COMMAND): cv.string,
|
vol.Optional(CONF_TURN_OFF_COMMAND): cv.string,
|
||||||
vol.Optional(CONF_STATE_DETECTION_RULES, default={}): vol.Schema(
|
vol.Optional(CONF_STATE_DETECTION_RULES, default={}): vol.Schema(
|
||||||
{cv.string: ha_state_detection_rules_validator(vol.Invalid)}
|
{cv.string: ha_state_detection_rules_validator(vol.Invalid)}
|
||||||
),
|
),
|
||||||
|
vol.Optional(CONF_EXCLUDE_UNNAMED_APPS, default=False): cv.boolean,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -232,6 +236,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
config[CONF_GET_SOURCES],
|
config[CONF_GET_SOURCES],
|
||||||
config.get(CONF_TURN_ON_COMMAND),
|
config.get(CONF_TURN_ON_COMMAND),
|
||||||
config.get(CONF_TURN_OFF_COMMAND),
|
config.get(CONF_TURN_OFF_COMMAND),
|
||||||
|
config[CONF_EXCLUDE_UNNAMED_APPS],
|
||||||
]
|
]
|
||||||
|
|
||||||
if aftv.DEVICE_CLASS == DEVICE_ANDROIDTV:
|
if aftv.DEVICE_CLASS == DEVICE_ANDROIDTV:
|
||||||
@ -367,7 +372,14 @@ class ADBDevice(MediaPlayerDevice):
|
|||||||
"""Representation of an Android TV or Fire TV device."""
|
"""Representation of an Android TV or Fire TV device."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, aftv, name, apps, get_sources, turn_on_command, turn_off_command
|
self,
|
||||||
|
aftv,
|
||||||
|
name,
|
||||||
|
apps,
|
||||||
|
get_sources,
|
||||||
|
turn_on_command,
|
||||||
|
turn_off_command,
|
||||||
|
exclude_unnamed_apps,
|
||||||
):
|
):
|
||||||
"""Initialize the Android TV / Fire TV device."""
|
"""Initialize the Android TV / Fire TV device."""
|
||||||
self.aftv = aftv
|
self.aftv = aftv
|
||||||
@ -375,7 +387,7 @@ class ADBDevice(MediaPlayerDevice):
|
|||||||
self._app_id_to_name = APPS.copy()
|
self._app_id_to_name = APPS.copy()
|
||||||
self._app_id_to_name.update(apps)
|
self._app_id_to_name.update(apps)
|
||||||
self._app_name_to_id = {
|
self._app_name_to_id = {
|
||||||
value: key for key, value in self._app_id_to_name.items()
|
value: key for key, value in self._app_id_to_name.items() if value
|
||||||
}
|
}
|
||||||
self._get_sources = get_sources
|
self._get_sources = get_sources
|
||||||
self._keys = KEYS
|
self._keys = KEYS
|
||||||
@ -386,6 +398,8 @@ class ADBDevice(MediaPlayerDevice):
|
|||||||
self.turn_on_command = turn_on_command
|
self.turn_on_command = turn_on_command
|
||||||
self.turn_off_command = turn_off_command
|
self.turn_off_command = turn_off_command
|
||||||
|
|
||||||
|
self._exclude_unnamed_apps = exclude_unnamed_apps
|
||||||
|
|
||||||
# ADB exceptions to catch
|
# ADB exceptions to catch
|
||||||
if not self.aftv.adb_server_ip:
|
if not self.aftv.adb_server_ip:
|
||||||
# Using "adb_shell" (Python ADB implementation)
|
# Using "adb_shell" (Python ADB implementation)
|
||||||
@ -561,11 +575,24 @@ class AndroidTVDevice(ADBDevice):
|
|||||||
"""Representation of an Android TV device."""
|
"""Representation of an Android TV device."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, aftv, name, apps, get_sources, turn_on_command, turn_off_command
|
self,
|
||||||
|
aftv,
|
||||||
|
name,
|
||||||
|
apps,
|
||||||
|
get_sources,
|
||||||
|
turn_on_command,
|
||||||
|
turn_off_command,
|
||||||
|
exclude_unnamed_apps,
|
||||||
):
|
):
|
||||||
"""Initialize the Android TV device."""
|
"""Initialize the Android TV device."""
|
||||||
super().__init__(
|
super().__init__(
|
||||||
aftv, name, apps, get_sources, turn_on_command, turn_off_command
|
aftv,
|
||||||
|
name,
|
||||||
|
apps,
|
||||||
|
get_sources,
|
||||||
|
turn_on_command,
|
||||||
|
turn_off_command,
|
||||||
|
exclude_unnamed_apps,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._is_volume_muted = None
|
self._is_volume_muted = None
|
||||||
@ -603,9 +630,13 @@ class AndroidTVDevice(ADBDevice):
|
|||||||
self._available = False
|
self._available = False
|
||||||
|
|
||||||
if running_apps:
|
if running_apps:
|
||||||
self._sources = [
|
sources = [
|
||||||
self._app_id_to_name.get(app_id, app_id) for app_id in running_apps
|
self._app_id_to_name.get(
|
||||||
|
app_id, app_id if not self._exclude_unnamed_apps else None
|
||||||
|
)
|
||||||
|
for app_id in running_apps
|
||||||
]
|
]
|
||||||
|
self._sources = [source for source in sources if source]
|
||||||
else:
|
else:
|
||||||
self._sources = None
|
self._sources = None
|
||||||
|
|
||||||
@ -678,9 +709,13 @@ class FireTVDevice(ADBDevice):
|
|||||||
self._available = False
|
self._available = False
|
||||||
|
|
||||||
if running_apps:
|
if running_apps:
|
||||||
self._sources = [
|
sources = [
|
||||||
self._app_id_to_name.get(app_id, app_id) for app_id in running_apps
|
self._app_id_to_name.get(
|
||||||
|
app_id, app_id if not self._exclude_unnamed_apps else None
|
||||||
|
)
|
||||||
|
for app_id in running_apps
|
||||||
]
|
]
|
||||||
|
self._sources = [source for source in sources if source]
|
||||||
else:
|
else:
|
||||||
self._sources = None
|
self._sources = None
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ from homeassistant.components.androidtv.media_player import (
|
|||||||
CONF_ADB_SERVER_IP,
|
CONF_ADB_SERVER_IP,
|
||||||
CONF_ADBKEY,
|
CONF_ADBKEY,
|
||||||
CONF_APPS,
|
CONF_APPS,
|
||||||
|
CONF_EXCLUDE_UNNAMED_APPS,
|
||||||
KEYS,
|
KEYS,
|
||||||
SERVICE_ADB_COMMAND,
|
SERVICE_ADB_COMMAND,
|
||||||
SERVICE_DOWNLOAD,
|
SERVICE_DOWNLOAD,
|
||||||
@ -300,7 +301,11 @@ async def test_setup_with_adbkey(hass):
|
|||||||
async def _test_sources(hass, config0):
|
async def _test_sources(hass, config0):
|
||||||
"""Test that sources (i.e., apps) are handled correctly for Android TV and Fire TV devices."""
|
"""Test that sources (i.e., apps) are handled correctly for Android TV and Fire TV devices."""
|
||||||
config = config0.copy()
|
config = config0.copy()
|
||||||
config[DOMAIN][CONF_APPS] = {"com.app.test1": "TEST 1"}
|
config[DOMAIN][CONF_APPS] = {
|
||||||
|
"com.app.test1": "TEST 1",
|
||||||
|
"com.app.test3": None,
|
||||||
|
"com.app.test4": "",
|
||||||
|
}
|
||||||
patch_key, entity_id = _setup(config)
|
patch_key, entity_id = _setup(config)
|
||||||
|
|
||||||
with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[
|
with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[
|
||||||
@ -316,14 +321,16 @@ async def _test_sources(hass, config0):
|
|||||||
patch_update = patchers.patch_androidtv_update(
|
patch_update = patchers.patch_androidtv_update(
|
||||||
"playing",
|
"playing",
|
||||||
"com.app.test1",
|
"com.app.test1",
|
||||||
["com.app.test1", "com.app.test2"],
|
["com.app.test1", "com.app.test2", "com.app.test3", "com.app.test4"],
|
||||||
"hdmi",
|
"hdmi",
|
||||||
False,
|
False,
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
patch_update = patchers.patch_firetv_update(
|
patch_update = patchers.patch_firetv_update(
|
||||||
"playing", "com.app.test1", ["com.app.test1", "com.app.test2"]
|
"playing",
|
||||||
|
"com.app.test1",
|
||||||
|
["com.app.test1", "com.app.test2", "com.app.test3", "com.app.test4"],
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch_update:
|
with patch_update:
|
||||||
@ -332,20 +339,22 @@ async def _test_sources(hass, config0):
|
|||||||
assert state is not None
|
assert state is not None
|
||||||
assert state.state == STATE_PLAYING
|
assert state.state == STATE_PLAYING
|
||||||
assert state.attributes["source"] == "TEST 1"
|
assert state.attributes["source"] == "TEST 1"
|
||||||
assert state.attributes["source_list"] == ["TEST 1", "com.app.test2"]
|
assert sorted(state.attributes["source_list"]) == ["TEST 1", "com.app.test2"]
|
||||||
|
|
||||||
if config[DOMAIN].get(CONF_DEVICE_CLASS) != "firetv":
|
if config[DOMAIN].get(CONF_DEVICE_CLASS) != "firetv":
|
||||||
patch_update = patchers.patch_androidtv_update(
|
patch_update = patchers.patch_androidtv_update(
|
||||||
"playing",
|
"playing",
|
||||||
"com.app.test2",
|
"com.app.test2",
|
||||||
["com.app.test2", "com.app.test1"],
|
["com.app.test2", "com.app.test1", "com.app.test3", "com.app.test4"],
|
||||||
"hdmi",
|
"hdmi",
|
||||||
True,
|
True,
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
patch_update = patchers.patch_firetv_update(
|
patch_update = patchers.patch_firetv_update(
|
||||||
"playing", "com.app.test2", ["com.app.test2", "com.app.test1"]
|
"playing",
|
||||||
|
"com.app.test2",
|
||||||
|
["com.app.test2", "com.app.test1", "com.app.test3", "com.app.test4"],
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch_update:
|
with patch_update:
|
||||||
@ -354,7 +363,7 @@ async def _test_sources(hass, config0):
|
|||||||
assert state is not None
|
assert state is not None
|
||||||
assert state.state == STATE_PLAYING
|
assert state.state == STATE_PLAYING
|
||||||
assert state.attributes["source"] == "com.app.test2"
|
assert state.attributes["source"] == "com.app.test2"
|
||||||
assert state.attributes["source_list"] == ["com.app.test2", "TEST 1"]
|
assert sorted(state.attributes["source_list"]) == ["TEST 1", "com.app.test2"]
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -369,10 +378,82 @@ async def test_firetv_sources(hass):
|
|||||||
assert await _test_sources(hass, CONFIG_FIRETV_ADB_SERVER)
|
assert await _test_sources(hass, CONFIG_FIRETV_ADB_SERVER)
|
||||||
|
|
||||||
|
|
||||||
|
async def _test_exclude_sources(hass, config0, expected_sources):
|
||||||
|
"""Test that sources (i.e., apps) are handled correctly when the `exclude_unnamed_apps` config parameter is provided."""
|
||||||
|
config = config0.copy()
|
||||||
|
config[DOMAIN][CONF_APPS] = {
|
||||||
|
"com.app.test1": "TEST 1",
|
||||||
|
"com.app.test3": None,
|
||||||
|
"com.app.test4": "",
|
||||||
|
}
|
||||||
|
patch_key, entity_id = _setup(config)
|
||||||
|
|
||||||
|
with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[
|
||||||
|
patch_key
|
||||||
|
], patchers.patch_shell("")[patch_key]:
|
||||||
|
assert await async_setup_component(hass, DOMAIN, config)
|
||||||
|
await hass.helpers.entity_component.async_update_entity(entity_id)
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
|
if config[DOMAIN].get(CONF_DEVICE_CLASS) != "firetv":
|
||||||
|
patch_update = patchers.patch_androidtv_update(
|
||||||
|
"playing",
|
||||||
|
"com.app.test1",
|
||||||
|
[
|
||||||
|
"com.app.test1",
|
||||||
|
"com.app.test2",
|
||||||
|
"com.app.test3",
|
||||||
|
"com.app.test4",
|
||||||
|
"com.app.test5",
|
||||||
|
],
|
||||||
|
"hdmi",
|
||||||
|
False,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
patch_update = patchers.patch_firetv_update(
|
||||||
|
"playing",
|
||||||
|
"com.app.test1",
|
||||||
|
[
|
||||||
|
"com.app.test1",
|
||||||
|
"com.app.test2",
|
||||||
|
"com.app.test3",
|
||||||
|
"com.app.test4",
|
||||||
|
"com.app.test5",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch_update:
|
||||||
|
await hass.helpers.entity_component.async_update_entity(entity_id)
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == STATE_PLAYING
|
||||||
|
assert state.attributes["source"] == "TEST 1"
|
||||||
|
assert sorted(state.attributes["source_list"]) == expected_sources
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def test_androidtv_exclude_sources(hass):
|
||||||
|
"""Test that sources (i.e., apps) are handled correctly for Android TV devices when the `exclude_unnamed_apps` config parameter is provided as true."""
|
||||||
|
config = CONFIG_ANDROIDTV_ADB_SERVER.copy()
|
||||||
|
config[DOMAIN][CONF_EXCLUDE_UNNAMED_APPS] = True
|
||||||
|
assert await _test_exclude_sources(hass, config, ["TEST 1"])
|
||||||
|
|
||||||
|
|
||||||
|
async def test_firetv_exclude_sources(hass):
|
||||||
|
"""Test that sources (i.e., apps) are handled correctly for Fire TV devices when the `exclude_unnamed_apps` config parameter is provided as true."""
|
||||||
|
config = CONFIG_FIRETV_ADB_SERVER.copy()
|
||||||
|
config[DOMAIN][CONF_EXCLUDE_UNNAMED_APPS] = True
|
||||||
|
assert await _test_exclude_sources(hass, config, ["TEST 1"])
|
||||||
|
|
||||||
|
|
||||||
async def _test_select_source(hass, config0, source, expected_arg, method_patch):
|
async def _test_select_source(hass, config0, source, expected_arg, method_patch):
|
||||||
"""Test that the methods for launching and stopping apps are called correctly when selecting a source."""
|
"""Test that the methods for launching and stopping apps are called correctly when selecting a source."""
|
||||||
config = config0.copy()
|
config = config0.copy()
|
||||||
config[DOMAIN][CONF_APPS] = {"com.app.test1": "TEST 1"}
|
config[DOMAIN][CONF_APPS] = {"com.app.test1": "TEST 1", "com.app.test3": None}
|
||||||
patch_key, entity_id = _setup(config)
|
patch_key, entity_id = _setup(config)
|
||||||
|
|
||||||
with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[
|
with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[
|
||||||
@ -429,6 +510,17 @@ async def test_androidtv_select_source_launch_app_id_no_name(hass):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_androidtv_select_source_launch_app_hidden(hass):
|
||||||
|
"""Test that an app can be launched using its app ID when it is hidden from the sources list."""
|
||||||
|
assert await _test_select_source(
|
||||||
|
hass,
|
||||||
|
CONFIG_ANDROIDTV_ADB_SERVER,
|
||||||
|
"com.app.test3",
|
||||||
|
"com.app.test3",
|
||||||
|
patchers.PATCH_LAUNCH_APP,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_androidtv_select_source_stop_app_id(hass):
|
async def test_androidtv_select_source_stop_app_id(hass):
|
||||||
"""Test that an app can be stopped using its app ID."""
|
"""Test that an app can be stopped using its app ID."""
|
||||||
assert await _test_select_source(
|
assert await _test_select_source(
|
||||||
@ -462,6 +554,17 @@ async def test_androidtv_select_source_stop_app_id_no_name(hass):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_androidtv_select_source_stop_app_hidden(hass):
|
||||||
|
"""Test that an app can be stopped using its app ID when it is hidden from the sources list."""
|
||||||
|
assert await _test_select_source(
|
||||||
|
hass,
|
||||||
|
CONFIG_ANDROIDTV_ADB_SERVER,
|
||||||
|
"!com.app.test3",
|
||||||
|
"com.app.test3",
|
||||||
|
patchers.PATCH_STOP_APP,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_firetv_select_source_launch_app_id(hass):
|
async def test_firetv_select_source_launch_app_id(hass):
|
||||||
"""Test that an app can be launched using its app ID."""
|
"""Test that an app can be launched using its app ID."""
|
||||||
assert await _test_select_source(
|
assert await _test_select_source(
|
||||||
@ -495,6 +598,17 @@ async def test_firetv_select_source_launch_app_id_no_name(hass):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_firetv_select_source_launch_app_hidden(hass):
|
||||||
|
"""Test that an app can be launched using its app ID when it is hidden from the sources list."""
|
||||||
|
assert await _test_select_source(
|
||||||
|
hass,
|
||||||
|
CONFIG_FIRETV_ADB_SERVER,
|
||||||
|
"com.app.test3",
|
||||||
|
"com.app.test3",
|
||||||
|
patchers.PATCH_LAUNCH_APP,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_firetv_select_source_stop_app_id(hass):
|
async def test_firetv_select_source_stop_app_id(hass):
|
||||||
"""Test that an app can be stopped using its app ID."""
|
"""Test that an app can be stopped using its app ID."""
|
||||||
assert await _test_select_source(
|
assert await _test_select_source(
|
||||||
@ -528,6 +642,17 @@ async def test_firetv_select_source_stop_app_id_no_name(hass):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_firetv_select_source_stop_hidden(hass):
|
||||||
|
"""Test that an app can be stopped using its app ID when it is hidden from the sources list."""
|
||||||
|
assert await _test_select_source(
|
||||||
|
hass,
|
||||||
|
CONFIG_FIRETV_ADB_SERVER,
|
||||||
|
"!com.app.test3",
|
||||||
|
"com.app.test3",
|
||||||
|
patchers.PATCH_STOP_APP,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _test_setup_fail(hass, config):
|
async def _test_setup_fail(hass, config):
|
||||||
"""Test that the entity is not created when the ADB connection is not established."""
|
"""Test that the entity is not created when the ADB connection is not established."""
|
||||||
patch_key, entity_id = _setup(config)
|
patch_key, entity_id = _setup(config)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user