2019-04-03 15:40:03 +00:00
|
|
|
"""Support for functionality to interact with Android TV / Fire TV devices."""
|
2020-03-26 18:31:23 +00:00
|
|
|
from datetime import datetime
|
2018-11-19 06:05:58 +00:00
|
|
|
import functools
|
2015-10-10 20:45:13 +00:00
|
|
|
import logging
|
2019-10-17 13:33:20 +00:00
|
|
|
import os
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2019-10-17 13:33:20 +00:00
|
|
|
from adb_shell.auth.keygen import keygen
|
2019-09-27 05:53:26 +00:00
|
|
|
from adb_shell.exceptions import (
|
2020-07-05 19:13:08 +00:00
|
|
|
AdbTimeoutError,
|
2019-09-27 05:53:26 +00:00
|
|
|
InvalidChecksumError,
|
|
|
|
InvalidCommandError,
|
|
|
|
InvalidResponseError,
|
|
|
|
TcpTimeoutException,
|
|
|
|
)
|
2020-07-05 19:13:08 +00:00
|
|
|
from androidtv import ha_state_detection_rules_validator
|
2020-08-02 14:08:12 +00:00
|
|
|
from androidtv.adb_manager.adb_manager_sync import ADBPythonSync
|
2019-08-05 16:58:42 +00:00
|
|
|
from androidtv.constants import APPS, KEYS
|
2020-01-06 22:10:13 +00:00
|
|
|
from androidtv.exceptions import LockNotAcquiredException
|
2020-07-05 19:13:08 +00:00
|
|
|
from androidtv.setup_async import setup
|
2019-12-09 12:57:24 +00:00
|
|
|
import voluptuous as vol
|
2019-08-05 16:58:42 +00:00
|
|
|
|
2020-04-25 16:00:57 +00:00
|
|
|
from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity
|
2019-02-08 22:18:18 +00:00
|
|
|
from homeassistant.components.media_player.const import (
|
2019-07-31 19:25:30 +00:00
|
|
|
SUPPORT_NEXT_TRACK,
|
|
|
|
SUPPORT_PAUSE,
|
|
|
|
SUPPORT_PLAY,
|
|
|
|
SUPPORT_PREVIOUS_TRACK,
|
|
|
|
SUPPORT_SELECT_SOURCE,
|
|
|
|
SUPPORT_STOP,
|
|
|
|
SUPPORT_TURN_OFF,
|
|
|
|
SUPPORT_TURN_ON,
|
|
|
|
SUPPORT_VOLUME_MUTE,
|
2020-01-26 09:39:19 +00:00
|
|
|
SUPPORT_VOLUME_SET,
|
2019-07-31 19:25:30 +00:00
|
|
|
SUPPORT_VOLUME_STEP,
|
|
|
|
)
|
2016-02-19 05:27:50 +00:00
|
|
|
from homeassistant.const import (
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_COMMAND,
|
|
|
|
ATTR_ENTITY_ID,
|
|
|
|
CONF_DEVICE_CLASS,
|
|
|
|
CONF_HOST,
|
|
|
|
CONF_NAME,
|
|
|
|
CONF_PORT,
|
2020-07-20 00:48:08 +00:00
|
|
|
EVENT_HOMEASSISTANT_STOP,
|
2019-07-31 19:25:30 +00:00
|
|
|
STATE_IDLE,
|
|
|
|
STATE_OFF,
|
|
|
|
STATE_PAUSED,
|
|
|
|
STATE_PLAYING,
|
|
|
|
STATE_STANDBY,
|
|
|
|
)
|
2019-03-29 15:08:36 +00:00
|
|
|
from homeassistant.exceptions import PlatformNotReady
|
2020-06-30 01:17:04 +00:00
|
|
|
from homeassistant.helpers import config_validation as cv, entity_platform
|
2019-10-17 13:33:20 +00:00
|
|
|
from homeassistant.helpers.storage import STORAGE_DIR
|
2016-09-05 15:40:57 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
ANDROIDTV_DOMAIN = "androidtv"
|
2019-02-28 11:29:56 +00:00
|
|
|
|
2016-09-05 15:40:57 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
SUPPORT_ANDROIDTV = (
|
|
|
|
SUPPORT_PAUSE
|
|
|
|
| SUPPORT_PLAY
|
|
|
|
| SUPPORT_TURN_ON
|
|
|
|
| SUPPORT_TURN_OFF
|
|
|
|
| SUPPORT_PREVIOUS_TRACK
|
|
|
|
| SUPPORT_NEXT_TRACK
|
2019-12-14 15:54:41 +00:00
|
|
|
| SUPPORT_SELECT_SOURCE
|
2019-07-31 19:25:30 +00:00
|
|
|
| SUPPORT_STOP
|
|
|
|
| SUPPORT_VOLUME_MUTE
|
2020-01-26 09:39:19 +00:00
|
|
|
| SUPPORT_VOLUME_SET
|
2019-07-31 19:25:30 +00:00
|
|
|
| SUPPORT_VOLUME_STEP
|
|
|
|
)
|
|
|
|
|
|
|
|
SUPPORT_FIRETV = (
|
|
|
|
SUPPORT_PAUSE
|
|
|
|
| SUPPORT_PLAY
|
|
|
|
| SUPPORT_TURN_ON
|
|
|
|
| SUPPORT_TURN_OFF
|
|
|
|
| SUPPORT_PREVIOUS_TRACK
|
|
|
|
| SUPPORT_NEXT_TRACK
|
|
|
|
| SUPPORT_SELECT_SOURCE
|
|
|
|
| SUPPORT_STOP
|
|
|
|
)
|
|
|
|
|
2020-01-06 22:10:13 +00:00
|
|
|
ATTR_DEVICE_PATH = "device_path"
|
|
|
|
ATTR_LOCAL_PATH = "local_path"
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_ADBKEY = "adbkey"
|
|
|
|
CONF_ADB_SERVER_IP = "adb_server_ip"
|
|
|
|
CONF_ADB_SERVER_PORT = "adb_server_port"
|
|
|
|
CONF_APPS = "apps"
|
2020-01-29 20:13:09 +00:00
|
|
|
CONF_EXCLUDE_UNNAMED_APPS = "exclude_unnamed_apps"
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_GET_SOURCES = "get_sources"
|
2019-08-05 16:58:42 +00:00
|
|
|
CONF_STATE_DETECTION_RULES = "state_detection_rules"
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_TURN_ON_COMMAND = "turn_on_command"
|
|
|
|
CONF_TURN_OFF_COMMAND = "turn_off_command"
|
2020-04-14 16:41:19 +00:00
|
|
|
CONF_SCREENCAP = "screencap"
|
2019-07-31 19:25:30 +00:00
|
|
|
|
|
|
|
DEFAULT_NAME = "Android TV"
|
2018-11-19 06:05:58 +00:00
|
|
|
DEFAULT_PORT = 5555
|
2019-02-24 23:16:49 +00:00
|
|
|
DEFAULT_ADB_SERVER_PORT = 5037
|
2018-11-19 06:05:58 +00:00
|
|
|
DEFAULT_GET_SOURCES = True
|
2019-07-31 19:25:30 +00:00
|
|
|
DEFAULT_DEVICE_CLASS = "auto"
|
2020-04-14 16:41:19 +00:00
|
|
|
DEFAULT_SCREENCAP = True
|
2019-03-13 10:18:59 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
DEVICE_ANDROIDTV = "androidtv"
|
|
|
|
DEVICE_FIRETV = "firetv"
|
2019-03-13 10:18:59 +00:00
|
|
|
DEVICE_CLASSES = [DEFAULT_DEVICE_CLASS, DEVICE_ANDROIDTV, DEVICE_FIRETV]
|
2018-11-19 06:05:58 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
SERVICE_ADB_COMMAND = "adb_command"
|
2020-01-06 22:10:13 +00:00
|
|
|
SERVICE_DOWNLOAD = "download"
|
2020-06-30 01:17:04 +00:00
|
|
|
SERVICE_LEARN_SENDEVENT = "learn_sendevent"
|
2020-01-06 22:10:13 +00:00
|
|
|
SERVICE_UPLOAD = "upload"
|
2019-07-31 19:25:30 +00:00
|
|
|
|
|
|
|
SERVICE_ADB_COMMAND_SCHEMA = vol.Schema(
|
|
|
|
{vol.Required(ATTR_ENTITY_ID): cv.entity_ids, vol.Required(ATTR_COMMAND): cv.string}
|
|
|
|
)
|
|
|
|
|
2020-01-06 22:10:13 +00:00
|
|
|
SERVICE_DOWNLOAD_SCHEMA = vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
|
|
|
|
vol.Required(ATTR_DEVICE_PATH): cv.string,
|
|
|
|
vol.Required(ATTR_LOCAL_PATH): cv.string,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
SERVICE_UPLOAD_SCHEMA = vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
|
|
|
|
vol.Required(ATTR_DEVICE_PATH): cv.string,
|
|
|
|
vol.Required(ATTR_LOCAL_PATH): cv.string,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
|
|
|
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|
|
|
{
|
|
|
|
vol.Required(CONF_HOST): cv.string,
|
|
|
|
vol.Optional(CONF_DEVICE_CLASS, default=DEFAULT_DEVICE_CLASS): vol.In(
|
|
|
|
DEVICE_CLASSES
|
|
|
|
),
|
|
|
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
|
|
|
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
|
|
|
vol.Optional(CONF_ADBKEY): cv.isfile,
|
|
|
|
vol.Optional(CONF_ADB_SERVER_IP): cv.string,
|
|
|
|
vol.Optional(CONF_ADB_SERVER_PORT, default=DEFAULT_ADB_SERVER_PORT): cv.port,
|
|
|
|
vol.Optional(CONF_GET_SOURCES, default=DEFAULT_GET_SOURCES): cv.boolean,
|
2020-04-04 20:31:56 +00:00
|
|
|
vol.Optional(CONF_APPS, default={}): vol.Schema(
|
2020-01-29 20:13:09 +00:00
|
|
|
{cv.string: vol.Any(cv.string, None)}
|
|
|
|
),
|
2019-07-31 19:25:30 +00:00
|
|
|
vol.Optional(CONF_TURN_ON_COMMAND): cv.string,
|
|
|
|
vol.Optional(CONF_TURN_OFF_COMMAND): cv.string,
|
2019-08-05 16:58:42 +00:00
|
|
|
vol.Optional(CONF_STATE_DETECTION_RULES, default={}): vol.Schema(
|
|
|
|
{cv.string: ha_state_detection_rules_validator(vol.Invalid)}
|
|
|
|
),
|
2020-01-29 20:13:09 +00:00
|
|
|
vol.Optional(CONF_EXCLUDE_UNNAMED_APPS, default=False): cv.boolean,
|
2020-04-14 16:41:19 +00:00
|
|
|
vol.Optional(CONF_SCREENCAP, default=DEFAULT_SCREENCAP): cv.boolean,
|
2019-07-31 19:25:30 +00:00
|
|
|
}
|
|
|
|
)
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
# Translate from `AndroidTV` / `FireTV` reported state to HA state.
|
2019-07-31 19:25:30 +00:00
|
|
|
ANDROIDTV_STATES = {
|
|
|
|
"off": STATE_OFF,
|
|
|
|
"idle": STATE_IDLE,
|
|
|
|
"standby": STATE_STANDBY,
|
|
|
|
"playing": STATE_PLAYING,
|
|
|
|
"paused": STATE_PAUSED,
|
|
|
|
}
|
2018-11-19 06:05:58 +00:00
|
|
|
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2020-06-30 01:17:04 +00:00
|
|
|
def setup_androidtv(hass, config):
|
2020-07-05 19:13:08 +00:00
|
|
|
"""Generate an ADB key (if needed) and load it."""
|
2020-06-30 01:17:04 +00:00
|
|
|
adbkey = config.get(CONF_ADBKEY, hass.config.path(STORAGE_DIR, "androidtv_adbkey"))
|
|
|
|
if CONF_ADB_SERVER_IP not in config:
|
|
|
|
# Use "adb_shell" (Python ADB implementation)
|
|
|
|
if not os.path.isfile(adbkey):
|
|
|
|
# Generate ADB key files
|
|
|
|
keygen(adbkey)
|
|
|
|
|
2020-07-05 19:13:08 +00:00
|
|
|
# Load the ADB key
|
2020-08-02 14:08:12 +00:00
|
|
|
signer = ADBPythonSync.load_adbkey(adbkey)
|
2020-06-30 01:17:04 +00:00
|
|
|
adb_log = f"using Python ADB implementation with adbkey='{adbkey}'"
|
|
|
|
|
|
|
|
else:
|
|
|
|
# Use "pure-python-adb" (communicate with ADB server)
|
2020-07-05 19:13:08 +00:00
|
|
|
signer = None
|
2020-06-30 01:17:04 +00:00
|
|
|
adb_log = f"using ADB server at {config[CONF_ADB_SERVER_IP]}:{config[CONF_ADB_SERVER_PORT]}"
|
|
|
|
|
2020-07-05 19:13:08 +00:00
|
|
|
return adbkey, signer, adb_log
|
2020-06-30 01:17:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Set up the Android TV / Fire TV platform."""
|
|
|
|
hass.data.setdefault(ANDROIDTV_DOMAIN, {})
|
2019-02-28 11:29:56 +00:00
|
|
|
|
2020-01-06 22:10:13 +00:00
|
|
|
address = f"{config[CONF_HOST]}:{config[CONF_PORT]}"
|
|
|
|
|
|
|
|
if address in hass.data[ANDROIDTV_DOMAIN]:
|
|
|
|
_LOGGER.warning("Platform already setup on %s, skipping", address)
|
|
|
|
return
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2020-07-05 19:13:08 +00:00
|
|
|
adbkey, signer, adb_log = await hass.async_add_executor_job(
|
|
|
|
setup_androidtv, hass, config
|
|
|
|
)
|
|
|
|
|
|
|
|
aftv = await setup(
|
|
|
|
config[CONF_HOST],
|
|
|
|
config[CONF_PORT],
|
|
|
|
adbkey,
|
|
|
|
config.get(CONF_ADB_SERVER_IP, ""),
|
|
|
|
config[CONF_ADB_SERVER_PORT],
|
|
|
|
config[CONF_STATE_DETECTION_RULES],
|
|
|
|
config[CONF_DEVICE_CLASS],
|
|
|
|
10.0,
|
|
|
|
signer,
|
|
|
|
)
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
if not aftv.available:
|
|
|
|
# Determine the name that will be used for the device in the log
|
|
|
|
if CONF_NAME in config:
|
|
|
|
device_name = config[CONF_NAME]
|
|
|
|
elif config[CONF_DEVICE_CLASS] == DEVICE_ANDROIDTV:
|
2019-07-31 19:25:30 +00:00
|
|
|
device_name = "Android TV device"
|
2019-03-13 10:18:59 +00:00
|
|
|
elif config[CONF_DEVICE_CLASS] == DEVICE_FIRETV:
|
2019-07-31 19:25:30 +00:00
|
|
|
device_name = "Fire TV device"
|
2019-03-13 10:18:59 +00:00
|
|
|
else:
|
2019-07-31 19:25:30 +00:00
|
|
|
device_name = "Android TV / Fire TV device"
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2020-01-06 22:10:13 +00:00
|
|
|
_LOGGER.warning(
|
|
|
|
"Could not connect to %s at %s %s", device_name, address, adb_log
|
|
|
|
)
|
2019-03-29 15:08:36 +00:00
|
|
|
raise PlatformNotReady
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2020-07-20 00:48:08 +00:00
|
|
|
async def _async_close(event):
|
|
|
|
"""Close the ADB socket connection when HA stops."""
|
|
|
|
await aftv.adb_close()
|
|
|
|
|
|
|
|
# Close the ADB connection when HA stops
|
|
|
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_close)
|
|
|
|
|
2020-01-06 22:10:13 +00:00
|
|
|
device_args = [
|
|
|
|
aftv,
|
|
|
|
config[CONF_NAME],
|
|
|
|
config[CONF_APPS],
|
|
|
|
config[CONF_GET_SOURCES],
|
|
|
|
config.get(CONF_TURN_ON_COMMAND),
|
|
|
|
config.get(CONF_TURN_OFF_COMMAND),
|
2020-01-29 20:13:09 +00:00
|
|
|
config[CONF_EXCLUDE_UNNAMED_APPS],
|
2020-04-14 16:41:19 +00:00
|
|
|
config[CONF_SCREENCAP],
|
2020-01-06 22:10:13 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
if aftv.DEVICE_CLASS == DEVICE_ANDROIDTV:
|
|
|
|
device = AndroidTVDevice(*device_args)
|
|
|
|
device_name = config.get(CONF_NAME, "Android TV")
|
2019-02-28 11:29:56 +00:00
|
|
|
else:
|
2020-01-06 22:10:13 +00:00
|
|
|
device = FireTVDevice(*device_args)
|
|
|
|
device_name = config.get(CONF_NAME, "Fire TV")
|
2019-03-13 10:18:59 +00:00
|
|
|
|
2020-06-30 01:17:04 +00:00
|
|
|
async_add_entities([device])
|
2020-01-06 22:10:13 +00:00
|
|
|
_LOGGER.debug("Setup %s at %s %s", device_name, address, adb_log)
|
|
|
|
hass.data[ANDROIDTV_DOMAIN][address] = device
|
2019-02-28 11:29:56 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
if hass.services.has_service(ANDROIDTV_DOMAIN, SERVICE_ADB_COMMAND):
|
2019-02-28 11:29:56 +00:00
|
|
|
return
|
|
|
|
|
2020-06-30 01:17:04 +00:00
|
|
|
platform = entity_platform.current_platform.get()
|
|
|
|
|
2020-07-05 19:13:08 +00:00
|
|
|
async def service_adb_command(service):
|
2019-02-28 11:29:56 +00:00
|
|
|
"""Dispatch service calls to target entities."""
|
2020-01-06 22:10:13 +00:00
|
|
|
cmd = service.data[ATTR_COMMAND]
|
|
|
|
entity_id = service.data[ATTR_ENTITY_ID]
|
2019-07-31 19:25:30 +00:00
|
|
|
target_devices = [
|
|
|
|
dev
|
|
|
|
for dev in hass.data[ANDROIDTV_DOMAIN].values()
|
|
|
|
if dev.entity_id in entity_id
|
|
|
|
]
|
2019-02-28 11:29:56 +00:00
|
|
|
|
|
|
|
for target_device in target_devices:
|
2020-07-05 19:13:08 +00:00
|
|
|
output = await target_device.adb_command(cmd)
|
2019-02-28 11:29:56 +00:00
|
|
|
|
2019-05-24 22:43:35 +00:00
|
|
|
# log the output, if there is any
|
|
|
|
if output:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.info(
|
|
|
|
"Output of command '%s' from '%s': %s",
|
|
|
|
cmd,
|
|
|
|
target_device.entity_id,
|
|
|
|
output,
|
|
|
|
)
|
2019-02-28 11:29:56 +00:00
|
|
|
|
2020-06-30 01:17:04 +00:00
|
|
|
hass.services.async_register(
|
2019-07-31 19:25:30 +00:00
|
|
|
ANDROIDTV_DOMAIN,
|
|
|
|
SERVICE_ADB_COMMAND,
|
|
|
|
service_adb_command,
|
|
|
|
schema=SERVICE_ADB_COMMAND_SCHEMA,
|
|
|
|
)
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2020-06-30 01:17:04 +00:00
|
|
|
platform.async_register_entity_service(
|
2020-07-05 19:13:08 +00:00
|
|
|
SERVICE_LEARN_SENDEVENT, {}, "learn_sendevent"
|
2020-06-30 01:17:04 +00:00
|
|
|
)
|
|
|
|
|
2020-07-05 19:13:08 +00:00
|
|
|
async def service_download(service):
|
2020-01-06 22:10:13 +00:00
|
|
|
"""Download a file from your Android TV / Fire TV device to your Home Assistant instance."""
|
|
|
|
local_path = service.data[ATTR_LOCAL_PATH]
|
|
|
|
if not hass.config.is_allowed_path(local_path):
|
|
|
|
_LOGGER.warning("'%s' is not secure to load data from!", local_path)
|
|
|
|
return
|
|
|
|
|
|
|
|
device_path = service.data[ATTR_DEVICE_PATH]
|
|
|
|
entity_id = service.data[ATTR_ENTITY_ID]
|
|
|
|
target_device = [
|
|
|
|
dev
|
|
|
|
for dev in hass.data[ANDROIDTV_DOMAIN].values()
|
|
|
|
if dev.entity_id in entity_id
|
|
|
|
][0]
|
|
|
|
|
2020-07-05 19:13:08 +00:00
|
|
|
await target_device.adb_pull(local_path, device_path)
|
2020-01-06 22:10:13 +00:00
|
|
|
|
2020-06-30 01:17:04 +00:00
|
|
|
hass.services.async_register(
|
2020-01-06 22:10:13 +00:00
|
|
|
ANDROIDTV_DOMAIN,
|
|
|
|
SERVICE_DOWNLOAD,
|
|
|
|
service_download,
|
|
|
|
schema=SERVICE_DOWNLOAD_SCHEMA,
|
|
|
|
)
|
|
|
|
|
2020-07-05 19:13:08 +00:00
|
|
|
async def service_upload(service):
|
2020-01-06 22:10:13 +00:00
|
|
|
"""Upload a file from your Home Assistant instance to an Android TV / Fire TV device."""
|
|
|
|
local_path = service.data[ATTR_LOCAL_PATH]
|
|
|
|
if not hass.config.is_allowed_path(local_path):
|
|
|
|
_LOGGER.warning("'%s' is not secure to load data from!", local_path)
|
|
|
|
return
|
|
|
|
|
|
|
|
device_path = service.data[ATTR_DEVICE_PATH]
|
|
|
|
entity_id = service.data[ATTR_ENTITY_ID]
|
|
|
|
target_devices = [
|
|
|
|
dev
|
|
|
|
for dev in hass.data[ANDROIDTV_DOMAIN].values()
|
|
|
|
if dev.entity_id in entity_id
|
|
|
|
]
|
|
|
|
|
|
|
|
for target_device in target_devices:
|
2020-07-05 19:13:08 +00:00
|
|
|
await target_device.adb_push(local_path, device_path)
|
2020-01-06 22:10:13 +00:00
|
|
|
|
2020-06-30 01:17:04 +00:00
|
|
|
hass.services.async_register(
|
2020-04-04 20:31:56 +00:00
|
|
|
ANDROIDTV_DOMAIN, SERVICE_UPLOAD, service_upload, schema=SERVICE_UPLOAD_SCHEMA
|
2020-01-06 22:10:13 +00:00
|
|
|
)
|
|
|
|
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2018-11-19 06:05:58 +00:00
|
|
|
def adb_decorator(override_available=False):
|
2019-11-17 11:47:14 +00:00
|
|
|
"""Wrap ADB methods and catch exceptions.
|
|
|
|
|
|
|
|
Allows for overriding the available status of the ADB connection via the
|
|
|
|
`override_available` parameter.
|
|
|
|
"""
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2019-02-24 23:16:49 +00:00
|
|
|
def _adb_decorator(func):
|
2019-11-17 11:47:14 +00:00
|
|
|
"""Wrap the provided ADB method and catch exceptions."""
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2018-11-19 06:05:58 +00:00
|
|
|
@functools.wraps(func)
|
2020-07-05 19:13:08 +00:00
|
|
|
async def _adb_exception_catcher(self, *args, **kwargs):
|
2019-11-17 11:47:14 +00:00
|
|
|
"""Call an ADB-related method and catch exceptions."""
|
2018-11-19 06:05:58 +00:00
|
|
|
if not self.available and not override_available:
|
|
|
|
return None
|
2018-06-18 16:13:50 +00:00
|
|
|
|
2018-11-19 06:05:58 +00:00
|
|
|
try:
|
2020-07-05 19:13:08 +00:00
|
|
|
return await func(self, *args, **kwargs)
|
2020-01-06 22:10:13 +00:00
|
|
|
except LockNotAcquiredException:
|
|
|
|
# If the ADB lock could not be acquired, skip this command
|
|
|
|
_LOGGER.info(
|
|
|
|
"ADB command not executed because the connection is currently in use"
|
|
|
|
)
|
|
|
|
return
|
2019-01-26 22:11:16 +00:00
|
|
|
except self.exceptions as err:
|
|
|
|
_LOGGER.error(
|
|
|
|
"Failed to execute an ADB command. ADB connection re-"
|
2019-07-31 19:25:30 +00:00
|
|
|
"establishing attempt in the next update. Error: %s",
|
|
|
|
err,
|
|
|
|
)
|
2020-07-05 19:13:08 +00:00
|
|
|
await self.aftv.adb_close()
|
2020-08-29 05:59:24 +00:00
|
|
|
self._available = False
|
2019-02-24 23:16:49 +00:00
|
|
|
return None
|
2020-08-30 02:56:25 +00:00
|
|
|
except Exception:
|
|
|
|
# An unforeseen exception occurred. Close the ADB connection so that
|
|
|
|
# it doesn't happen over and over again, then raise the exception.
|
|
|
|
await self.aftv.adb_close()
|
2020-09-12 21:53:41 +00:00
|
|
|
self._available = False
|
2020-08-30 02:56:25 +00:00
|
|
|
raise
|
2018-11-19 06:05:58 +00:00
|
|
|
|
2019-02-24 23:16:49 +00:00
|
|
|
return _adb_exception_catcher
|
2018-11-19 06:05:58 +00:00
|
|
|
|
2019-02-24 23:16:49 +00:00
|
|
|
return _adb_decorator
|
2018-06-18 16:13:50 +00:00
|
|
|
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2020-04-25 16:00:57 +00:00
|
|
|
class ADBDevice(MediaPlayerEntity):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Representation of an Android TV or Fire TV device."""
|
2019-03-03 20:39:39 +00:00
|
|
|
|
2019-12-14 15:54:41 +00:00
|
|
|
def __init__(
|
2020-01-29 20:13:09 +00:00
|
|
|
self,
|
|
|
|
aftv,
|
|
|
|
name,
|
|
|
|
apps,
|
|
|
|
get_sources,
|
|
|
|
turn_on_command,
|
|
|
|
turn_off_command,
|
|
|
|
exclude_unnamed_apps,
|
2020-04-14 16:41:19 +00:00
|
|
|
screencap,
|
2019-12-14 15:54:41 +00:00
|
|
|
):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Initialize the Android TV / Fire TV device."""
|
|
|
|
self.aftv = aftv
|
2015-10-09 01:07:36 +00:00
|
|
|
self._name = name
|
2019-11-07 22:04:59 +00:00
|
|
|
self._app_id_to_name = APPS.copy()
|
|
|
|
self._app_id_to_name.update(apps)
|
|
|
|
self._app_name_to_id = {
|
2020-01-29 20:13:09 +00:00
|
|
|
value: key for key, value in self._app_id_to_name.items() if value
|
2019-11-07 22:04:59 +00:00
|
|
|
}
|
2020-11-05 15:41:22 +00:00
|
|
|
|
|
|
|
# Make sure that apps overridden via the `apps` parameter are reflected
|
|
|
|
# in `self._app_name_to_id`
|
|
|
|
for key, value in apps.items():
|
|
|
|
self._app_name_to_id[value] = key
|
|
|
|
|
2019-12-14 15:54:41 +00:00
|
|
|
self._get_sources = get_sources
|
2019-03-13 10:18:59 +00:00
|
|
|
self._keys = KEYS
|
2018-11-19 06:05:58 +00:00
|
|
|
|
2019-08-23 13:58:24 +00:00
|
|
|
self._device_properties = self.aftv.device_properties
|
|
|
|
self._unique_id = self._device_properties.get("serialno")
|
|
|
|
|
2019-03-25 12:37:10 +00:00
|
|
|
self.turn_on_command = turn_on_command
|
|
|
|
self.turn_off_command = turn_off_command
|
|
|
|
|
2020-01-29 20:13:09 +00:00
|
|
|
self._exclude_unnamed_apps = exclude_unnamed_apps
|
2020-04-14 16:41:19 +00:00
|
|
|
self._screencap = screencap
|
2020-01-29 20:13:09 +00:00
|
|
|
|
2018-11-19 06:05:58 +00:00
|
|
|
# ADB exceptions to catch
|
2019-03-13 10:18:59 +00:00
|
|
|
if not self.aftv.adb_server_ip:
|
2019-09-27 05:53:26 +00:00
|
|
|
# Using "adb_shell" (Python ADB implementation)
|
2019-07-31 19:25:30 +00:00
|
|
|
self.exceptions = (
|
2020-07-05 19:13:08 +00:00
|
|
|
AdbTimeoutError,
|
2019-07-31 19:25:30 +00:00
|
|
|
BrokenPipeError,
|
2020-01-29 14:18:57 +00:00
|
|
|
ConnectionResetError,
|
2019-07-31 19:25:30 +00:00
|
|
|
ValueError,
|
|
|
|
InvalidChecksumError,
|
|
|
|
InvalidCommandError,
|
|
|
|
InvalidResponseError,
|
|
|
|
TcpTimeoutException,
|
|
|
|
)
|
2019-02-24 23:16:49 +00:00
|
|
|
else:
|
|
|
|
# Using "pure-python-adb" (communicate with ADB server)
|
2019-03-22 12:47:06 +00:00
|
|
|
self.exceptions = (ConnectionResetError, RuntimeError)
|
2018-11-19 06:05:58 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
# Property attributes
|
2019-05-24 22:43:35 +00:00
|
|
|
self._adb_response = None
|
2019-11-17 11:47:14 +00:00
|
|
|
self._available = True
|
2018-06-18 16:13:50 +00:00
|
|
|
self._current_app = None
|
2019-12-14 15:54:41 +00:00
|
|
|
self._sources = None
|
2019-03-13 10:18:59 +00:00
|
|
|
self._state = None
|
2020-11-05 13:36:46 +00:00
|
|
|
self._hdmi_input = None
|
2019-03-13 10:18:59 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def app_id(self):
|
|
|
|
"""Return the current app."""
|
|
|
|
return self._current_app
|
|
|
|
|
|
|
|
@property
|
|
|
|
def app_name(self):
|
|
|
|
"""Return the friendly name of the current app."""
|
2019-11-07 22:04:59 +00:00
|
|
|
return self._app_id_to_name.get(self._current_app, self._current_app)
|
2019-03-13 10:18:59 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def available(self):
|
|
|
|
"""Return whether or not the ADB connection is valid."""
|
|
|
|
return self._available
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2019-05-24 22:43:35 +00:00
|
|
|
@property
|
2021-03-11 15:51:03 +00:00
|
|
|
def extra_state_attributes(self):
|
2020-11-05 13:36:46 +00:00
|
|
|
"""Provide the last ADB command's response and the device's HDMI input as attributes."""
|
|
|
|
return {
|
|
|
|
"adb_response": self._adb_response,
|
|
|
|
"hdmi_input": self._hdmi_input,
|
|
|
|
}
|
2019-05-24 22:43:35 +00:00
|
|
|
|
2020-05-27 00:02:18 +00:00
|
|
|
@property
|
|
|
|
def media_image_hash(self):
|
|
|
|
"""Hash value for media image."""
|
|
|
|
return f"{datetime.now().timestamp()}" if self._screencap else None
|
|
|
|
|
2015-10-09 01:07:36 +00:00
|
|
|
@property
|
|
|
|
def name(self):
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Return the device name."""
|
2015-10-09 01:07:36 +00:00
|
|
|
return self._name
|
|
|
|
|
2019-12-14 15:54:41 +00:00
|
|
|
@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
|
|
|
|
|
2015-10-09 01:07:36 +00:00
|
|
|
@property
|
|
|
|
def state(self):
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Return the state of the player."""
|
2015-10-10 20:45:13 +00:00
|
|
|
return self._state
|
|
|
|
|
2019-08-23 13:58:24 +00:00
|
|
|
@property
|
|
|
|
def unique_id(self):
|
|
|
|
"""Return the device unique id."""
|
|
|
|
return self._unique_id
|
|
|
|
|
2020-07-05 19:13:08 +00:00
|
|
|
@adb_decorator()
|
2020-09-28 14:04:08 +00:00
|
|
|
async def _adb_screencap(self):
|
|
|
|
"""Take a screen capture from the device."""
|
|
|
|
return await self.aftv.adb_screencap()
|
|
|
|
|
2020-03-26 18:31:23 +00:00
|
|
|
async def async_get_media_image(self):
|
|
|
|
"""Fetch current playing image."""
|
2020-04-14 16:41:19 +00:00
|
|
|
if not self._screencap or self.state in [STATE_OFF, None] or not self.available:
|
2020-03-26 18:31:23 +00:00
|
|
|
return None, None
|
|
|
|
|
2020-09-28 14:04:08 +00:00
|
|
|
media_data = await self._adb_screencap()
|
2020-03-26 18:31:23 +00:00
|
|
|
if media_data:
|
|
|
|
return media_data, "image/png"
|
2020-09-28 14:04:08 +00:00
|
|
|
|
|
|
|
# If an exception occurred and the device is no longer available, write the state
|
|
|
|
if not self.available:
|
|
|
|
self.async_write_ha_state()
|
|
|
|
|
2020-03-26 18:31:23 +00:00
|
|
|
return None, None
|
|
|
|
|
|
|
|
@adb_decorator()
|
2020-07-05 19:13:08 +00:00
|
|
|
async def async_media_play(self):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Send play command."""
|
2020-07-05 19:13:08 +00:00
|
|
|
await self.aftv.media_play()
|
2018-11-19 06:05:58 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
@adb_decorator()
|
2020-07-05 19:13:08 +00:00
|
|
|
async def async_media_pause(self):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Send pause command."""
|
2020-07-05 19:13:08 +00:00
|
|
|
await self.aftv.media_pause()
|
2019-02-03 21:57:17 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
@adb_decorator()
|
2020-07-05 19:13:08 +00:00
|
|
|
async def async_media_play_pause(self):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Send play/pause command."""
|
2020-07-05 19:13:08 +00:00
|
|
|
await self.aftv.media_play_pause()
|
2019-03-03 20:39:39 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
@adb_decorator()
|
2020-07-05 19:13:08 +00:00
|
|
|
async def async_turn_on(self):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Turn on the device."""
|
2019-03-25 12:37:10 +00:00
|
|
|
if self.turn_on_command:
|
2020-07-05 19:13:08 +00:00
|
|
|
await self.aftv.adb_shell(self.turn_on_command)
|
2019-03-25 12:37:10 +00:00
|
|
|
else:
|
2020-07-05 19:13:08 +00:00
|
|
|
await self.aftv.turn_on()
|
2018-06-18 16:13:50 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
@adb_decorator()
|
2020-07-05 19:13:08 +00:00
|
|
|
async def async_turn_off(self):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Turn off the device."""
|
2019-03-25 12:37:10 +00:00
|
|
|
if self.turn_off_command:
|
2020-07-05 19:13:08 +00:00
|
|
|
await self.aftv.adb_shell(self.turn_off_command)
|
2019-03-25 12:37:10 +00:00
|
|
|
else:
|
2020-07-05 19:13:08 +00:00
|
|
|
await self.aftv.turn_off()
|
2019-03-13 10:18:59 +00:00
|
|
|
|
|
|
|
@adb_decorator()
|
2020-07-05 19:13:08 +00:00
|
|
|
async def async_media_previous_track(self):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Send previous track command (results in rewind)."""
|
2020-07-05 19:13:08 +00:00
|
|
|
await self.aftv.media_previous_track()
|
2019-03-13 10:18:59 +00:00
|
|
|
|
|
|
|
@adb_decorator()
|
2020-07-05 19:13:08 +00:00
|
|
|
async def async_media_next_track(self):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Send next track command (results in fast-forward)."""
|
2020-07-05 19:13:08 +00:00
|
|
|
await self.aftv.media_next_track()
|
2019-03-13 10:18:59 +00:00
|
|
|
|
2019-12-14 15:54:41 +00:00
|
|
|
@adb_decorator()
|
2020-07-05 19:13:08 +00:00
|
|
|
async def async_select_source(self, source):
|
2019-12-14 15:54:41 +00:00
|
|
|
"""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("!"):
|
2020-07-05 19:13:08 +00:00
|
|
|
await self.aftv.launch_app(self._app_name_to_id.get(source, source))
|
2019-12-14 15:54:41 +00:00
|
|
|
else:
|
|
|
|
source_ = source[1:].lstrip()
|
2020-07-05 19:13:08 +00:00
|
|
|
await self.aftv.stop_app(self._app_name_to_id.get(source_, source_))
|
2019-12-14 15:54:41 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
@adb_decorator()
|
2020-07-05 19:13:08 +00:00
|
|
|
async def adb_command(self, cmd):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Send an ADB command to an Android TV / Fire TV device."""
|
|
|
|
key = self._keys.get(cmd)
|
|
|
|
if key:
|
2020-07-05 19:13:08 +00:00
|
|
|
await self.aftv.adb_shell(f"input keyevent {key}")
|
2019-05-24 22:43:35 +00:00
|
|
|
return
|
2019-03-13 10:18:59 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
if cmd == "GET_PROPERTIES":
|
2020-07-05 19:13:08 +00:00
|
|
|
self._adb_response = str(await self.aftv.get_properties_dict())
|
|
|
|
self.async_write_ha_state()
|
2019-05-24 22:43:35 +00:00
|
|
|
return self._adb_response
|
|
|
|
|
2020-01-07 12:30:34 +00:00
|
|
|
try:
|
2020-07-05 19:13:08 +00:00
|
|
|
response = await self.aftv.adb_shell(cmd)
|
2020-01-07 12:30:34 +00:00
|
|
|
except UnicodeDecodeError:
|
|
|
|
return
|
|
|
|
|
2019-05-24 22:43:35 +00:00
|
|
|
if isinstance(response, str) and response.strip():
|
|
|
|
self._adb_response = response.strip()
|
2020-07-05 19:13:08 +00:00
|
|
|
self.async_write_ha_state()
|
2019-03-13 10:18:59 +00:00
|
|
|
|
2019-05-24 22:43:35 +00:00
|
|
|
return self._adb_response
|
2019-03-13 10:18:59 +00:00
|
|
|
|
2020-06-30 01:17:04 +00:00
|
|
|
@adb_decorator()
|
2020-07-05 19:13:08 +00:00
|
|
|
async def learn_sendevent(self):
|
2020-06-30 01:17:04 +00:00
|
|
|
"""Translate a key press on a remote to ADB 'sendevent' commands."""
|
2020-07-05 19:13:08 +00:00
|
|
|
output = await self.aftv.learn_sendevent()
|
2020-06-30 01:17:04 +00:00
|
|
|
if output:
|
|
|
|
self._adb_response = output
|
2020-07-05 19:13:08 +00:00
|
|
|
self.async_write_ha_state()
|
2020-06-30 01:17:04 +00:00
|
|
|
|
|
|
|
msg = f"Output from service '{SERVICE_LEARN_SENDEVENT}' from {self.entity_id}: '{output}'"
|
2020-07-05 19:13:08 +00:00
|
|
|
self.hass.components.persistent_notification.async_create(
|
2020-08-27 11:56:20 +00:00
|
|
|
msg,
|
|
|
|
title="Android TV",
|
2020-07-05 19:13:08 +00:00
|
|
|
)
|
2020-06-30 01:17:04 +00:00
|
|
|
_LOGGER.info("%s", msg)
|
|
|
|
|
2020-01-06 22:10:13 +00:00
|
|
|
@adb_decorator()
|
2020-07-05 19:13:08 +00:00
|
|
|
async def adb_pull(self, local_path, device_path):
|
2020-01-06 22:10:13 +00:00
|
|
|
"""Download a file from your Android TV / Fire TV device to your Home Assistant instance."""
|
2020-07-05 19:13:08 +00:00
|
|
|
await self.aftv.adb_pull(local_path, device_path)
|
2020-01-06 22:10:13 +00:00
|
|
|
|
|
|
|
@adb_decorator()
|
2020-07-05 19:13:08 +00:00
|
|
|
async def adb_push(self, local_path, device_path):
|
2020-01-06 22:10:13 +00:00
|
|
|
"""Upload a file from your Home Assistant instance to an Android TV / Fire TV device."""
|
2020-07-05 19:13:08 +00:00
|
|
|
await self.aftv.adb_push(local_path, device_path)
|
2020-01-06 22:10:13 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
|
|
|
|
class AndroidTVDevice(ADBDevice):
|
|
|
|
"""Representation of an Android TV device."""
|
|
|
|
|
2019-12-14 15:54:41 +00:00
|
|
|
def __init__(
|
2020-01-29 20:13:09 +00:00
|
|
|
self,
|
|
|
|
aftv,
|
|
|
|
name,
|
|
|
|
apps,
|
|
|
|
get_sources,
|
|
|
|
turn_on_command,
|
|
|
|
turn_off_command,
|
|
|
|
exclude_unnamed_apps,
|
2020-04-14 16:41:19 +00:00
|
|
|
screencap,
|
2019-12-14 15:54:41 +00:00
|
|
|
):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Initialize the Android TV device."""
|
2019-12-14 15:54:41 +00:00
|
|
|
super().__init__(
|
2020-01-29 20:13:09 +00:00
|
|
|
aftv,
|
|
|
|
name,
|
|
|
|
apps,
|
|
|
|
get_sources,
|
|
|
|
turn_on_command,
|
|
|
|
turn_off_command,
|
|
|
|
exclude_unnamed_apps,
|
2020-04-14 16:41:19 +00:00
|
|
|
screencap,
|
2019-12-14 15:54:41 +00:00
|
|
|
)
|
2019-03-13 10:18:59 +00:00
|
|
|
|
2019-04-06 15:18:50 +00:00
|
|
|
self._is_volume_muted = None
|
|
|
|
self._volume_level = None
|
2018-06-18 16:13:50 +00:00
|
|
|
|
2018-11-19 06:05:58 +00:00
|
|
|
@adb_decorator(override_available=True)
|
2020-07-05 19:13:08 +00:00
|
|
|
async def async_update(self):
|
2019-02-24 23:16:49 +00:00
|
|
|
"""Update the device state and, if necessary, re-connect."""
|
2018-11-19 06:05:58 +00:00
|
|
|
# Check if device is disconnected.
|
|
|
|
if not self._available:
|
|
|
|
# Try to connect
|
2020-07-05 19:13:08 +00:00
|
|
|
self._available = await self.aftv.adb_connect(always_log_errors=False)
|
2018-11-19 06:05:58 +00:00
|
|
|
|
|
|
|
# If the ADB connection is not intact, don't update.
|
|
|
|
if not self._available:
|
|
|
|
return
|
|
|
|
|
2019-04-06 15:18:50 +00:00
|
|
|
# Get the updated state and attributes.
|
2019-10-29 06:32:34 +00:00
|
|
|
(
|
|
|
|
state,
|
|
|
|
self._current_app,
|
2019-12-14 15:54:41 +00:00
|
|
|
running_apps,
|
|
|
|
_,
|
2019-10-29 06:32:34 +00:00
|
|
|
self._is_volume_muted,
|
|
|
|
self._volume_level,
|
2020-11-05 13:36:46 +00:00
|
|
|
self._hdmi_input,
|
2020-07-05 19:13:08 +00:00
|
|
|
) = await self.aftv.update(self._get_sources)
|
2018-11-19 06:05:58 +00:00
|
|
|
|
2019-08-29 10:03:03 +00:00
|
|
|
self._state = ANDROIDTV_STATES.get(state)
|
|
|
|
if self._state is None:
|
|
|
|
self._available = False
|
2018-11-19 06:05:58 +00:00
|
|
|
|
2019-12-14 15:54:41 +00:00
|
|
|
if running_apps:
|
2020-01-29 20:13:09 +00:00
|
|
|
sources = [
|
|
|
|
self._app_id_to_name.get(
|
|
|
|
app_id, app_id if not self._exclude_unnamed_apps else None
|
|
|
|
)
|
|
|
|
for app_id in running_apps
|
2019-12-14 15:54:41 +00:00
|
|
|
]
|
2020-01-29 20:13:09 +00:00
|
|
|
self._sources = [source for source in sources if source]
|
2019-12-14 15:54:41 +00:00
|
|
|
else:
|
|
|
|
self._sources = None
|
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
@property
|
|
|
|
def is_volume_muted(self):
|
|
|
|
"""Boolean if volume is currently muted."""
|
2019-04-06 15:18:50 +00:00
|
|
|
return self._is_volume_muted
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
@property
|
|
|
|
def supported_features(self):
|
|
|
|
"""Flag media player features that are supported."""
|
|
|
|
return SUPPORT_ANDROIDTV
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
@property
|
|
|
|
def volume_level(self):
|
|
|
|
"""Return the volume level."""
|
2019-04-06 15:18:50 +00:00
|
|
|
return self._volume_level
|
2018-11-19 06:05:58 +00:00
|
|
|
|
|
|
|
@adb_decorator()
|
2020-07-05 19:13:08 +00:00
|
|
|
async def async_media_stop(self):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Send stop command."""
|
2020-07-05 19:13:08 +00:00
|
|
|
await self.aftv.media_stop()
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2018-11-19 06:05:58 +00:00
|
|
|
@adb_decorator()
|
2020-07-05 19:13:08 +00:00
|
|
|
async def async_mute_volume(self, mute):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Mute the volume."""
|
2020-07-05 19:13:08 +00:00
|
|
|
await self.aftv.mute_volume()
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2020-01-26 09:39:19 +00:00
|
|
|
@adb_decorator()
|
2020-07-05 19:13:08 +00:00
|
|
|
async def async_set_volume_level(self, volume):
|
2020-01-26 09:39:19 +00:00
|
|
|
"""Set the volume level."""
|
2020-07-05 19:13:08 +00:00
|
|
|
await self.aftv.set_volume_level(volume)
|
2020-01-26 09:39:19 +00:00
|
|
|
|
2018-11-19 06:05:58 +00:00
|
|
|
@adb_decorator()
|
2020-07-05 19:13:08 +00:00
|
|
|
async def async_volume_down(self):
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Send volume down command."""
|
2020-07-05 19:13:08 +00:00
|
|
|
self._volume_level = await self.aftv.volume_down(self._volume_level)
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2018-11-19 06:05:58 +00:00
|
|
|
@adb_decorator()
|
2020-07-05 19:13:08 +00:00
|
|
|
async def async_volume_up(self):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Send volume up command."""
|
2020-07-05 19:13:08 +00:00
|
|
|
self._volume_level = await self.aftv.volume_up(self._volume_level)
|
2019-03-13 10:18:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
class FireTVDevice(ADBDevice):
|
|
|
|
"""Representation of a Fire TV device."""
|
|
|
|
|
|
|
|
@adb_decorator(override_available=True)
|
2020-07-05 19:13:08 +00:00
|
|
|
async def async_update(self):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Update the device state and, if necessary, re-connect."""
|
|
|
|
# Check if device is disconnected.
|
|
|
|
if not self._available:
|
|
|
|
# Try to connect
|
2020-07-05 19:13:08 +00:00
|
|
|
self._available = await self.aftv.adb_connect(always_log_errors=False)
|
2019-03-13 10:18:59 +00:00
|
|
|
|
|
|
|
# If the ADB connection is not intact, don't update.
|
|
|
|
if not self._available:
|
|
|
|
return
|
|
|
|
|
2020-11-05 13:36:46 +00:00
|
|
|
# Get the `state`, `current_app`, `running_apps` and `hdmi_input`.
|
|
|
|
(
|
|
|
|
state,
|
|
|
|
self._current_app,
|
|
|
|
running_apps,
|
|
|
|
self._hdmi_input,
|
|
|
|
) = await self.aftv.update(self._get_sources)
|
2019-03-13 10:18:59 +00:00
|
|
|
|
2019-08-29 10:03:03 +00:00
|
|
|
self._state = ANDROIDTV_STATES.get(state)
|
|
|
|
if self._state is None:
|
|
|
|
self._available = False
|
2019-03-13 10:18:59 +00:00
|
|
|
|
2019-11-07 22:04:59 +00:00
|
|
|
if running_apps:
|
2020-01-29 20:13:09 +00:00
|
|
|
sources = [
|
|
|
|
self._app_id_to_name.get(
|
|
|
|
app_id, app_id if not self._exclude_unnamed_apps else None
|
|
|
|
)
|
|
|
|
for app_id in running_apps
|
2019-11-07 22:04:59 +00:00
|
|
|
]
|
2020-01-29 20:13:09 +00:00
|
|
|
self._sources = [source for source in sources if source]
|
2019-11-07 22:04:59 +00:00
|
|
|
else:
|
|
|
|
self._sources = None
|
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
@property
|
|
|
|
def supported_features(self):
|
|
|
|
"""Flag media player features that are supported."""
|
|
|
|
return SUPPORT_FIRETV
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2018-11-19 06:05:58 +00:00
|
|
|
@adb_decorator()
|
2020-07-05 19:13:08 +00:00
|
|
|
async def async_media_stop(self):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Send stop (back) command."""
|
2020-07-05 19:13:08 +00:00
|
|
|
await self.aftv.back()
|