Update androidtv version to improve source selection support (#29579)

* Change androidtv module versions and add support for select_source for all device types

* Update and add tests

* Update requirements_test_all.txt

* Update requirements_all.txt

* Consolidate tests

* Fix typo

* Remove 'self._device'
This commit is contained in:
Raman Gupta 2019-12-14 10:54:41 -05:00 committed by Fabian Affolter
parent 3db7e8f5e9
commit 003658a3f0
6 changed files with 224 additions and 76 deletions

View File

@ -3,8 +3,8 @@
"name": "Androidtv",
"documentation": "https://www.home-assistant.io/integrations/androidtv",
"requirements": [
"adb-shell==0.0.8",
"androidtv==0.0.34",
"adb-shell==0.0.9",
"androidtv==0.0.35",
"pure-python-adb==0.2.2.dev0"
],
"dependencies": [],

View File

@ -55,6 +55,7 @@ SUPPORT_ANDROIDTV = (
| SUPPORT_TURN_OFF
| SUPPORT_PREVIOUS_TRACK
| SUPPORT_NEXT_TRACK
| SUPPORT_SELECT_SOURCE
| SUPPORT_STOP
| SUPPORT_VOLUME_MUTE
| SUPPORT_VOLUME_STEP
@ -199,6 +200,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
aftv,
config[CONF_NAME],
config[CONF_APPS],
config[CONF_GET_SOURCES],
config.get(CONF_TURN_ON_COMMAND),
config.get(CONF_TURN_OFF_COMMAND),
)
@ -287,7 +289,9 @@ def adb_decorator(override_available=False):
class ADBDevice(MediaPlayerDevice):
"""Representation of an Android TV or Fire TV device."""
def __init__(self, aftv, name, apps, turn_on_command, turn_off_command):
def __init__(
self, aftv, name, apps, get_sources, turn_on_command, turn_off_command
):
"""Initialize the Android TV / Fire TV device."""
self.aftv = aftv
self._name = name
@ -296,6 +300,7 @@ class ADBDevice(MediaPlayerDevice):
self._app_name_to_id = {
value: key for key, value in self._app_id_to_name.items()
}
self._get_sources = get_sources
self._keys = KEYS
self._device_properties = self.aftv.device_properties
@ -325,6 +330,7 @@ class ADBDevice(MediaPlayerDevice):
self._adb_response = None
self._available = True
self._current_app = None
self._sources = None
self._state = None
@property
@ -357,6 +363,16 @@ class ADBDevice(MediaPlayerDevice):
"""Device should be polled."""
return True
@property
def source(self):
"""Return the current app."""
return self._app_id_to_name.get(self._current_app, self._current_app)
@property
def source_list(self):
"""Return a list of running apps."""
return self._sources
@property
def state(self):
"""Return the state of the player."""
@ -408,6 +424,20 @@ class ADBDevice(MediaPlayerDevice):
"""Send next track command (results in fast-forward)."""
self.aftv.media_next_track()
@adb_decorator()
def select_source(self, source):
"""Select input source.
If the source starts with a '!', then it will close the app instead of
opening it.
"""
if isinstance(source, str):
if not source.startswith("!"):
self.aftv.launch_app(self._app_name_to_id.get(source, source))
else:
source_ = source[1:].lstrip()
self.aftv.stop_app(self._app_name_to_id.get(source_, source_))
@adb_decorator()
def adb_command(self, cmd):
"""Send an ADB command to an Android TV / Fire TV device."""
@ -436,11 +466,14 @@ class ADBDevice(MediaPlayerDevice):
class AndroidTVDevice(ADBDevice):
"""Representation of an Android TV device."""
def __init__(self, aftv, name, apps, turn_on_command, turn_off_command):
def __init__(
self, aftv, name, apps, get_sources, turn_on_command, turn_off_command
):
"""Initialize the Android TV device."""
super().__init__(aftv, name, apps, turn_on_command, turn_off_command)
super().__init__(
aftv, name, apps, get_sources, turn_on_command, turn_off_command
)
self._device = None
self._is_volume_muted = None
self._volume_level = None
@ -465,25 +498,28 @@ class AndroidTVDevice(ADBDevice):
(
state,
self._current_app,
self._device,
running_apps,
_,
self._is_volume_muted,
self._volume_level,
) = self.aftv.update()
) = self.aftv.update(self._get_sources)
self._state = ANDROIDTV_STATES.get(state)
if self._state is None:
self._available = False
if running_apps:
self._sources = [
self._app_id_to_name.get(app_id, app_id) for app_id in running_apps
]
else:
self._sources = None
@property
def is_volume_muted(self):
"""Boolean if volume is currently muted."""
return self._is_volume_muted
@property
def source(self):
"""Return the current playback device."""
return self._device
@property
def supported_features(self):
"""Flag media player features that are supported."""
@ -518,15 +554,6 @@ class AndroidTVDevice(ADBDevice):
class FireTVDevice(ADBDevice):
"""Representation of a Fire TV device."""
def __init__(
self, aftv, name, apps, get_sources, turn_on_command, turn_off_command
):
"""Initialize the Fire TV device."""
super().__init__(aftv, name, apps, turn_on_command, turn_off_command)
self._get_sources = get_sources
self._sources = None
@adb_decorator(override_available=True)
def update(self):
"""Update the device state and, if necessary, re-connect."""
@ -558,16 +585,6 @@ class FireTVDevice(ADBDevice):
else:
self._sources = None
@property
def source(self):
"""Return the current app."""
return self._app_id_to_name.get(self._current_app, self._current_app)
@property
def source_list(self):
"""Return a list of running apps."""
return self._sources
@property
def supported_features(self):
"""Flag media player features that are supported."""
@ -577,17 +594,3 @@ class FireTVDevice(ADBDevice):
def media_stop(self):
"""Send stop (back) command."""
self.aftv.back()
@adb_decorator()
def select_source(self, source):
"""Select input source.
If the source starts with a '!', then it will close the app instead of
opening it.
"""
if isinstance(source, str):
if not source.startswith("!"):
self.aftv.launch_app(self._app_name_to_id.get(source, source))
else:
source_ = source[1:].lstrip()
self.aftv.stop_app(self._app_name_to_id.get(source_, source_))

