From c55caabbffeee93f70535d613f7f5982dd548bd9 Mon Sep 17 00:00:00 2001
From: "J. Nick Koston" <nick@koston.org>
Date: Tue, 28 Jan 2025 13:05:53 -1000
Subject: [PATCH] Abort Bluetooth options flow if local adapters do not support
 passive scans (#136748)

---
 .../components/bluetooth/config_flow.py       | 18 ++++++++++++-
 .../components/bluetooth/strings.json         |  3 ++-
 .../components/bluetooth/test_config_flow.py  | 27 +++++++++++++++++++
 3 files changed, 46 insertions(+), 2 deletions(-)

diff --git a/homeassistant/components/bluetooth/config_flow.py b/homeassistant/components/bluetooth/config_flow.py
index 5bfe5e7089c..6425aabe12f 100644
--- a/homeassistant/components/bluetooth/config_flow.py
+++ b/homeassistant/components/bluetooth/config_flow.py
@@ -211,10 +211,16 @@ class BluetoothConfigFlow(ConfigFlow, domain=DOMAIN):
     @callback
     def async_get_options_flow(
         config_entry: ConfigEntry,
-    ) -> SchemaOptionsFlowHandler | RemoteAdapterOptionsFlowHandler:
+    ) -> (
+        SchemaOptionsFlowHandler
+        | RemoteAdapterOptionsFlowHandler
+        | LocalNoPassiveOptionsFlowHandler
+    ):
         """Get the options flow for this handler."""
         if CONF_SOURCE in config_entry.data:
             return RemoteAdapterOptionsFlowHandler()
+        if not (manager := get_manager()) or not manager.supports_passive_scan:
+            return LocalNoPassiveOptionsFlowHandler()
         return SchemaOptionsFlowHandler(config_entry, OPTIONS_FLOW)
 
     @classmethod
@@ -232,3 +238,13 @@ class RemoteAdapterOptionsFlowHandler(OptionsFlow):
     ) -> ConfigFlowResult:
         """Handle options flow."""
         return self.async_abort(reason="remote_adapters_not_supported")
+
+
+class LocalNoPassiveOptionsFlowHandler(OptionsFlow):
+    """Handle a option flow for local adapters with no passive support."""
+
+    async def async_step_init(
+        self, user_input: dict[str, Any] | None = None
+    ) -> ConfigFlowResult:
+        """Handle options flow."""
+        return self.async_abort(reason="local_adapters_no_passive_support")
diff --git a/homeassistant/components/bluetooth/strings.json b/homeassistant/components/bluetooth/strings.json
index 1b8231c66ca..5f9a380d631 100644
--- a/homeassistant/components/bluetooth/strings.json
+++ b/homeassistant/components/bluetooth/strings.json
@@ -35,7 +35,8 @@
       }
     },
     "abort": {
-      "remote_adapters_not_supported": "Bluetooth configuration for remote adapters is not supported."
+      "remote_adapters_not_supported": "Bluetooth configuration for remote adapters is not supported.",
+      "local_adapters_no_passive_support": "Local Bluetooth adapters that do not support passive scanning cannot be configured."
     }
   }
 }
diff --git a/tests/components/bluetooth/test_config_flow.py b/tests/components/bluetooth/test_config_flow.py
index abb3a5e2393..0070bebe4b6 100644
--- a/tests/components/bluetooth/test_config_flow.py
+++ b/tests/components/bluetooth/test_config_flow.py
@@ -487,6 +487,33 @@ async def test_options_flow_remote_adapter(hass: HomeAssistant) -> None:
     assert result["reason"] == "remote_adapters_not_supported"
 
 
+@pytest.mark.usefixtures(
+    "one_adapter", "mock_bleak_scanner_start", "mock_bluetooth_adapters"
+)
+async def test_options_flow_local_no_passive_support(hass: HomeAssistant) -> None:
+    """Test options are not available for local adapters without passive support."""
+    source_entry = MockConfigEntry(
+        domain="test",
+    )
+    source_entry.add_to_hass(hass)
+    entry = MockConfigEntry(
+        domain=DOMAIN,
+        data={},
+        options={},
+        unique_id="BB:BB:BB:BB:BB:BB",
+    )
+    entry.add_to_hass(hass)
+
+    await hass.config_entries.async_setup(entry.entry_id)
+    await hass.async_block_till_done()
+
+    _get_manager()._adapters["hci0"]["passive_scan"] = False
+
+    result = await hass.config_entries.options.async_init(entry.entry_id)
+    assert result["type"] is FlowResultType.ABORT
+    assert result["reason"] == "local_adapters_no_passive_support"
+
+
 @pytest.mark.usefixtures("one_adapter")
 async def test_async_step_user_linux_adapter_is_ignored(hass: HomeAssistant) -> None:
     """Test we give a hint that the adapter is ignored."""
-- 
GitLab