title: Combine WiFi Specifier and Suggestion APIs on Android Q
body: <p>I am building an app where the connection to a 2nd device is the essence. Therefore, I used the <a href="https://developer.android.com/guide/topics/connectivity/wifi-bootstrap" rel="nofollow noreferrer">WifiNetworkSpecifier</a> API. However, the application must be able to automatically reconnect to the target network once the users leave and return to the Wi-Fi perimeter. Thus, I used the <a href="https://developer.android.com/guide/topics/connectivity/wifi-suggest" rel="nofollow noreferrer">WifiNetworkSuggestion</a> API. However, I am experiencing several issues there:</p>
  <ol>
  <li>Once I get connected to the SSID using the specifier API and I confirm the push notification generated by the suggestion API, the suggestion API does not seem to work until I manually disconnect from the SSID (unregister network callback previously assigned to the specifier request) or kill the application.</li>
  <li>If there is another network present in the perimeter which the user previously connected to by using the OS Wi-Fi manager (a hotspot, for instance), Android will prioritize this network, hence the suggestion API for my application would never auto-reconnect to the wanted and accessible SSID.</li>
  </ol>
  <p>From my experience and understanding (which might be wrong) so far, it seems like we have to manually unregister the network callback previously assigned to the specifier request, or kill the application, and let the suggestion API to do its thing until it can work properly. This might be problematic if there are other networks (which the user previously connected to by using the OS Wi-Fi manager) present in the perimeter. In this case, we'd never auto-reconnect to the SSID defined by the application and the suggestion API would never work.</p>
  <p>The question is: how to combine those two APIs to be able to connect to an SSID, yet auto-reconnect, without doing such ugly hacks as manually disconnecting the user, or killing the application, which also doesn't give us any guarantees?</p>
  <p>In my opinion, this whole new implementation with the new network APIs is not done well, it's creating a lot of issues and restrictions for developers, or at least it's poorly documented.</p>
  <p>Here's the code used for making the requests. Note that the device I'm connecting to does not have actual internet access, it's just used as a p2p network.</p>
  <pre class="lang-kotlin prettyprint-override"><code>@RequiresApi(api = Build.VERSION_CODES.Q)
  private fun connectToWiFiOnQ(wifiCredentials: WifiCredentials, onUnavailable: () -&gt; Unit) {
  
      val request = NetworkRequest.Builder()
          .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
          .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
          .setNetworkSpecifier(createWifiNetworkSpecifier(wifiCredentials))
          .build()
  
      networkCallback = object : ConnectivityManager.NetworkCallback() {
          override fun onAvailable(network: Network) {
              super.onAvailable(network)
              connectivityManager.bindProcessToNetwork(network)
          }
  
          override fun onUnavailable() {
              super.onUnavailable()
              onUnavailable.invoke()
          }
      }
  
      networkCallback?.let {
          addNetworkSuggestion(wifiCredentials)
          connectivityManager.requestNetwork(request, it)
      }
  }
  
  @RequiresApi(api = Build.VERSION_CODES.Q)
  private fun addNetworkSuggestion(wifiCredentials: WifiCredentials) {
      wifiManager.addNetworkSuggestions(listOf(createWifiNetworkSuggestion(wifiCredentials))).apply {
          if (this != WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) {
              if (this == WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP) {
                  wifiManager.removeNetworkSuggestions(emptyList())
                  addNetworkSuggestion(wifiCredentials)
              }
          }
      }
  
      suggestionBroadcastReceiver?.let { context.unregisterReceiver(it) }
      suggestionBroadcastReceiver = object : BroadcastReceiver() {
          override fun onReceive(context: Context?, intent: Intent?) {
              if (intent?.action != WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION)
                  return
  
              // Post connection processing..
          }
      }
  
      context.registerReceiver(
          suggestionBroadcastReceiver, IntentFilter(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION)
      )
  }
  
  @RequiresApi(api = Build.VERSION_CODES.Q)
  private fun createWifiNetworkSpecifier(wifiCredentials: WifiCredentials): WifiNetworkSpecifier {
      return when (wifiCredentials.authenticationType.toLowerCase()) {
          WifiCipherType.NOPASS.name.toLowerCase() -&gt; WifiNetworkSpecifier.Builder()
              .setSsid(wifiCredentials.networkSSID)
              .setIsHiddenSsid(wifiCredentials.isSSIDHidden)
              .build()
          WifiCipherType.WPA.name.toLowerCase() -&gt; WifiNetworkSpecifier.Builder()
              .setSsid(wifiCredentials.networkSSID)
              .setWpa2Passphrase(wifiCredentials.password)
              .setIsHiddenSsid(wifiCredentials.isSSIDHidden)
              .build()
          else -&gt; WifiNetworkSpecifier.Builder()
              .setSsid(wifiCredentials.networkSSID)
              .setIsHiddenSsid(wifiCredentials.isSSIDHidden)
              .build()
      }
  }
  
  @RequiresApi(api = Build.VERSION_CODES.Q)
  private fun createWifiNetworkSuggestion(wifiCredentials: WifiCredentials): WifiNetworkSuggestion {
      return when (wifiCredentials.authenticationType.toLowerCase()) {
          WifiCipherType.NOPASS.name.toLowerCase() -&gt; WifiNetworkSuggestion.Builder()
              .setSsid(wifiCredentials.networkSSID)
              .setIsHiddenSsid(wifiCredentials.isSSIDHidden)
              .build()
          WifiCipherType.WPA.name.toLowerCase() -&gt; WifiNetworkSuggestion.Builder()
              .setSsid(wifiCredentials.networkSSID)
              .setWpa2Passphrase(wifiCredentials.password)
              .setIsHiddenSsid(wifiCredentials.isSSIDHidden)
              .build()
          else -&gt; WifiNetworkSuggestion.Builder()
              .setSsid(wifiCredentials.networkSSID)
              .setIsHiddenSsid(wifiCredentials.isSSIDHidden)
              .build()
      }
  }
  </code></pre>