Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 44 additions & 9 deletions homeassistant/components/androidtv/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
CONF_ADB_SERVER_IP = "adb_server_ip"
CONF_ADB_SERVER_PORT = "adb_server_port"
CONF_APPS = "apps"
CONF_EXCLUDE_UNNAMED_APPS = "exclude_unnamed_apps"
CONF_GET_SOURCES = "get_sources"
CONF_STATE_DETECTION_RULES = "state_detection_rules"
CONF_TURN_ON_COMMAND = "turn_on_command"
Expand Down Expand Up @@ -132,12 +133,15 @@
vol.Optional(CONF_ADB_SERVER_IP): cv.string,
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_APPS, default=dict()): vol.Schema({cv.string: cv.string}),
vol.Optional(CONF_APPS, default=dict()): vol.Schema(
{cv.string: vol.Any(cv.string, None)}
Comment thread
JeffLIrion marked this conversation as resolved.
),
vol.Optional(CONF_TURN_ON_COMMAND): cv.string,
vol.Optional(CONF_TURN_OFF_COMMAND): cv.string,
vol.Optional(CONF_STATE_DETECTION_RULES, default={}): vol.Schema(
{cv.string: ha_state_detection_rules_validator(vol.Invalid)}
),
vol.Optional(CONF_EXCLUDE_UNNAMED_APPS, default=False): cv.boolean,
}
)

Expand Down Expand Up @@ -230,6 +234,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
config[CONF_GET_SOURCES],
config.get(CONF_TURN_ON_COMMAND),
config.get(CONF_TURN_OFF_COMMAND),
config[CONF_EXCLUDE_UNNAMED_APPS],
]

if aftv.DEVICE_CLASS == DEVICE_ANDROIDTV:
Expand Down Expand Up @@ -365,15 +370,22 @@ class ADBDevice(MediaPlayerDevice):
"""Representation of an Android TV or Fire TV device."""

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."""
self.aftv = aftv
self._name = name
self._app_id_to_name = APPS.copy()
self._app_id_to_name.update(apps)
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._keys = KEYS
Expand All @@ -384,6 +396,8 @@ def __init__(
self.turn_on_command = turn_on_command
self.turn_off_command = turn_off_command

self._exclude_unnamed_apps = exclude_unnamed_apps

# ADB exceptions to catch
if not self.aftv.adb_server_ip:
# Using "adb_shell" (Python ADB implementation)
Expand Down Expand Up @@ -558,11 +572,24 @@ class AndroidTVDevice(ADBDevice):
"""Representation of an Android TV device."""

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."""
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
Expand Down Expand Up @@ -600,9 +627,13 @@ def update(self):
self._available = False

if running_apps:
self._sources = [
self._app_id_to_name.get(app_id, app_id) for app_id in running_apps
sources = [
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:
self._sources = None

Expand Down Expand Up @@ -670,9 +701,13 @@ def update(self):
self._available = False

if running_apps:
self._sources = [
self._app_id_to_name.get(app_id, app_id) for app_id in running_apps
sources = [
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:
self._sources = None

Expand Down
141 changes: 133 additions & 8 deletions tests/components/androidtv/test_media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
CONF_ADB_SERVER_IP,
CONF_ADBKEY,
CONF_APPS,
CONF_EXCLUDE_UNNAMED_APPS,
KEYS,
SERVICE_ADB_COMMAND,
SERVICE_DOWNLOAD,
Expand Down Expand Up @@ -299,7 +300,11 @@ async def test_setup_with_adbkey(hass):
async def _test_sources(hass, config0):
"""Test that sources (i.e., apps) are handled correctly for Android TV and Fire TV devices."""
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,
Comment thread
JeffLIrion marked this conversation as resolved.
"com.app.test4": "",
}
patch_key, entity_id = _setup(config)

with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[
Expand All @@ -315,14 +320,16 @@ async def _test_sources(hass, config0):
patch_update = patchers.patch_androidtv_update(
"playing",
"com.app.test1",
["com.app.test1", "com.app.test2"],
["com.app.test1", "com.app.test2", "com.app.test3", "com.app.test4"],
"hdmi",
False,
1,
)
else:
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:
Expand All @@ -331,20 +338,22 @@ async def _test_sources(hass, config0):
assert state is not None
assert state.state == STATE_PLAYING
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":
patch_update = patchers.patch_androidtv_update(
"playing",
"com.app.test2",
["com.app.test2", "com.app.test1"],
["com.app.test2", "com.app.test1", "com.app.test3", "com.app.test4"],
"hdmi",
True,
0,
)
else:
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:
Expand All @@ -353,7 +362,7 @@ async def _test_sources(hass, config0):
assert state is not None
assert state.state == STATE_PLAYING
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

Expand All @@ -368,10 +377,82 @@ async def test_firetv_sources(hass):
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):
"""Test that the methods for launching and stopping apps are called correctly when selecting a source."""
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}
Comment thread
JeffLIrion marked this conversation as resolved.
patch_key, entity_id = _setup(config)

with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[
Expand Down Expand Up @@ -428,6 +509,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):
"""Test that an app can be stopped using its app ID."""
assert await _test_select_source(
Expand Down Expand Up @@ -461,6 +553,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):
"""Test that an app can be launched using its app ID."""
assert await _test_select_source(
Expand Down Expand Up @@ -494,6 +597,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):
"""Test that an app can be stopped using its app ID."""
assert await _test_select_source(
Expand Down Expand Up @@ -527,6 +641,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):
"""Test that the entity is not created when the ADB connection is not established."""
patch_key, entity_id = _setup(config)
Expand Down