Back to blog

Android Will Allow Dual-Band Wi-Fi Router Configuration: What This Means For IoT Developers

Hello HaWkers, a new Android feature is about to significantly make life easier for those developing Internet of Things (IoT) and smart home applications: the operating system will gain native support for configuring dual-band Wi-Fi routers directly from the phone.

If you have ever tried to create an app that configures IoT devices via Wi-Fi, you know the headache of dealing with different frequency bands. This news could change the game.

What Is Coming

Google is adding native APIs to Android that will allow applications to programmatically configure dual-band Wi-Fi routers. This means developers will be able to create much smoother setup experiences for connected devices.

Main Features

What will be possible:

  • Configure SSID and password for 2.4GHz and 5GHz bands separately
  • Switch between bands automatically based on requirements
  • Manage QoS (Quality of Service) settings
  • Configure guest networks
  • Integrate with Matter and Thread for smart home

Expected APIs:

  • Enhanced WifiNetworkConfigBuilder
  • New intents for router configuration
  • Wi-Fi Easy Connect (DPP 2.0) support
  • Integration with Local Network Access APIs

Why This Matters For IoT

IoT device configuration has always been one of the biggest friction points in user experience. With these new APIs, several common problems can be solved.

Current IoT Setup Problems

Common difficulties:

  1. IoT devices only work on 2.4GHz, but phone connects to 5GHz
  2. User needs to manually switch networks during setup
  3. Apps need invasive permissions to manage Wi-Fi
  4. Configuration fails silently without clear feedback
  5. Different manufacturers use proprietary protocols

How New APIs Solve This

Problem Solution with New APIs
Incorrect band Automatic band selection
Manual network switch API manages temporary connection
Invasive permissions More limited and secure scope
Silent failures Detailed error callbacks
Proprietary protocols Unified Android standard

Impact on App Development

For mobile and IoT developers, these changes bring significant opportunities.

New Possibilities

Smart home apps:

  • Device setup in seconds
  • Batch configuration of multiple devices
  • Automatic migration between networks
  • Connection problem diagnostics

Network configuration apps:

  • More granular parental controls
  • Device prioritization
  • Connection quality analysis
  • Automatic band optimization

Implementation Example

Let us see how it will be possible to use these new APIs to configure an IoT device:

Basic IoT Device Setup

// MainActivity.kt - IoT device configuration
class IoTDeviceSetupActivity : AppCompatActivity() {

    private lateinit var wifiManager: WifiManager
    private lateinit var connectivityManager: ConnectivityManager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_setup)

        wifiManager = getSystemService(Context.WIFI_SERVICE) as WifiManager
        connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE)
            as ConnectivityManager

        setupIoTDevice()
    }

    private fun setupIoTDevice() {
        // Configure specific network for IoT (2.4GHz)
        val iotNetworkRequest = NetworkRequest.Builder()
            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
            .setNetworkSpecifier(
                WifiNetworkSpecifier.Builder()
                    .setSsid("IoT_Device_AP")
                    .setWpa2Passphrase("setup_password")
                    .setBand(WifiNetworkSpecifier.BAND_24GHZ) // Force 2.4GHz
                    .build()
            )
            .build()

        // Connection callback
        val networkCallback = object : ConnectivityManager.NetworkCallback() {
            override fun onAvailable(network: Network) {
                super.onAvailable(network)
                // Connected to IoT device AP
                runOnUiThread {
                    showStatus("Connected to device")
                    configureDeviceWifi(network)
                }
            }

            override fun onUnavailable() {
                super.onUnavailable()
                runOnUiThread {
                    showError("Failed to connect to device")
                }
            }
        }

        // Request connection
        connectivityManager.requestNetwork(iotNetworkRequest, networkCallback)
    }

    private fun configureDeviceWifi(network: Network) {
        // Send home network configuration to device
        val homeNetworkConfig = HomeNetworkConfig(
            ssid = "MyWifiNetwork",
            password = "secure_password",
            preferredBand = Band.AUTO, // Let device choose
            securityType = SecurityType.WPA3
        )

        // Use socket bound to device network
        network.bindSocket(socket)

        // Send configuration via HTTP/CoAP/MQTT
        sendConfigToDevice(homeNetworkConfig)
    }

    private fun showStatus(message: String) {
        binding.statusText.text = message
    }

    private fun showError(message: String) {
        binding.statusText.text = "Error: $message"
    }
}

// Data class for configuration
data class HomeNetworkConfig(
    val ssid: String,
    val password: String,
    val preferredBand: Band,
    val securityType: SecurityType
)

enum class Band { AUTO, BAND_24GHZ, BAND_5GHZ, BAND_6GHZ }
enum class SecurityType { WPA2, WPA3, OPEN }

Device Discovery with mDNS

// DeviceDiscoveryManager.kt
class DeviceDiscoveryManager(private val context: Context) {

    private val nsdManager: NsdManager =
        context.getSystemService(Context.NSD_SERVICE) as NsdManager

    private val discoveredDevices = mutableListOf<IoTDevice>()