View File

@ -115,7 +115,7 @@ adafruit-blinka==1.2.1
adafruit-circuitpython-mcp230xx==1.1.2
# homeassistant.components.androidtv
adb-shell==0.0.8
adb-shell==0.0.9
# homeassistant.components.adguard
adguardhome==0.3.0
@ -215,7 +215,7 @@ ambiclimate==0.2.1
amcrest==1.5.3
# homeassistant.components.androidtv
androidtv==0.0.34
androidtv==0.0.35
# homeassistant.components.anel_pwrctrl
anel_pwrctrl-homeassistant==0.0.1.dev2

View File

@ -29,7 +29,7 @@ YesssSMS==0.4.1
abodepy==0.16.7
# homeassistant.components.androidtv
adb-shell==0.0.8
adb-shell==0.0.9
# homeassistant.components.adguard
adguardhome==0.3.0
@ -84,7 +84,7 @@ airly==0.0.2
ambiclimate==0.2.1
# homeassistant.components.androidtv
androidtv==0.0.34
androidtv==0.0.35
# homeassistant.components.apns
apns2==0.3.0

View File

@ -149,5 +149,22 @@ def patch_firetv_update(state, current_app, running_apps):
)
PATCH_LAUNCH_APP = patch("androidtv.firetv.FireTV.launch_app")
PATCH_STOP_APP = patch("androidtv.firetv.FireTV.stop_app")
def patch_androidtv_update(
state, current_app, running_apps, device, is_volume_muted, volume_level
):
"""Patch the `AndroidTV.update()` method."""
return patch(
"androidtv.androidtv.AndroidTV.update",
return_value=(
state,
current_app,
running_apps,
device,
is_volume_muted,
volume_level,
),
)
PATCH_LAUNCH_APP = patch("androidtv.basetv.BaseTV.launch_app")
PATCH_STOP_APP = patch("androidtv.basetv.BaseTV.stop_app")

