mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 09:47:13 +00:00
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:
parent
3db7e8f5e9
commit
003658a3f0
@ -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": [],
|
||||
|
@ -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_))
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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,
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user