From d0722e2312ee79d15319b50863f8b5abe94d0479 Mon Sep 17 00:00:00 2001 From: tronikos Date: Mon, 24 Jul 2023 11:00:51 -0700 Subject: [PATCH] Android TV Remote: Add option to disable IME (#95765) --- .../components/androidtv_remote/__init__.py | 10 +++- .../androidtv_remote/config_flow.py | 44 +++++++++++++-- .../components/androidtv_remote/const.py | 3 + .../components/androidtv_remote/helpers.py | 11 +++- .../components/androidtv_remote/strings.json | 9 +++ .../androidtv_remote/test_config_flow.py | 56 +++++++++++++++++++ 6 files changed, 126 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/androidtv_remote/__init__.py b/homeassistant/components/androidtv_remote/__init__.py index 9299b1ed0b0..4c58f82b8e7 100644 --- a/homeassistant/components/androidtv_remote/__init__.py +++ b/homeassistant/components/androidtv_remote/__init__.py @@ -18,7 +18,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from .const import DOMAIN -from .helpers import create_api +from .helpers import create_api, get_enable_ime _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: """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 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( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop) ) + entry.async_on_unload(entry.add_update_listener(update_listener)) return True @@ -87,3 +88,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: api.disconnect() return unload_ok + + +async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Handle options update.""" + await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/androidtv_remote/config_flow.py b/homeassistant/components/androidtv_remote/config_flow.py index f7e1078d3fa..b8399fd7ba2 100644 --- a/homeassistant/components/androidtv_remote/config_flow.py +++ b/homeassistant/components/androidtv_remote/config_flow.py @@ -15,11 +15,12 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.components import zeroconf from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME +from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.device_registry import format_mac -from .const import DOMAIN -from .helpers import create_api +from .const import CONF_ENABLE_IME, DOMAIN +from .helpers import create_api, get_enable_ime STEP_USER_DATA_SCHEMA = vol.Schema( { @@ -55,7 +56,7 @@ class AndroidTVRemoteConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if user_input is not None: self.host = user_input["host"] assert self.host - api = create_api(self.hass, self.host) + api = create_api(self.hass, self.host, enable_ime=False) try: self.name, self.mac = await api.async_get_name_and_mac() assert self.mac @@ -75,7 +76,7 @@ class AndroidTVRemoteConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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.""" 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_start_pairing() return await self.async_step_pair() @@ -186,3 +187,38 @@ class AndroidTVRemoteConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): description_placeholders={CONF_NAME: self.name}, 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, + } + ), + ) diff --git a/homeassistant/components/androidtv_remote/const.py b/homeassistant/components/androidtv_remote/const.py index 82f494b81aa..44d7098adc1 100644 --- a/homeassistant/components/androidtv_remote/const.py +++ b/homeassistant/components/androidtv_remote/const.py @@ -4,3 +4,6 @@ from __future__ import annotations from typing import Final DOMAIN: Final = "androidtv_remote" + +CONF_ENABLE_IME: Final = "enable_ime" +CONF_ENABLE_IME_DEFAULT_VALUE: Final = True diff --git a/homeassistant/components/androidtv_remote/helpers.py b/homeassistant/components/androidtv_remote/helpers.py index 0bc1f1b904f..41b056269f2 100644 --- a/homeassistant/components/androidtv_remote/helpers.py +++ b/homeassistant/components/androidtv_remote/helpers.py @@ -3,11 +3,14 @@ from __future__ import annotations from androidtvremote2 import AndroidTVRemote +from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant 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.""" return AndroidTVRemote( 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"), host=host, 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) diff --git a/homeassistant/components/androidtv_remote/strings.json b/homeassistant/components/androidtv_remote/strings.json index 983c604370b..dbbf6a2d383 100644 --- a/homeassistant/components/androidtv_remote/strings.json +++ b/homeassistant/components/androidtv_remote/strings.json @@ -34,5 +34,14 @@ "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "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." + } + } + } } } diff --git a/tests/components/androidtv_remote/test_config_flow.py b/tests/components/androidtv_remote/test_config_flow.py index ec368081a95..4e0067152e7 100644 --- a/tests/components/androidtv_remote/test_config_flow.py +++ b/tests/components/androidtv_remote/test_config_flow.py @@ -857,3 +857,59 @@ async def test_reauth_flow_cannot_connect( await hass.async_block_till_done() assert len(mock_unload_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