View File

@ -33,6 +33,7 @@ CONFIG_ANDROIDTV_PYTHON_ADB = {
CONF_PLATFORM: ANDROIDTV_DOMAIN,
CONF_HOST: "127.0.0.1",
CONF_NAME: "Android TV",
CONF_DEVICE_CLASS: "androidtv",
}
}
@ -42,6 +43,7 @@ CONFIG_ANDROIDTV_ADB_SERVER = {
CONF_PLATFORM: ANDROIDTV_DOMAIN,
CONF_HOST: "127.0.0.1",
CONF_NAME: "Android TV",
CONF_DEVICE_CLASS: "androidtv",
CONF_ADB_SERVER_IP: "127.0.0.1",
}
}
@ -284,9 +286,9 @@ async def test_setup_with_adbkey(hass):
assert state.state == STATE_OFF
async def test_firetv_sources(hass):
"""Test that sources (i.e., apps) are handled correctly for Fire TV devices."""
config = CONFIG_FIRETV_ADB_SERVER.copy()
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"}
patch_key, entity_id = _setup(hass, config)
@ -299,9 +301,21 @@ async def test_firetv_sources(hass):
assert state is not None
assert state.state == STATE_OFF
with patchers.patch_firetv_update(
if config[DOMAIN].get(CONF_DEVICE_CLASS) != "firetv":
patch_update = patchers.patch_androidtv_update(
"playing",
"com.app.test1",
["com.app.test1", "com.app.test2"],
"hdmi",
False,
1,
)
else:
patch_update = patchers.patch_firetv_update(
"playing", "com.app.test1", ["com.app.test1", "com.app.test2"]
):
)
with patch_update:
await hass.helpers.entity_component.async_update_entity(entity_id)
state = hass.states.get(entity_id)
assert state is not None
@ -309,9 +323,21 @@ async def test_firetv_sources(hass):
assert state.attributes["source"] == "TEST 1"
assert state.attributes["source_list"] == ["TEST 1", "com.app.test2"]
with patchers.patch_firetv_update(
if config[DOMAIN].get(CONF_DEVICE_CLASS) != "firetv":
patch_update = patchers.patch_androidtv_update(
"playing",
"com.app.test2",
["com.app.test2", "com.app.test1"],
"hdmi",
True,
0,
)
else:
patch_update = patchers.patch_firetv_update(
"playing", "com.app.test2", ["com.app.test2", "com.app.test1"]
):
)
with patch_update:
await hass.helpers.entity_component.async_update_entity(entity_id)
state = hass.states.get(entity_id)
assert state is not None
@ -319,10 +345,22 @@ async def test_firetv_sources(hass):
assert state.attributes["source"] == "com.app.test2"
assert state.attributes["source_list"] == ["com.app.test2", "TEST 1"]
return True
async def _test_firetv_select_source(hass, source, expected_arg, method_patch):
"""Test that the `FireTV.launch_app` and `FireTV.stop_app` methods are called with the right parameter."""
config = CONFIG_FIRETV_ADB_SERVER.copy()
async def test_androidtv_sources(hass):
"""Test that sources (i.e., apps) are handled correctly for Android TV devices."""
assert await _test_sources(hass, CONFIG_ANDROIDTV_ADB_SERVER)
async def test_firetv_sources(hass):
"""Test that sources (i.e., apps) are handled correctly for Fire TV devices."""
assert await _test_sources(hass, CONFIG_FIRETV_ADB_SERVER)
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"}
patch_key, entity_id = _setup(hass, config)
@ -347,43 +385,133 @@ async def _test_firetv_select_source(hass, source, expected_arg, method_patch):
return True
async def test_androidtv_select_source_launch_app_id(hass):
"""Test that an app can be launched using its app ID."""
assert await _test_select_source(
hass,
CONFIG_ANDROIDTV_ADB_SERVER,
"com.app.test1",
"com.app.test1",
patchers.PATCH_LAUNCH_APP,
)
async def test_androidtv_select_source_launch_app_name(hass):
"""Test that an app can be launched using its friendly name."""
assert await _test_select_source(
hass,
CONFIG_ANDROIDTV_ADB_SERVER,
"TEST 1",
"com.app.test1",
patchers.PATCH_LAUNCH_APP,
)
async def test_androidtv_select_source_launch_app_id_no_name(hass):
"""Test that an app can be launched using its app ID when it has no friendly name."""
assert await _test_select_source(
hass,
CONFIG_ANDROIDTV_ADB_SERVER,
"com.app.test2",
"com.app.test2",
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(
hass,
CONFIG_ANDROIDTV_ADB_SERVER,
"!com.app.test1",
"com.app.test1",
patchers.PATCH_STOP_APP,
)
async def test_androidtv_select_source_stop_app_name(hass):
"""Test that an app can be stopped using its friendly name."""
assert await _test_select_source(
hass,
CONFIG_ANDROIDTV_ADB_SERVER,
"!TEST 1",
"com.app.test1",
patchers.PATCH_STOP_APP,
)
async def test_androidtv_select_source_stop_app_id_no_name(hass):
"""Test that an app can be stopped using its app ID when it has no friendly name."""
assert await _test_select_source(
hass,
CONFIG_ANDROIDTV_ADB_SERVER,
"!com.app.test2",
"com.app.test2",
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_firetv_select_source(
hass, "com.app.test1", "com.app.test1", patchers.PATCH_LAUNCH_APP
assert await _test_select_source(
hass,
CONFIG_FIRETV_ADB_SERVER,
"com.app.test1",
"com.app.test1",
patchers.PATCH_LAUNCH_APP,
)
async def test_firetv_select_source_launch_app_name(hass):
"""Test that an app can be launched using its friendly name."""
assert await _test_firetv_select_source(
hass, "TEST 1", "com.app.test1", patchers.PATCH_LAUNCH_APP
assert await _test_select_source(
hass,
CONFIG_FIRETV_ADB_SERVER,
"TEST 1",
"com.app.test1",
patchers.PATCH_LAUNCH_APP,
)
async def test_firetv_select_source_launch_app_id_no_name(hass):
"""Test that an app can be launched using its app ID when it has no friendly name."""
assert await _test_firetv_select_source(
hass, "com.app.test2", "com.app.test2", patchers.PATCH_LAUNCH_APP
assert await _test_select_source(
hass,
CONFIG_FIRETV_ADB_SERVER,
"com.app.test2",
"com.app.test2",
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_firetv_select_source(
hass, "!com.app.test1", "com.app.test1", patchers.PATCH_STOP_APP
assert await _test_select_source(
hass,
CONFIG_FIRETV_ADB_SERVER,
"!com.app.test1",
"com.app.test1",
patchers.PATCH_STOP_APP,
)
async def test_firetv_select_source_stop_app_name(hass):
"""Test that an app can be stopped using its friendly name."""
assert await _test_firetv_select_source(
hass, "!TEST 1", "com.app.test1", patchers.PATCH_STOP_APP
assert await _test_select_source(
hass,
CONFIG_FIRETV_ADB_SERVER,
"!TEST 1",
"com.app.test1",
patchers.PATCH_STOP_APP,
)
async def test_firetv_select_source_stop_app_id_no_name(hass):
"""Test that an app can be stopped using its app ID when it has no friendly name."""
assert await _test_firetv_select_source(
hass, "!com.app.test2", "com.app.test2", patchers.PATCH_STOP_APP
assert await _test_select_source(
hass,
CONFIG_FIRETV_ADB_SERVER,
"!com.app.test2",
"com.app.test2",
patchers.PATCH_STOP_APP,
)