Android TV Remote: Add option to disable IME (#95765)

This commit is contained in:
tronikos 2023-07-24 11:00:51 -07:00 committed by GitHub
parent 2cfc11d4b9
commit d0722e2312
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 126 additions and 7 deletions

View File

@ -18,7 +18,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from .const import DOMAIN from .const import DOMAIN
from .helpers import create_api from .helpers import create_api, get_enable_ime
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -27,7 +27,7 @@ PLATFORMS: list[Platform] = [Platform.MEDIA_PLAYER, Platform.REMOTE]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Android TV Remote from a config entry.""" """Set up Android TV Remote from a config entry."""
api = create_api(hass, entry.data[CONF_HOST]) api = create_api(hass, entry.data[CONF_HOST], get_enable_ime(entry))
@callback @callback
def is_available_updated(is_available: bool) -> None: def is_available_updated(is_available: bool) -> None:
@ -76,6 +76,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
entry.async_on_unload( entry.async_on_unload(
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop)
) )
entry.async_on_unload(entry.add_update_listener(update_listener))
return True return True
@ -87,3 +88,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
api.disconnect() api.disconnect()
return unload_ok return unload_ok
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle options update."""
await hass.config_entries.async_reload(entry.entry_id)

View File

@ -15,11 +15,12 @@ import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components import zeroconf from homeassistant.components import zeroconf
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.device_registry import format_mac
from .const import DOMAIN from .const import CONF_ENABLE_IME, DOMAIN
from .helpers import create_api from .helpers import create_api, get_enable_ime
STEP_USER_DATA_SCHEMA = vol.Schema( STEP_USER_DATA_SCHEMA = vol.Schema(
{ {
@ -55,7 +56,7 @@ class AndroidTVRemoteConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
if user_input is not None: if user_input is not None:
self.host = user_input["host"] self.host = user_input["host"]
assert self.host assert self.host
api = create_api(self.hass, self.host) api = create_api(self.hass, self.host, enable_ime=False)
try: try:
self.name, self.mac = await api.async_get_name_and_mac() self.name, self.mac = await api.async_get_name_and_mac()
assert self.mac assert self.mac
@ -75,7 +76,7 @@ class AndroidTVRemoteConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def _async_start_pair(self) -> FlowResult: async def _async_start_pair(self) -> FlowResult:
"""Start pairing with the Android TV. Navigate to the pair flow to enter the PIN shown on screen.""" """Start pairing with the Android TV. Navigate to the pair flow to enter the PIN shown on screen."""
assert self.host assert self.host
self.api = create_api(self.hass, self.host) self.api = create_api(self.hass, self.host, enable_ime=False)
await self.api.async_generate_cert_if_missing() await self.api.async_generate_cert_if_missing()
await self.api.async_start_pairing() await self.api.async_start_pairing()
return await self.async_step_pair() return await self.async_step_pair()
@ -186,3 +187,38 @@ class AndroidTVRemoteConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
description_placeholders={CONF_NAME: self.name}, description_placeholders={CONF_NAME: self.name},
errors=errors, errors=errors,
) )
@staticmethod
@callback
def async_get_options_flow(
config_entry: config_entries.ConfigEntry,
) -> config_entries.OptionsFlow:
"""Create the options flow."""
return OptionsFlowHandler(config_entry)
class OptionsFlowHandler(config_entries.OptionsFlow):
"""Android TV Remote options flow."""
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
"""Initialize options flow."""
self.config_entry = config_entry
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Manage the options."""
if user_input is not None:
return self.async_create_entry(title="", data=user_input)
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Required(
CONF_ENABLE_IME,
default=get_enable_ime(self.config_entry),
): bool,
}
),
)

View File

@ -4,3 +4,6 @@ from __future__ import annotations
from typing import Final from typing import Final
DOMAIN: Final = "androidtv_remote" DOMAIN: Final = "androidtv_remote"
CONF_ENABLE_IME: Final = "enable_ime"
CONF_ENABLE_IME_DEFAULT_VALUE: Final = True

View File

@ -3,11 +3,14 @@ from __future__ import annotations
from androidtvremote2 import AndroidTVRemote from androidtvremote2 import AndroidTVRemote
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.storage import STORAGE_DIR from homeassistant.helpers.storage import STORAGE_DIR
from .const import CONF_ENABLE_IME, CONF_ENABLE_IME_DEFAULT_VALUE
def create_api(hass: HomeAssistant, host: str) -> AndroidTVRemote:
def create_api(hass: HomeAssistant, host: str, enable_ime: bool) -> AndroidTVRemote:
"""Create an AndroidTVRemote instance.""" """Create an AndroidTVRemote instance."""
return AndroidTVRemote( return AndroidTVRemote(
client_name="Home Assistant", client_name="Home Assistant",
@ -15,4 +18,10 @@ def create_api(hass: HomeAssistant, host: str) -> AndroidTVRemote:
keyfile=hass.config.path(STORAGE_DIR, "androidtv_remote_key.pem"), keyfile=hass.config.path(STORAGE_DIR, "androidtv_remote_key.pem"),
host=host, host=host,
loop=hass.loop, loop=hass.loop,
enable_ime=enable_ime,
) )
def get_enable_ime(entry: ConfigEntry) -> bool:
"""Get value of enable_ime option or its default value."""
return entry.options.get(CONF_ENABLE_IME, CONF_ENABLE_IME_DEFAULT_VALUE)

View File

@ -34,5 +34,14 @@
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
} }
},
"options": {
"step": {
"init": {
"data": {
"enable_ime": "Enable IME. Needed for getting the current app. Disable for devices that show 'Use keyboard on mobile device screen' instead of the on screen keyboard."
}
}
}
} }
} }

View File

@ -857,3 +857,59 @@ async def test_reauth_flow_cannot_connect(
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(mock_unload_entry.mock_calls) == 0 assert len(mock_unload_entry.mock_calls) == 0
assert len(mock_setup_entry.mock_calls) == 0 assert len(mock_setup_entry.mock_calls) == 0
async def test_options_flow(
hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_api: MagicMock
) -> None:
"""Test options flow."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_api.disconnect.call_count == 0
assert mock_api.async_connect.call_count == 1
# Trigger options flow, first time
result = await hass.config_entries.options.async_init(mock_config_entry.entry_id)
assert result["type"] == "form"
assert result["step_id"] == "init"
data_schema = result["data_schema"].schema
assert set(data_schema) == {"enable_ime"}
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={"enable_ime": False},
)
assert result["type"] == "create_entry"
assert mock_config_entry.options == {"enable_ime": False}
await hass.async_block_till_done()
assert mock_api.disconnect.call_count == 1
assert mock_api.async_connect.call_count == 2
# Trigger options flow, second time, no change, doesn't reload
result = await hass.config_entries.options.async_init(mock_config_entry.entry_id)
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={"enable_ime": False},
)
assert result["type"] == "create_entry"
assert mock_config_entry.options == {"enable_ime": False}
await hass.async_block_till_done()
assert mock_api.disconnect.call_count == 1
assert mock_api.async_connect.call_count == 2
# Trigger options flow, third time, change, reloads
result = await hass.config_entries.options.async_init(mock_config_entry.entry_id)
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={"enable_ime": True},
)
assert result["type"] == "create_entry"
assert mock_config_entry.options == {"enable_ime": True}
await hass.async_block_till_done()
assert mock_api.disconnect.call_count == 2
assert mock_api.async_connect.call_count == 3