Android Q (29)以上でWiFi P2Pをする際にbindProcessToNetworkが失敗する場合の解決

# 背景
Android Q以降から、セキュリティ強化の関係でWiFi接続の切り替えが制限された。
インターネットに接続する目的では接続先WiFiをプログラムから制御することができなくなり、デバイスとのP2P (ネットワークへの接続が不可)に限られるようになった。
さらに、接続したネットワークにアクセスできるのも接続したアプリ内に限られる。

この操作はWifiNetworkSpecifierを介して実行できるが、Switchの「スマートフォンに送る」から画像を取得しようとして、数時間ハマったので陥りやすい点をまとめた。


# WiFi P2Pで忘れがちな点まとめ
* AndroidManifest.xmlに権限を設定
```xml
<maninfest ...>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    
    ...
    
    <application
        ...
        android:usesCleartextTraffic="true"
    >
    ...
```
* たまに権限の追加が反映されないため、その場合はAndroid StudioのBuild → Clean Projectをした後、エミュレータ or テスト端末から一度アンインストールする。
* 解説ページによって記載漏れしているが、`bindProcessToNetwork`をcallback内で実行。
* VPN接続していると、`bindProcessToNetwork`がfalseを返して失敗する(**これがどこにも記載がなくてハマった**)
* そもそも一部OEM ROM (OPPO, OnePlus, Samsungの一部端末)でWiFiを維持できない不具合がある模様です(以下ページ; 未確認)
    - https://stackoverflow.com/questions/60660236/android-q-wifi-connection-via-wifinetworkspecifier-lose-connection-immediately-a
    - https://issuetracker.google.com/issues/143549613#comment14

# 実際のコード(Kotlin)
別途AndroidManifest.xmlの設定もすること。
```kotlin
val wifiNetworkSpecifier = WifiNetworkSpecifier.Builder()
    .setSsid(ssid)
    .setWpa2Passphrase(password)
    .build()

val networkRequest = NetworkRequest.Builder()
    .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
    .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    .setNetworkSpecifier(wifiNetworkSpecifier)
    .build()

// contextはapplicationContextなり、CurrentLocal.contextなりで取得
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkCallback = object : ConnectivityManager.NetworkCallback() {
    override fun onAvailable(network: Network) {
        super.onAvailable(network)

        // これを実行することで、URL#openConnectionが全て設定したnetworkを介するようになる。
        // VPNがONの場合など、実行に失敗するとfalseが返り、openConnectionも失敗する。
        connectivityManager.bindProcessToNetwork(network)
        
        CoroutineScope(Dispatchers.IO).launch {
            val url = "http://example.com/"
            
            val connection = URL(url).openConnection() as HttpURLConnection
            // ここで、URL#openConnectionではなく、
            // val connection = network.openConnection(URL(url)) as HttpURLConnection
            // だと、接続できないため注意
            
            try{
                val statusCode = connection.responseCode
                if(statusCode){
                    // 以下はhtmlなどのテキスト内容をそのまま取得する場合
                    val str = connection.inputStream.bufferedReader(Charsets.UTF_8)
                        .use { br ->
                            br.readLines().joinToString("")
                        }
                    println("Reponse: $str")
                } else {
                    println("Request Failed: $status")
                }
                
            } catch(e: IOException) {
                e.printStackTrace()
            }
            // 接続の解除。モバイル回線などPrimary回線につながるようになる
            connectivityManager.bindProcessToNetwork(null)
            // WiFi接続の解除。基本的には元々接続されていたWiFiに自動でつながるはず。
            connectivityManager.unregisterNetworkCallback(this)
        }
    }
    override fun onUnavailable() {
        super.onUnavailable()
        println("NetworkCallback: Unavailable")
    }
    override fun onLost(network: Network) {
        super.onLost(network)
        println("NetworkCallback: Lost")
    }
}

connectivityManager.requestNetwork(
    networkRequest,
    networkCallback
)
```