"""Communicate with an Android TV device via ADB over a network.
ADB Debugging must be enabled.
"""
import logging
from ..basetv.basetv import BaseTV
from .. import constants
_LOGGER = logging.getLogger(__name__)
[docs]class BaseAndroidTV(BaseTV): # pylint: disable=too-few-public-methods
"""Representation of an Android TV device.
Parameters
----------
host : str
The address of the device; may be an IP address or a host name
port : int
The device port to which we are connecting (default is 5555)
adbkey : str
The path to the ``adbkey`` file for ADB authentication
adb_server_ip : str
The IP address of the ADB server
adb_server_port : int
The port for the ADB server
state_detection_rules : dict, None
A dictionary of rules for determining the state (see :class:`~androidtv.basetv.basetv.BaseTV`)
"""
DEVICE_CLASS = 'androidtv'
def __init__(self, host, port=5555, adbkey='', adb_server_ip='', adb_server_port=5037, state_detection_rules=None):
BaseTV.__init__(self, None, host, port, adbkey, adb_server_ip, adb_server_port, state_detection_rules)
# ======================================================================= #
# #
# Home Assistant Update #
# #
# ======================================================================= #
[docs] def _update(self, screen_on, awake, audio_state, wake_lock_size, current_app, media_session_state, audio_output_device, is_volume_muted, volume, running_apps):
"""Get the info needed for a Home Assistant update.
Parameters
----------
screen_on : bool, None
Whether or not the device is on, or ``None`` if it was not determined
awake : bool, None
Whether or not the device is awake (screensaver is not running), or ``None`` if it was not determined
audio_state : str, None
The audio state, as determined from "dumpsys audio", or ``None`` if it was not determined
wake_lock_size : int, None
The size of the current wake lock, or ``None`` if it was not determined
current_app : str, None
The current app property, or ``None`` if it was not determined
media_session_state : int, None
The state from the output of ``dumpsys media_session``, or ``None`` if it was not determined
audio_output_device : str, None
The current audio playback device, or ``None`` if it was not determined
is_volume_muted : bool, None
Whether or not the volume is muted, or ``None`` if it was not determined
volume : int, None
The absolute volume level, or ``None`` if it was not determined
running_apps : list, None
A list of the running apps, or ``None`` if it was not determined
Returns
-------
state : str
The state of the device
current_app : str
The current running app
running_apps : list
A list of the running apps if ``get_running_apps`` is True, otherwise the list ``[current_app]``
audio_output_device : str
The current audio playback device
is_volume_muted : bool
Whether or not the volume is muted
volume_level : float
The volume level (between 0 and 1)
"""
# Get the volume (between 0 and 1)
volume_level = self._volume_level(volume)
# Check if device is unavailable
if screen_on is None:
state = None
# Check if device is off
elif not screen_on or current_app == 'off':
state = constants.STATE_OFF
# Check if screen saver is on
elif not awake:
state = constants.STATE_STANDBY
else:
# Get the running apps
if not running_apps and current_app:
running_apps = [current_app]
# Determine the state using custom rules
state = self._custom_state_detection(current_app=current_app, media_session_state=media_session_state, wake_lock_size=wake_lock_size, audio_state=audio_state)
if state:
return state, current_app, running_apps, audio_output_device, is_volume_muted, volume_level
# ATV Launcher
if current_app in [constants.APP_ATV_LAUNCHER, None]:
state = constants.STATE_IDLE
# BELL Fibe
elif current_app == constants.APP_BELL_FIBE:
state = audio_state
# Netflix
elif current_app == constants.APP_NETFLIX:
if media_session_state == 2:
state = constants.STATE_PAUSED
elif media_session_state == 3:
state = constants.STATE_PLAYING
else:
state = constants.STATE_IDLE
# Plex
elif current_app == constants.APP_PLEX:
if media_session_state == 3:
if wake_lock_size == 1:
state = constants.STATE_PAUSED
else:
state = constants.STATE_PLAYING
else:
state = constants.STATE_IDLE
# TVheadend
elif current_app == constants.APP_TVHEADEND:
if wake_lock_size == 5:
state = constants.STATE_PAUSED
elif wake_lock_size == 6:
state = constants.STATE_PLAYING
else:
state = constants.STATE_IDLE
# VLC
elif current_app == constants.APP_VLC:
if media_session_state == 2:
state = constants.STATE_PAUSED
elif media_session_state == 3:
state = constants.STATE_PLAYING
else:
state = constants.STATE_IDLE
# VRV
elif current_app == constants.APP_VRV:
state = audio_state
# YouTube
elif current_app == constants.APP_YOUTUBE:
if media_session_state == 2:
state = constants.STATE_PAUSED
elif media_session_state == 3:
state = constants.STATE_PLAYING
else:
state = constants.STATE_IDLE
# Get the state from `media_session_state`
elif media_session_state:
if media_session_state == 2:
state = constants.STATE_PAUSED
elif media_session_state == 3:
state = constants.STATE_PLAYING
else:
state = constants.STATE_IDLE
# Get the state from `audio_state`
elif audio_state != constants.STATE_IDLE:
state = audio_state
# Get the state from `wake_lock_size`
else:
if wake_lock_size == 1:
state = constants.STATE_PAUSED
elif wake_lock_size == 2:
state = constants.STATE_PLAYING
else:
state = constants.STATE_IDLE
return state, current_app, running_apps, audio_output_device, is_volume_muted, volume_level
# ======================================================================= #
# #
# Properties #
# #
# ======================================================================= #
[docs] def _get_properties(self, output, get_running_apps):
"""Get the properties needed for Home Assistant updates.
Parameters
----------
output : str, None
The output of the ADB command used to retrieve the properties
get_running_apps : bool
Whether or not to get the ``running_apps`` property
Returns
-------
screen_on : bool, None
Whether or not the device is on, or ``None`` if it was not determined
awake : bool, None
Whether or not the device is awake (screensaver is not running), or ``None`` if it was not determined
audio_state : str, None
The audio state, as determined from "dumpsys audio", or ``None`` if it was not determined
wake_lock_size : int, None
The size of the current wake lock, or ``None`` if it was not determined
current_app : str, None
The current app property, or ``None`` if it was not determined
media_session_state : int, None
The state from the output of ``dumpsys media_session``, or ``None`` if it was not determined
audio_output_device : str, None
The current audio playback device, or ``None`` if it was not determined
is_volume_muted : bool, None
Whether or not the volume is muted, or ``None`` if it was not determined
volume : int, None
The absolute volume level, or ``None`` if it was not determined
running_apps : list, None
A list of the running apps, or ``None`` if it was not determined
"""
# ADB command was unsuccessful
if output is None:
return None, None, None, None, None, None, None, None, None, None
# `screen_on` property
if not output:
return False, False, None, -1, None, None, None, None, None, None
screen_on = output[0] == '1'
# `awake` property
if len(output) < 2:
return screen_on, False, None, -1, None, None, None, None, None, None
awake = output[1] == '1'
# `audio_state` property
if len(output) < 3:
return screen_on, awake, None, -1, None, None, None, None, None, None
audio_state = self._audio_state(output[2])
lines = output.strip().splitlines()
# `wake_lock_size` property
if len(lines[0]) < 4:
return screen_on, awake, audio_state, -1, None, None, None, None, None, None
wake_lock_size = self._wake_lock_size(lines[0])
# `current_app` property
if len(lines) < 2:
return screen_on, awake, audio_state, wake_lock_size, None, None, None, None, None, None
current_app = self._current_app(lines[1])
# `media_session_state` property
if len(lines) < 3:
return screen_on, awake, audio_state, wake_lock_size, current_app, None, None, None, None, None
media_session_state = self._media_session_state(lines[2], current_app)
# "STREAM_MUSIC" block
if len(lines) < 4:
return screen_on, awake, audio_state, wake_lock_size, current_app, media_session_state, None, None, None, None
# reconstruct the output of `constants.CMD_STREAM_MUSIC`
stream_music_raw = "\n".join(lines[3:])
# the "STREAM_MUSIC" block from `adb shell dumpsys audio`
stream_music = self._parse_stream_music(stream_music_raw)
# `audio_output_device` property
audio_output_device = self._audio_output_device(stream_music)
# `volume` property
volume = self._volume(stream_music, audio_output_device)
# `is_volume_muted` property
is_volume_muted = self._is_volume_muted(stream_music)
# `running_apps` property
if not get_running_apps or len(lines) < 16:
return screen_on, awake, audio_state, wake_lock_size, current_app, media_session_state, audio_output_device, is_volume_muted, volume, None
running_apps = self._running_apps(lines[15:])
return screen_on, awake, audio_state, wake_lock_size, current_app, media_session_state, audio_output_device, is_volume_muted, volume, running_apps