0
1

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

Last updated at Posted at 2024-06-02

背景

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

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

WiFi P2Pで忘れがちな点まとめ

  • AndroidManifest.xmlに権限を設定
<maninfest ...>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    
    ...
    
    <application
        ...
        android:usesCleartextTraffic="true"
    >
    ...

実際のコード(Kotlin)

別途AndroidManifest.xmlの設定もすること。

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
)
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1