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", "name": "Androidtv",
"documentation": "https://www.home-assistant.io/integrations/androidtv", "documentation": "https://www.home-assistant.io/integrations/androidtv",
"requirements": [ "requirements": [
"adb-shell==0.0.8", "adb-shell==0.0.9",
"androidtv==0.0.34", "androidtv==0.0.35",
"pure-python-adb==0.2.2.dev0" "pure-python-adb==0.2.2.dev0"
], ],
"dependencies": [], "dependencies": [],

View File

@ -55,6 +55,7 @@ SUPPORT_ANDROIDTV = (
| SUPPORT_TURN_OFF | SUPPORT_TURN_OFF
| SUPPORT_PREVIOUS_TRACK | SUPPORT_PREVIOUS_TRACK
| SUPPORT_NEXT_TRACK | SUPPORT_NEXT_TRACK
| SUPPORT_SELECT_SOURCE
| SUPPORT_STOP | SUPPORT_STOP
| SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_MUTE
| SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_STEP
@ -199,6 +200,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
aftv, aftv,
config[CONF_NAME], config[CONF_NAME],
config[CONF_APPS], config[CONF_APPS],
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),
) )
@ -287,7 +289,9 @@ def adb_decorator(override_available=False):
class ADBDevice(MediaPlayerDevice): class ADBDevice(MediaPlayerDevice):
"""Representation of an Android TV or Fire TV device.""" """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.""" """Initialize the Android TV / Fire TV device."""
self.aftv = aftv self.aftv = aftv
self._name = name self._name = name
@ -296,6 +300,7 @@ class ADBDevice(MediaPlayerDevice):
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()
} }
self._get_sources = get_sources
self._keys = KEYS self._keys = KEYS
self._device_properties = self.aftv.device_properties self._device_properties = self.aftv.device_properties
@ -325,6 +330,7 @@ class ADBDevice(MediaPlayerDevice):
self._adb_response = None self._adb_response = None
self._available = True self._available = True
self._current_app = None self._current_app = None
self._sources = None
self._state = None self._state = None
@property @property
@ -357,6 +363,16 @@ class ADBDevice(MediaPlayerDevice):
"""Device should be polled.""" """Device should be polled."""
return True 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 @property
def state(self): def state(self):
"""Return the state of the player.""" """Return the state of the player."""
@ -408,6 +424,20 @@ class ADBDevice(MediaPlayerDevice):
"""Send next track command (results in fast-forward).""" """Send next track command (results in fast-forward)."""
self.aftv.media_next_track() 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() @adb_decorator()
def adb_command(self, cmd): def adb_command(self, cmd):
"""Send an ADB command to an Android TV / Fire TV device.""" """Send an ADB command to an Android TV / Fire TV device."""
@ -436,11 +466,14 @@ class ADBDevice(MediaPlayerDevice):
class AndroidTVDevice(ADBDevice): class AndroidTVDevice(ADBDevice):
"""Representation of an Android TV device.""" """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.""" """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._is_volume_muted = None
self._volume_level = None self._volume_level = None
@ -465,25 +498,28 @@ class AndroidTVDevice(ADBDevice):
( (
state, state,
self._current_app, self._current_app,
self._device, running_apps,
_,
self._is_volume_muted, self._is_volume_muted,
self._volume_level, self._volume_level,
) = self.aftv.update() ) = self.aftv.update(self._get_sources)
self._state = ANDROIDTV_STATES.get(state) self._state = ANDROIDTV_STATES.get(state)
if self._state is None: if self._state is None:
self._available = False 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 @property
def is_volume_muted(self): def is_volume_muted(self):
"""Boolean if volume is currently muted.""" """Boolean if volume is currently muted."""
return self._is_volume_muted return self._is_volume_muted
@property
def source(self):
"""Return the current playback device."""
return self._device
@property @property
def supported_features(self): def supported_features(self):
"""Flag media player features that are supported.""" """Flag media player features that are supported."""
@ -518,15 +554,6 @@ class AndroidTVDevice(ADBDevice):
class FireTVDevice(ADBDevice): class FireTVDevice(ADBDevice):
"""Representation of a Fire TV device.""" """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) @adb_decorator(override_available=True)
def update(self): def update(self):
"""Update the device state and, if necessary, re-connect.""" """Update the device state and, if necessary, re-connect."""
@ -558,16 +585,6 @@ class FireTVDevice(ADBDevice):
else: else:
self._sources = None 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 @property
def supported_features(self): def supported_features(self):
"""Flag media player features that are supported.""" """Flag media player features that are supported."""
@ -577,17 +594,3 @@ class FireTVDevice(ADBDevice):
def media_stop(self): def media_stop(self):
"""Send stop (back) command.""" """Send stop (back) command."""
self.aftv.back() 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 adafruit-circuitpython-mcp230xx==1.1.2
# homeassistant.components.androidtv # homeassistant.components.androidtv
adb-shell==0.0.8 adb-shell==0.0.9
# homeassistant.components.adguard # homeassistant.components.adguard
adguardhome==0.3.0 adguardhome==0.3.0
@ -215,7 +215,7 @@ ambiclimate==0.2.1
amcrest==1.5.3 amcrest==1.5.3
# homeassistant.components.androidtv # homeassistant.components.androidtv
androidtv==0.0.34 androidtv==0.0.35
# homeassistant.components.anel_pwrctrl # homeassistant.components.anel_pwrctrl
anel_pwrctrl-homeassistant==0.0.1.dev2 anel_pwrctrl-homeassistant==0.0.1.dev2

