Allow filtering of sources for Android TV (#30994)

This commit is contained in:
Jeff Irion 2020-01-29 12:13:09 -08:00 committed by GitHub
parent 61e41f0ddc
commit 31dc2ad284
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 177 additions and 17 deletions

View File

@ -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

View File

@ -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)