Generate ADB key for Android TV integration (#27344)

* Generate ADB key for Android TV integration

* Remove 'do_nothing' function

* Remove 'return True'

* Re-add 2 'return True' lines
This commit is contained in:
Jeff Irion 2019-10-17 06:33:20 -07:00 committed by Martin Hjelmare
parent 2c535c92bd
commit 28cef89e03
6 changed files with 96 additions and 45 deletions

View File

@ -3,8 +3,8 @@
"name": "Androidtv",
"documentation": "https://www.home-assistant.io/integrations/androidtv",
"requirements": [
"adb-shell==0.0.4",
"androidtv==0.0.30"
"adb-shell==0.0.7",
"androidtv==0.0.32"
],
"dependencies": [],
"codeowners": ["@JeffLIrion"]

View File

@ -1,8 +1,10 @@
"""Support for functionality to interact with Android TV / Fire TV devices."""
import functools
import logging
import os
import voluptuous as vol
from adb_shell.auth.keygen import keygen
from adb_shell.exceptions import (
InvalidChecksumError,
InvalidCommandError,
@ -40,6 +42,7 @@ from homeassistant.const import (
)
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.storage import STORAGE_DIR
ANDROIDTV_DOMAIN = "androidtv"
@ -133,27 +136,39 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
if CONF_ADB_SERVER_IP not in config:
# Use "adb_shell" (Python ADB implementation)
adb_log = "using Python ADB implementation " + (
f"with adbkey='{config[CONF_ADBKEY]}'"
if CONF_ADBKEY in config
else "without adbkey authentication"
)
if CONF_ADBKEY in config:
if CONF_ADBKEY not in config:
# Generate ADB key files (if they don't exist)
adbkey = hass.config.path(STORAGE_DIR, "androidtv_adbkey")
if not os.path.isfile(adbkey):
keygen(adbkey)
adb_log = f"using Python ADB implementation with adbkey='{adbkey}'"
aftv = setup(
host,
adbkey,
device_class=config[CONF_DEVICE_CLASS],
state_detection_rules=config[CONF_STATE_DETECTION_RULES],
auth_timeout_s=10.0,
)
else:
adb_log = (
f"using Python ADB implementation with adbkey='{config[CONF_ADBKEY]}'"
)
aftv = setup(
host,
config[CONF_ADBKEY],
device_class=config[CONF_DEVICE_CLASS],
state_detection_rules=config[CONF_STATE_DETECTION_RULES],
auth_timeout_s=10.0,
)
else:
aftv = setup(
host,
device_class=config[CONF_DEVICE_CLASS],
state_detection_rules=config[CONF_STATE_DETECTION_RULES],
)
else:
# Use "pure-python-adb" (communicate with ADB server)
adb_log = f"using ADB server at {config[CONF_ADB_SERVER_IP]}:{config[CONF_ADB_SERVER_PORT]}"
aftv = setup(
host,
adb_server_ip=config[CONF_ADB_SERVER_IP],
@ -161,7 +176,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
device_class=config[CONF_DEVICE_CLASS],
state_detection_rules=config[CONF_STATE_DETECTION_RULES],
)
adb_log = f"using ADB server at {config[CONF_ADB_SERVER_IP]}:{config[CONF_ADB_SERVER_PORT]}"
if not aftv.available:
# Determine the name that will be used for the device in the log
@ -257,7 +271,7 @@ def adb_decorator(override_available=False):
"establishing attempt in the next update. Error: %s",
err,
)
self.aftv.adb.close()
self.aftv.adb_close()
self._available = False # pylint: disable=protected-access
return None
@ -429,7 +443,7 @@ class AndroidTVDevice(ADBDevice):
# Check if device is disconnected.
if not self._available:
# Try to connect
self._available = self.aftv.connect(always_log_errors=False)
self._available = self.aftv.adb_connect(always_log_errors=False)
# To be safe, wait until the next update to run ADB commands if
# using the Python ADB implementation.
@ -508,7 +522,7 @@ class FireTVDevice(ADBDevice):
# Check if device is disconnected.
if not self._available:
# Try to connect
self._available = self.aftv.connect(always_log_errors=False)
self._available = self.aftv.adb_connect(always_log_errors=False)
# To be safe, wait until the next update to run ADB commands if
# using the Python ADB implementation.

View File

@ -112,7 +112,7 @@ adafruit-blinka==1.2.1
adafruit-circuitpython-mcp230xx==1.1.2
# homeassistant.components.androidtv
adb-shell==0.0.4
adb-shell==0.0.7
# homeassistant.components.adguard
adguardhome==0.2.1
@ -203,7 +203,7 @@ ambiclimate==0.2.1
amcrest==1.5.3
# homeassistant.components.androidtv
androidtv==0.0.30
androidtv==0.0.32
# homeassistant.components.anel_pwrctrl
anel_pwrctrl-homeassistant==0.0.1.dev2

View File

@ -49,7 +49,7 @@ YesssSMS==0.4.1
abodepy==0.16.5
# homeassistant.components.androidtv
adb-shell==0.0.4
adb-shell==0.0.7
# homeassistant.components.adguard
adguardhome==0.2.1
@ -98,7 +98,7 @@ airly==0.0.2
ambiclimate==0.2.1
# homeassistant.components.androidtv
androidtv==0.0.30
androidtv==0.0.32
# homeassistant.components.apns
apns2==0.3.0

View File

@ -1,7 +1,7 @@
"""Define patches used for androidtv tests."""
from socket import error as socket_error
from unittest.mock import patch
from unittest.mock import mock_open, patch
class AdbDeviceFake:
@ -128,3 +128,15 @@ def patch_shell(response=None, error=False):
PATCH_ADB_DEVICE = patch("androidtv.adb_manager.AdbDevice", AdbDeviceFake)
PATCH_ANDROIDTV_OPEN = patch("androidtv.adb_manager.open", mock_open())
PATCH_KEYGEN = patch("homeassistant.components.androidtv.media_player.keygen")
PATCH_SIGNER = patch("androidtv.adb_manager.PythonRSASigner")
def isfile(filepath):
"""Mock `os.path.isfile`."""
return filepath.endswith("adbkey")
PATCH_ISFILE = patch("os.path.isfile", isfile)
PATCH_ACCESS = patch("os.access", return_value=True)

View File

@ -5,6 +5,7 @@ from homeassistant.setup import async_setup_component
from homeassistant.components.androidtv.media_player import (
ANDROIDTV_DOMAIN,
CONF_ADB_SERVER_IP,
CONF_ADBKEY,
)
from homeassistant.components.media_player.const import DOMAIN
from homeassistant.const import (
@ -61,14 +62,8 @@ CONFIG_FIRETV_ADB_SERVER = {
}
async def _test_reconnect(hass, caplog, config):
"""Test that the error and reconnection attempts are logged correctly.
"Handles device/service unavailable. Log a warning once when
unavailable, log once when reconnected."
https://developers.home-assistant.io/docs/en/integration_quality_scale_index.html
"""
def _setup(hass, config):
"""Perform common setup tasks for the tests."""
if CONF_ADB_SERVER_IP not in config[DOMAIN]:
patch_key = "python"
else:
@ -79,10 +74,26 @@ async def _test_reconnect(hass, caplog, config):
else:
entity_id = "media_player.fire_tv"
return patch_key, entity_id
async def _test_reconnect(hass, caplog, config):
"""Test that the error and reconnection attempts are logged correctly.
"Handles device/service unavailable. Log a warning once when
unavailable, log once when reconnected."
https://developers.home-assistant.io/docs/en/integration_quality_scale_index.html
"""
patch_key, entity_id = _setup(hass, config)
with patchers.PATCH_ADB_DEVICE, patchers.patch_connect(True)[
patch_key
], patchers.patch_shell("")[patch_key]:
], patchers.patch_shell("")[
patch_key
], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER:
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
@ -93,7 +104,7 @@ async def _test_reconnect(hass, caplog, config):
with patchers.patch_connect(False)[patch_key], patchers.patch_shell(error=True)[
patch_key
]:
], patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER:
for _ in range(5):
await hass.helpers.entity_component.async_update_entity(entity_id)
state = hass.states.get(entity_id)
@ -105,7 +116,9 @@ async def _test_reconnect(hass, caplog, config):
assert caplog.record_tuples[1][1] == logging.WARNING
caplog.set_level(logging.DEBUG)
with patchers.patch_connect(True)[patch_key], patchers.patch_shell("1")[patch_key]:
with patchers.patch_connect(True)[patch_key], patchers.patch_shell("1")[
patch_key
], patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER:
# Update 1 will reconnect
await hass.helpers.entity_component.async_update_entity(entity_id)
@ -143,19 +156,13 @@ async def _test_adb_shell_returns_none(hass, config):
The state should be `None` and the device should be unavailable.
"""
if CONF_ADB_SERVER_IP not in config[DOMAIN]:
patch_key = "python"
else:
patch_key = "server"
if config[DOMAIN].get(CONF_DEVICE_CLASS) != "firetv":
entity_id = "media_player.android_tv"
else:
entity_id = "media_player.fire_tv"
patch_key, entity_id = _setup(hass, config)
with patchers.PATCH_ADB_DEVICE, patchers.patch_connect(True)[
patch_key
], patchers.patch_shell("")[patch_key]:
], patchers.patch_shell("")[
patch_key
], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER:
assert await async_setup_component(hass, DOMAIN, config)
await hass.helpers.entity_component.async_update_entity(entity_id)
state = hass.states.get(entity_id)
@ -164,7 +171,7 @@ async def _test_adb_shell_returns_none(hass, config):
with patchers.patch_shell(None)[patch_key], patchers.patch_shell(error=True)[
patch_key
]:
], patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER:
await hass.helpers.entity_component.async_update_entity(entity_id)
state = hass.states.get(entity_id)
assert state is not None
@ -251,3 +258,21 @@ async def test_adb_shell_returns_none_firetv_adb_server(hass):
"""
assert await _test_adb_shell_returns_none(hass, CONFIG_FIRETV_ADB_SERVER)
async def test_setup_with_adbkey(hass):
"""Test that setup succeeds when using an ADB key."""
config = CONFIG_ANDROIDTV_PYTHON_ADB.copy()
config[DOMAIN][CONF_ADBKEY] = hass.config.path("user_provided_adbkey")
patch_key, entity_id = _setup(hass, config)
with patchers.PATCH_ADB_DEVICE, patchers.patch_connect(True)[
patch_key
], patchers.patch_shell("")[
patch_key
], patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER, patchers.PATCH_ISFILE, patchers.PATCH_ACCESS:
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