    fun startDiscovery(onDeviceFound: (IoTDevice) -> Unit) {
        val discoveryListener = object : NsdManager.DiscoveryListener {
            override fun onDiscoveryStarted(regType: String) {
                Log.d(TAG, "Discovery started")
            }

            override fun onServiceFound(service: NsdServiceInfo) {
                // Filter by IoT service type
                if (service.serviceType == SERVICE_TYPE) {
                    nsdManager.resolveService(service, createResolveListener(onDeviceFound))
                }
            }

            override fun onServiceLost(service: NsdServiceInfo) {
                discoveredDevices.removeIf { it.name == service.serviceName }
            }

            override fun onDiscoveryStopped(serviceType: String) {
                Log.d(TAG, "Discovery stopped")
            }

            override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
                Log.e(TAG, "Failed to start discovery: $errorCode")
            }

            override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
                Log.e(TAG, "Failed to stop discovery: $errorCode")
            }
        }

        nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener)
    }

    private fun createResolveListener(
        onDeviceFound: (IoTDevice) -> Unit
    ): NsdManager.ResolveListener {
        return object : NsdManager.ResolveListener {
            override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
                Log.e(TAG, "Failed to resolve: $errorCode")
            }

            override fun onServiceResolved(serviceInfo: NsdServiceInfo) {
                val device = IoTDevice(
                    name = serviceInfo.serviceName,
                    host = serviceInfo.host,
                    port = serviceInfo.port,
                    attributes = serviceInfo.attributes
                )
                discoveredDevices.add(device)
                onDeviceFound(device)
            }
        }
    }

    companion object {
        private const val TAG = "DeviceDiscovery"
        private const val SERVICE_TYPE = "_iot._tcp."
    }
}

data class IoTDevice(
    val name: String,
    val host: InetAddress,
    val port: Int,
    val attributes: Map<String, ByteArray>
)

Matter Integration

// MatterDeviceSetup.kt
class MatterDeviceSetup(private val context: Context) {

    private val commissioningClient: CommissioningClient =
        Matter.getCommissioningClient(context)

    suspend fun setupMatterDevice(
        deviceInfo: MatterDeviceInfo,
        wifiCredentials: WifiCredentials
    ): Result<CommissionedDevice> {
        return try {
            // Start commissioning process
            val commissioningRequest = CommissioningRequest.builder()
                .setCommissioningService(ComponentName(context, MatterCommissioningService::class.java))
                .setWifiCredentials(
                    WifiCredentials.Builder()
                        .setSsid(wifiCredentials.ssid)
                        .setPassword(wifiCredentials.password)
                        .build()
                )
                .setOnboardingPayload(deviceInfo.qrCode)
                .build()

            val result = commissioningClient
                .commissionDevice(commissioningRequest)
                .await()

            Result.success(result)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    // Service for background commissioning
    class MatterCommissioningService : Service() {
        override fun onBind(intent: Intent): IBinder? = null

        override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
            // Process commissioning
            return START_NOT_STICKY
        }
    }
}

data class MatterDeviceInfo(
    val qrCode: String,
    val manualCode: String,
    val vendorId: Int,
    val productId: Int
)

data class WifiCredentials(
    val ssid: String,
    val password: String
)

Best Practices For IoT Apps

1. Robust Error Handling

sealed class SetupError {
    object NetworkNotFound : SetupError()
    object AuthenticationFailed : SetupError()
    object DeviceUnreachable : SetupError()
    object ConfigurationFailed : SetupError()
    data class Unknown(val message: String) : SetupError()
}

fun handleSetupError(error: SetupError): String {
    return when (error) {
        is SetupError.NetworkNotFound ->
            "Wi-Fi network not found. Check the network name."
        is SetupError.AuthenticationFailed ->
            "Incorrect password. Try again."
        is SetupError.DeviceUnreachable ->
            "Device not responding. Restart it and try again."
        is SetupError.ConfigurationFailed ->
            "Configuration failed. Try again in a few seconds."
        is SetupError.Unknown ->
            "Error: ${error.message}"
    }
}

2. Progressive Setup UX

enum class SetupStep {
    SCANNING,
    CONNECTING_TO_DEVICE,
    SENDING_CONFIG,
    WAITING_DEVICE_CONNECT,
    VERIFYING,
    COMPLETE
}

@Composable
fun SetupProgressUI(currentStep: SetupStep) {
    Column(
        modifier = Modifier.fillMaxWidth(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = when (currentStep) {
                SetupStep.SCANNING -> "Searching for device..."
                SetupStep.CONNECTING_TO_DEVICE -> "Connecting to device..."
                SetupStep.SENDING_CONFIG -> "Sending configuration..."
                SetupStep.WAITING_DEVICE_CONNECT -> "Waiting for device to connect..."
                SetupStep.VERIFYING -> "Verifying connection..."
                SetupStep.COMPLETE -> "Setup complete!"
            },
            style = MaterialTheme.typography.headlineSmall
        )

        Spacer(modifier = Modifier.height(16.dp))

        LinearProgressIndicator(
            progress = { (currentStep.ordinal + 1).toFloat() / SetupStep.entries.size },
            modifier = Modifier.fillMaxWidth()
        )
    }
}

Compatibility and Requirements

Supported Versions

Feature Minimum Android API Level
Wi-Fi Network Suggestion Android 10 29
Wi-Fi Network Specifier Android 10 29
Wi-Fi Easy Connect Android 10 29
Dual-Band Config (new) Android 16 36
Matter Integration Android 13 33

Required Permissions

<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
<uses-permission android:name="android.permission.INTERNET" />

<!-- For Matter -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

Conclusion

Android's native support for dual-band Wi-Fi router configuration is an important evolution for the IoT ecosystem. For developers, this means less boilerplate code, better user experience, and higher success rates in connected device setups.

If you work with mobile or IoT development, it is worth starting to explore these APIs and planning how to integrate them into your projects. The future of smart home depends on frictionless configuration experiences.

To dive deeper into mobile and IoT development, I recommend checking out the article on JavaScript and the World of IoT: Integrating the Web into the Physical Environment where we explore how to use JavaScript to create connected solutions.

Let's go! 🦅

Comments (0)

This article has no comments yet 😢. Be the first! 🚀🦅

Add comments