View File

@ -29,7 +29,7 @@ YesssSMS==0.4.1
abodepy==0.16.7 abodepy==0.16.7
# homeassistant.components.androidtv # homeassistant.components.androidtv
adb-shell==0.0.8 adb-shell==0.0.9
# homeassistant.components.adguard # homeassistant.components.adguard
adguardhome==0.3.0 adguardhome==0.3.0
@ -84,7 +84,7 @@ airly==0.0.2
ambiclimate==0.2.1 ambiclimate==0.2.1
# homeassistant.components.androidtv # homeassistant.components.androidtv
androidtv==0.0.34 androidtv==0.0.35
# homeassistant.components.apns # homeassistant.components.apns
apns2==0.3.0 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") def patch_androidtv_update(
PATCH_STOP_APP = patch("androidtv.firetv.FireTV.stop_app") 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_PLATFORM: ANDROIDTV_DOMAIN,
CONF_HOST: "127.0.0.1", CONF_HOST: "127.0.0.1",
CONF_NAME: "Android TV", CONF_NAME: "Android TV",
CONF_DEVICE_CLASS: "androidtv",
} }
} }
@ -42,6 +43,7 @@ CONFIG_ANDROIDTV_ADB_SERVER = {
CONF_PLATFORM: ANDROIDTV_DOMAIN, CONF_PLATFORM: ANDROIDTV_DOMAIN,
CONF_HOST: "127.0.0.1", CONF_HOST: "127.0.0.1",
CONF_NAME: "Android TV", CONF_NAME: "Android TV",
CONF_DEVICE_CLASS: "androidtv",
CONF_ADB_SERVER_IP: "127.0.0.1", CONF_ADB_SERVER_IP: "127.0.0.1",
} }
} }
@ -284,9 +286,9 @@ async def test_setup_with_adbkey(hass):
assert state.state == STATE_OFF assert state.state == STATE_OFF
async def test_firetv_sources(hass): async def _test_sources(hass, config0):
"""Test that sources (i.e., apps) are handled correctly for Fire TV devices.""" """Test that sources (i.e., apps) are handled correctly for Android TV and Fire TV devices."""
config = CONFIG_FIRETV_ADB_SERVER.copy() config = config0.copy()
config[DOMAIN][CONF_APPS] = {"com.app.test1": "TEST 1"} config[DOMAIN][CONF_APPS] = {"com.app.test1": "TEST 1"}
patch_key, entity_id = _setup(hass, config) patch_key, entity_id = _setup(hass, config)
@ -299,9 +301,21 @@ async def test_firetv_sources(hass):
assert state is not None assert state is not None
assert state.state == STATE_OFF assert state.state == STATE_OFF
with patchers.patch_firetv_update( if config[DOMAIN].get(CONF_DEVICE_CLASS) != "firetv":
"playing", "com.app.test1", ["com.app.test1", "com.app.test2"] 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) await hass.helpers.entity_component.async_update_entity(entity_id)
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
assert state is not None 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"] == "TEST 1"
assert state.attributes["source_list"] == ["TEST 1", "com.app.test2"] assert state.attributes["source_list"] == ["TEST 1", "com.app.test2"]
with patchers.patch_firetv_update( if config[DOMAIN].get(CONF_DEVICE_CLASS) != "firetv":
"playing", "com.app.test2", ["com.app.test2", "com.app.test1"] 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) await hass.helpers.entity_component.async_update_entity(entity_id)
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
assert state is not None 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"] == "com.app.test2"
assert state.attributes["source_list"] == ["com.app.test2", "TEST 1"] 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.""" async def test_androidtv_sources(hass):
config = CONFIG_FIRETV_ADB_SERVER.copy() """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"} config[DOMAIN][CONF_APPS] = {"com.app.test1": "TEST 1"}
patch_key, entity_id = _setup(hass, config) 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 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): 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_firetv_select_source( assert await _test_select_source(
hass, "com.app.test1", "com.app.test1", patchers.PATCH_LAUNCH_APP 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): async def test_firetv_select_source_launch_app_name(hass):
"""Test that an app can be launched using its friendly name.""" """Test that an app can be launched using its friendly name."""
assert await _test_firetv_select_source( assert await _test_select_source(
hass, "TEST 1", "com.app.test1", patchers.PATCH_LAUNCH_APP 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): 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.""" """Test that an app can be launched using its app ID when it has no friendly name."""
assert await _test_firetv_select_source( assert await _test_select_source(
hass, "com.app.test2", "com.app.test2", patchers.PATCH_LAUNCH_APP 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): 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_firetv_select_source( assert await _test_select_source(
hass, "!com.app.test1", "com.app.test1", patchers.PATCH_STOP_APP 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): async def test_firetv_select_source_stop_app_name(hass):
"""Test that an app can be stopped using its friendly name.""" """Test that an app can be stopped using its friendly name."""
assert await _test_firetv_select_source( assert await _test_select_source(
hass, "!TEST 1", "com.app.test1", patchers.PATCH_STOP_APP 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): 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.""" """Test that an app can be stopped using its app ID when it has no friendly name."""
assert await _test_firetv_select_source( assert await _test_select_source(
hass, "!com.app.test2", "com.app.test2", patchers.PATCH_STOP_APP hass,
CONFIG_FIRETV_ADB_SERVER,
"!com.app.test2",
"com.app.test2",
patchers.PATCH_STOP_APP,
) )