From 1e90ef3c789d211a4c61a41c542c8bffb19b392a Mon Sep 17 00:00:00 2001 From: tateisu Date: Tue, 11 Sep 2018 21:39:28 +0900 Subject: [PATCH] =?UTF-8?q?=E3=83=AA=E3=83=95=E3=82=A1=E3=82=AF=E3=82=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../juggler/fadownloader/targets/FlashAir.kt | 2 +- .../fadownloader/targets/PqiAirCard.kt | 2 +- .../fadownloader/tracker/NetworkTracker.kt | 993 ++++++++++-------- 3 files changed, 529 insertions(+), 468 deletions(-) diff --git a/app/src/main/java/jp/juggler/fadownloader/targets/FlashAir.kt b/app/src/main/java/jp/juggler/fadownloader/targets/FlashAir.kt index 17b930b..e7903d8 100644 --- a/app/src/main/java/jp/juggler/fadownloader/targets/FlashAir.kt +++ b/app/src/main/java/jp/juggler/fadownloader/targets/FlashAir.kt @@ -273,7 +273,7 @@ class FlashAir(private val service : DownloadService, internal val thread : Down if(thread.target_type == Pref.TARGET_TYPE_FLASHAIR_STA) { while(! thread.isCancelled) { - val tracker_last_result = service.wifi_tracker.bLastConnected.get() + val tracker_last_result = service.wifi_tracker.bLastConnected val air_url = service.wifi_tracker.lastTargetUrl.get() if(tracker_last_result && air_url.isNotEmpty() ) { thread.target_url = air_url diff --git a/app/src/main/java/jp/juggler/fadownloader/targets/PqiAirCard.kt b/app/src/main/java/jp/juggler/fadownloader/targets/PqiAirCard.kt index d9b5895..0949e90 100644 --- a/app/src/main/java/jp/juggler/fadownloader/targets/PqiAirCard.kt +++ b/app/src/main/java/jp/juggler/fadownloader/targets/PqiAirCard.kt @@ -298,7 +298,7 @@ class PqiAirCard( if(thread.target_type == Pref.TARGET_TYPE_PQI_AIR_CARD_TETHER) { while(! thread.isCancelled) { - val tracker_last_result = service.wifi_tracker.bLastConnected.get() + val tracker_last_result = service.wifi_tracker.bLastConnected val air_url = service.wifi_tracker.lastTargetUrl.get() if(tracker_last_result && air_url.isNotEmpty()) { thread.target_url = air_url diff --git a/app/src/main/java/jp/juggler/fadownloader/tracker/NetworkTracker.kt b/app/src/main/java/jp/juggler/fadownloader/tracker/NetworkTracker.kt index 5125b46..2378350 100644 --- a/app/src/main/java/jp/juggler/fadownloader/tracker/NetworkTracker.kt +++ b/app/src/main/java/jp/juggler/fadownloader/tracker/NetworkTracker.kt @@ -17,10 +17,15 @@ import jp.juggler.fadownloader.R import jp.juggler.fadownloader.util.* import java.net.* import java.util.* -import java.util.concurrent.atomic.AtomicBoolean import java.util.regex.Pattern import kotlin.math.min +/* + DownloadService のonCreateで作られ、onDestroy でdisposeされる + 生存中は何かしらのタイミングでworkerスレッドがnotifyされて処理を開始する + 通信状態を確認・変更を行ってから適当な時間待機、を繰り返す +*/ + class NetworkTracker( internal val context : Context, internal val log : LogWriter, @@ -44,125 +49,127 @@ class NetworkTracker( } - interface Callback{ + interface Callback { fun onConnectionStatus(is_connected : Boolean, cause : String) fun onTetheringOff() } - internal class NetworkStatus( - var type_name : String, - var sub_name : String? = null, - var is_active : Boolean = false, - var strWifiStatus : String? = null - ) - - internal class NetworkStateList : ArrayList() { + // 設定。初期状態は無害な値にすること + class Setting( + val force_wifi : Boolean = false, + val target_ssid : String = "", + val target_type : Int = 0, + val target_url : String = "http://flashair/", - var wifi_status : NetworkStatus? = null - var other_active : String = "" + val tetherSprayInterval : Long = 1000L, + val tetherTestConnectionTimeout : Long = 1000L, + val wifiChangeApInterval : Long = 1000L, + val wifiScanInterval : Long = 1000L, - fun addNetworkInfo(is_active : Boolean, ni : NetworkInfo?) { - ni ?: return - - val is_wifi = ni.type == ConnectivityManager.TYPE_WIFI - - // Wi-Fiでもなく接続中でもないなら全くの無関係 - if(! is_wifi && ! ni.isConnected) return - - val ns = NetworkStatus( - type_name = ni.typeName, - sub_name = ni.subtypeName, - is_active = is_active - ) - this.add(ns) - - if(is_wifi) { - wifi_status = ns - } else if(is_active) { - other_active = ns.type_name - } + val stopWhenTetheringOff : Boolean = false + ) + + @Volatile + private var worker : Worker? = null + + @Volatile + private var setting : Setting = Setting() + + @Volatile + private var timeLastSpray = 0L + + @Volatile + private var timeLastWiFiScan : Long = 0 + + @Volatile + private var timeLastWiFiApChange : Long = 0 + + @Volatile + private var timeLastTargetDetected = 0L + + @Volatile + var bLastConnected : Boolean = false + + private val testerMap = HashMap() + + val lastTargetUrl = AtomicReferenceNotNull("") + + private var last_force_status = AtomicReferenceNotNull("") + private var last_error_status = AtomicReferenceNotNull("") + private var last_current_status = AtomicReferenceNotNull("") + private var last_other_active = AtomicReferenceNotNull("") + + val otherActive : String + get() = last_other_active.get() + + private val isDisposed : Boolean + get() = worker != null + + private val wifiManager = + context.applicationContext.getSystemService(Context.WIFI_SERVICE) + as WifiManager + + private val connectivityManager = + context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) + as ConnectivityManager + + init { + context.registerReceiver(this, IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) + context.registerReceiver(this, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) + context.registerReceiver(this, IntentFilter(TETHER_STATE_CHANGED)) + worker = Worker().apply { + start() } + } + + fun dispose() { + context.unregisterReceiver(this) + worker?.cancel("disposed") + worker = null + } + + // 設定の変化 + fun updateSetting(setting : Setting) { + if(isDisposed) return - fun afterAddAll() { - if(wifi_status == null) { - val ws = NetworkStatus(type_name = "WIFI") - wifi_status = ws - this.add(ws) - } - } + this.setting = setting - internal fun buildCurrentStatus() : String { - - sortWith(Comparator { a, b -> - if(a.is_active && ! b.is_active) { - - 1 - } else if(! a.is_active && b.is_active) { - 1 - } else { - a.type_name.compareTo(b.type_name) - } - }) - - val sb = StringBuilder() - for(ns in this) { - if(sb.isNotEmpty()) sb.append(" / ") - if(ns.is_active) sb.append("(Active)") - if(ns.strWifiStatus != null) { - sb.append("Wi-Fi(").append(ns.strWifiStatus).append(')') - } else { - sb.append(ns.type_name) - val sub_name = ns.sub_name - if(sub_name?.isNotEmpty() == true) { - sb.append('(').append(sub_name).append(')') - } - } - } - return sb.toString() - } + timeLastTargetDetected = 0L + timeLastSpray = 0L + timeLastWiFiApChange = 0L + timeLastWiFiScan = 0L + lastTargetUrl.set("") - var force_status : String? = null - var error_status : String? = null + worker?.notifyEx() } - // API 26以降でpriorityは使えなくなった - @Suppress("DEPRECATION") - private fun getPriority(wc : WifiConfiguration) : Int { - return wc.priority + // ネットワーク状態の表示 + fun getStatus() : String { + return last_current_status.get() } - // API 26以降でpriorityは使えなくなった - @Suppress("DEPRECATION") - private fun updatePriority( - target_config : WifiConfiguration, - priority_max : Int - ) : String? { - try { - val priority_list = LinkedList() - - // priority の変更 - val p = target_config.priority - if(p != priority_max) { - priority_list.add(p) - if(priority_list.size > 5) priority_list.removeFirst() - if(priority_list.size < 5 || priority_list.first.toInt() != priority_list.last.toInt()) { - // まだ上がるか試してみる - target_config.priority = priority_max + 1 - wifiManager.updateNetwork(target_config) - wifiManager.saveConfiguration() - ////頻出するのでログ出さない log.d( R.string.wifi_ap_priority_changed ); + // WiFiスキャン完了、ネットワーク接続状態の変化、テザリング状態の変化などのブロードキャスト受信イベント + override fun onReceive(context : Context, intent : Intent) { + + if(intent.action == TETHER_STATE_CHANGED) { + val sb = StringBuilder("TETHER_STATE_CHANGED. ") + val extras = intent.extras + for(key in extras.keySet()) { + val v = extras[key] + when(v) { + is ArrayList<*> -> sb.append("$key=[${v.joinToString("/")}],") + is Array<*> -> sb.append("$key=[${v.joinToString("/")}],") + else -> sb.append("$key=$v,") } } - } catch(ex : Throwable) { - log.trace(ex, "updateNetwork() or saveConfiguration() failed.") - return ex.withCaption("updateNetwork() or saveConfiguration() failed.") + log.d(sb.toString()) } - return null + + worker?.notifyEx() } - private val wifiManager = - context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager - private val connectivityManager = - context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + /////////////////////////////////////////////////////////////////////////// + // テザリングモード用のユーティリティ private val isTetheringEnabled : Boolean get() { @@ -208,119 +215,155 @@ class NetworkTracker( // ネットワークアドレス(XXX.XXX.XXX.XXX) と ネットマスク(XXX.XXX.XXX.)を指定してUDPパケットをばら撒く private fun sprayUDPPacket(nw_addr : String, ip_base : String) { - val start = SystemClock.elapsedRealtime() try { - val data = ByteArray(1) - val port = 80 - val socket = DatagramSocket() - for(n in 2 .. 254) { - val try_ip = "$ip_base$n" - if(try_ip == nw_addr) continue - try { - val packet = DatagramPacket( - data, - data.size, - InetAddress.getByName(try_ip), - port - ) - socket.send(packet) - } catch(ex : Throwable) { - log.trace(ex, "sprayUDPPacket") - } + val timeStart = SystemClock.elapsedRealtime() + + DatagramSocket().use { socket -> + + val data = ByteArray(1) + for(n in 2 .. 254) { + val try_ip = "$ip_base$n" + if(try_ip == nw_addr) continue + try { + val packet = DatagramPacket( + data, + data.size, + InetAddress.getByName(try_ip), + 80 + ) + socket.send(packet) + } catch(ex : Throwable) { + log.trace(ex, "DatagramPacket.send failed") + } + + } } - socket.close() + + val time = SystemClock.elapsedRealtime() - timeStart + log.v("sent UDP packet to '$ip_base*' (${time}ms)") + } catch(ex : Throwable) { - log.trace(ex, "sprayUDPPacket") + log.trace(ex, "sprayUDPPacket failed.") + log.e(ex, "sprayUDPPacket failed.") } - - log.v("sent UDP packet to '$ip_base*' time=${Utils.formatTimeDuration(SystemClock.elapsedRealtime() - start)}") } - class Setting( - val force_wifi : Boolean = false, - val target_ssid : String = "", - val target_type : Int = 0, - val target_url : String = "http://flashair/", - - val tetherSprayInterval : Long = 1000L, - val tetherTestConnectionTimeout : Long = 1000L, - val wifiChangeApInterval : Long = 1000L, - val wifiScanInterval : Long = 1000L, - - val stopWhenTetheringOff: Boolean =false - ) - - private var is_dispose = false - - private var worker : Worker? = null - - private var setting : Setting = Setting() - - private var timeLastSpray = 0L - private var timeLastWiFiScan : Long = 0 - private var timeLastWiFiApChange : Long = 0 - private var timeLastTargetDetected = 0L - - private val testerMap = HashMap() - val bLastConnected = AtomicBoolean() - val lastTargetUrl = AtomicReferenceNotNull("") - - private var last_force_status = AtomicReferenceNotNull("") - private var last_error_status = AtomicReferenceNotNull("") - private var last_current_status = AtomicReferenceNotNull("") - private var last_other_active = AtomicReferenceNotNull("") - - val otherActive : String - get() = last_other_active.get() - - init { - context.registerReceiver(this, IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) - context.registerReceiver(this, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) - context.registerReceiver(this, IntentFilter(TETHER_STATE_CHANGED)) - worker = Worker().apply { - start() - } - } + /////////////////////////////////////////////////////////////////////////// + // API 26以降でpriorityは使えなくなった - fun dispose() { - is_dispose = true - context.unregisterReceiver(this) - worker?.cancel("disposed") - worker = null + @Suppress("DEPRECATION") + private fun getPriority(wc : WifiConfiguration) : Int { + return wc.priority } - override fun onReceive(context : Context, intent : Intent) { - if(intent.action == TETHER_STATE_CHANGED) { - val sb = StringBuilder("TETHER_STATE_CHANGED. ") - val extras = intent.extras - for(key in extras.keySet()) { - val v = extras[key] - when(v) { - is ArrayList<*> -> sb.append("$key=[${v.joinToString("/")}],") - is Array<*> -> sb.append("$key=[${v.joinToString("/")}],") - else -> sb.append("$key=$v,") + @Suppress("DEPRECATION") + private fun updatePriority( + target_config : WifiConfiguration, + priority_max : Int + ) : String? { + try { + val priority_list = LinkedList() + + // priority の変更 + val p = target_config.priority + if(p != priority_max) { + priority_list.add(p) + if(priority_list.size > 5) priority_list.removeFirst() + if(priority_list.size < 5 || priority_list.first.toInt() != priority_list.last.toInt()) { + // まだ上がるか試してみる + target_config.priority = priority_max + 1 + wifiManager.updateNetwork(target_config) + wifiManager.saveConfiguration() + ////頻出するのでログ出さない log.d( R.string.wifi_ap_priority_changed ); } } - log.d(sb.toString()) + } catch(ex : Throwable) { + log.trace(ex, "updateNetwork() or saveConfiguration() failed.") + return ex.withCaption("updateNetwork() or saveConfiguration() failed.") } - if(! is_dispose) worker?.notifyEx() + return null } - fun updateSetting(setting : Setting) { - if(is_dispose) return - this.setting = setting - timeLastTargetDetected =0L - timeLastSpray =0L - timeLastWiFiApChange =0L - timeLastWiFiScan = 0L - lastTargetUrl.set("") - worker?.notifyEx() - } + /////////////////////////////////////////////////////////////////////////// - fun getStatus() : String { - return requireNotNull(last_current_status.get()) + // 種別ごとのネットワーク接続の状況 + private class NetworkStatus( + var type_name : String, + var sub_name : String? = null, + var is_active : Boolean = false, + var strWifiStatus : String? = null + ) + + private class NetworkStatusList : ArrayList() { + + var force_status : String? = null + var error_status : String? = null + + var wifi_status : NetworkStatus? = null + + var other_active : String = "" + + fun addNetworkInfo(is_active : Boolean, ni : NetworkInfo?) { + ni ?: return + + val is_wifi = ni.type == ConnectivityManager.TYPE_WIFI + + // Wi-Fiでもなく接続中でもないなら全くの無関係 + if(! is_wifi && ! ni.isConnected) return + + val ns = NetworkStatus( + type_name = ni.typeName, + sub_name = ni.subtypeName, + is_active = is_active + ) + this.add(ns) + + if(is_wifi) { + wifi_status = ns + } else if(is_active) { + other_active = ns.type_name + } + } + + fun afterAddAll() { + if(wifi_status == null) { + val ws = NetworkStatus(type_name = "WIFI") + wifi_status = ws + this.add(ws) + } + } + + fun buildCurrentStatus() : String { + + sortWith(Comparator { a, b -> + if(a.is_active && ! b.is_active) { + - 1 + } else if(! a.is_active && b.is_active) { + 1 + } else { + a.type_name.compareTo(b.type_name) + } + }) + + val sb = StringBuilder() + for(ns in this) { + if(sb.isNotEmpty()) sb.append(" / ") + if(ns.is_active) sb.append("(Active)") + if(ns.strWifiStatus != null) { + sb.append("Wi-Fi(").append(ns.strWifiStatus).append(')') + } else { + sb.append(ns.type_name) + val sub_name = ns.sub_name + if(sub_name?.isNotEmpty() == true) { + sb.append('(').append(sub_name).append(')') + } + } + } + return sb.toString() + } + } private class UrlTester( @@ -345,36 +388,37 @@ class NetworkTracker( var bFound = false var error : Throwable? = null try { + val conn = URL(checkUrl).openConnection() as HttpURLConnection try { - val urlObject = URL(checkUrl) - val conn = urlObject.openConnection() as HttpURLConnection + conn.doInput = true + conn.connectTimeout = setting.tetherTestConnectionTimeout.toInt() + conn.readTimeout = setting.tetherTestConnectionTimeout.toInt() + conn.doOutput = false + conn.connect() + val resCode = conn.responseCode + if(resCode == 200) { + bFound = true + } else { + log.e("HTTP error $resCode. url=$checkUrl") + } + } finally { try { - conn.doInput = true - conn.connectTimeout = setting.tetherTestConnectionTimeout.toInt() - conn.readTimeout = setting.tetherTestConnectionTimeout.toInt() - conn.doOutput = false - conn.connect() - val resCode = conn.responseCode - if(resCode == 200) { - bFound = true - } else { - logStatic.e("HTTP error %s. url=%s", resCode, checkUrl) - } - } finally { - try { - conn.disconnect() - } catch(ignored : Throwable) { - } - + conn.disconnect() + } catch(ignored : Throwable) { } - } catch(ex : Throwable) { - error = ex } + } catch(ex : Throwable) { + error = ex } finally { val time = SystemClock.elapsedRealtime() - timeStart + when(error) { null -> { + if(bFound) callback(this) + } + is InterruptedException -> { + // キャンセル時に発生する。このエラーは報告しない } is ConnectException -> { @@ -395,19 +439,19 @@ class NetworkTracker( log.e(error.withCaption("time=${time}ms, url=$checkUrl")) } } - if(bFound) callback(this) } } } - + private val onUrlTestComplete : (UrlTester) -> Unit = { tester -> synchronized(testerMap) { testerMap.remove(tester.checkUrl) - if(tester.setting.target_type == setting.target_type) { - if(tester.targetUrl != lastTargetUrl.get()) { - log.i("target detected. %s", tester.targetUrl) + if(! isDisposed && tester.setting.target_type == setting.target_type) { + val targetUrl = tester.targetUrl + if(targetUrl != lastTargetUrl.get()) { + log.i("target detected. %s",targetUrl) + lastTargetUrl.set(targetUrl) } - lastTargetUrl.set(tester.targetUrl) timeLastTargetDetected = SystemClock.elapsedRealtime() worker?.notifyEx() } @@ -425,25 +469,19 @@ class NetworkTracker( } } - // 接続先が見つかったら0L - // またはリトライまでの秒数を返す + // テザリングの状態確認や検出を行う + // 接続先が見つかったら0L、またはリトライまでの時間(ミリ秒)を返す private fun detectTetheringClient( - env : NetworkStateList, + env : NetworkStatusList, getCheckUrl : (String) -> String ) : Long { - val now = SystemClock.elapsedRealtime() - if(now - timeLastTargetDetected < 10000L) { - // ターゲットが見つかってからしばらくの間は検出を行わない - return 0L - } - if(! isTetheringEnabled) { env.error_status = "Wi-Fi Tethering is not enabled." - if(setting.stopWhenTetheringOff){ + if(setting.stopWhenTetheringOff) { Utils.runOnMainThread { - callback.onTetheringOff() + if(! isDisposed) callback.onTetheringOff() } } @@ -453,16 +491,22 @@ class NetworkTracker( val tethering_address = tetheringAddress if(tethering_address == null) { - env.error_status = "missing Wi-Fi Tethering IP address." + env.error_status = "Wi-Fi Tethering is missing IP address." return 1000L } + val now = SystemClock.elapsedRealtime() + + // ターゲットが見つかってからしばらくの間は検出を行わない + if(now - timeLastTargetDetected < 10000L) { + return 0L + } + if(reIPAddr.matcher(setting.target_url).find()) { // 設定で指定されたURLにIPアドレスが書かれているなら、それを試す startTestUrl(setting.target_url, getCheckUrl) } - // "XXX.XXX.XXX." val ip_base = tethering_address.replace(reLastDigits, "") @@ -495,12 +539,12 @@ class NetworkTracker( } } - // カードが見つからない場合 - // 直接ARPリクエストを投げるのは難しい?のでUDPパケットをばらまく - // 次回以降の確認で効果ARPテーブルを読めれば良いのだが…。 + // デバイスのアドレスを知るため、定期的にUDPパケットをばらまく + // (アプリからARPリクエストを投げるのは難しい) + // 次回以降のチェックでARPテーブルに反映されるかもしれない val remain = timeLastSpray + setting.tetherSprayInterval - now return if(remain > 0L) { - remain + min(remain, 1000L) } else { timeLastSpray = now sprayUDPPacket(tethering_address, ip_base) @@ -508,10 +552,232 @@ class NetworkTracker( } } - private fun keep_ap() : Long { + // Wi-Fi AP の状態確認や強制を行う + // 接続先が見つかったら0L、またはリトライまでの時間(ミリ秒)を返す + private fun checkWiFiAp(ns_list : NetworkStatusList) : Long { + val wifi_status = requireNotNull(ns_list.wifi_status) + + // Wi-Fiが無効なら有効にする + try { + wifi_status.strWifiStatus = "?" + if(! wifiManager.isWifiEnabled) { + wifi_status.strWifiStatus = context.getString(R.string.not_enabled) + return if(setting.force_wifi) { + wifiManager.isWifiEnabled = true + 1000L + } else { + Long.MAX_VALUE + } + } + } catch(ex : Throwable) { + log.trace(ex, "setWifiEnabled() failed.") + ns_list.error_status = ex.withCaption("setWifiEnabled() failed.") + return 10000L + } + + // Wi-Fiの現在の状態を取得する + val info : WifiInfo? + var current_supp_state : SupplicantState? = null + var current_ssid : String? = null + try { + info = wifiManager.connectionInfo + if(info != null) { + current_supp_state = info.supplicantState + current_ssid = info.ssid?.filterSsid() + } + } catch(ex : Throwable) { + log.trace(ex, "getConnectionInfo() failed.") + ns_list.error_status = ex.withCaption("getConnectionInfo() failed.") + return 10000L + } - val ns_list = NetworkStateList() + // 設定済みのAPを列挙する + var current_network_id = 0 + var target_config : WifiConfiguration? = null + var priority_max = 0 try { + wifi_status.strWifiStatus = context.getString(R.string.no_ap_associated) + + val wc_list = wifiManager.configuredNetworks + ?: return 5000L // getConfiguredNetworks() はたまにnullを返す + + for(wc in wc_list) { + val ssid = wc.SSID.filterSsid() + + val p = getPriority(wc) + + if(p > priority_max) { + priority_max = p + } + + // 目的のAPを覚えておく + if(ssid == setting.target_ssid) { + target_config = wc + } + + // 接続中のAPの情報 + if(ssid == current_ssid) { + current_network_id = wc.networkId + // + var strState = Utils.toCamelCase(current_supp_state?.toString() ?: "?") + if("Completed" == strState) strState = "Connected" + + wifi_status.strWifiStatus = "$ssid,$strState" + + // AP強制ではないなら、何かアクティブな接続が生きていればOK + if(! setting.force_wifi && current_supp_state == SupplicantState.COMPLETED) { + return 0L + } + } + } + // 列挙終了 + when(setting.force_wifi) { + false -> { + // AP強制ではない場合、接続中のAPがなければ待機して再確認 + // 多分通信状況ブロードキャストで起こされる + return 10000L + } + + true -> if(target_config == null) { + // 指定されたSSIDはこの端末に設定されていない + ns_list.force_status = + context.getString( + R.string.wifi_target_ssid_not_found, + setting.target_ssid + ) + return Long.MAX_VALUE + + } + } + } catch(ex : Throwable) { + log.trace(ex, "getConfiguredNetworks() failed.") + ns_list.error_status = ex.withCaption("getConfiguredNetworks() failed.") + return 10000L + } + + if(Build.VERSION.SDK_INT < 26) { + // API level 25まではAPの優先順位を変えることができた + val error = updatePriority(target_config, priority_max) + if(error != null) ns_list.error_status = error + } + + // 目的のAPが選択されていた場合 + if(current_ssid != null && current_network_id == target_config.networkId) { + when(current_supp_state) { + SupplicantState.ASSOCIATING, + SupplicantState.ASSOCIATED, + SupplicantState.AUTHENTICATING, + SupplicantState.FOUR_WAY_HANDSHAKE, + SupplicantState.GROUP_HANDSHAKE -> + // 現在のstateが何か作業中なら、余計なことはしないがOKでもない + return 500L + + SupplicantState.COMPLETED -> { + return if(ns_list.other_active.isNotEmpty()) { + // 認証はできたが他の接続がアクティブならさらに待機する + 1000L + } else { + // 他の接続もないしこれでOKだと思う + 0L + } + } + + else -> { + // fall + } + } + } + + // スキャン結果に目的のSSIDがあるか? + var lastSeen : Long? = null + var found_in_scan = false + try { + for(result in wifiManager.scanResults) { + if(Build.VERSION.SDK_INT >= 17) { + if(lastSeen == null || result.timestamp > lastSeen) { + lastSeen = result.timestamp + } + } + if(setting.target_ssid == result.SSID.filterSsid()) { + found_in_scan = true + break + } + } + } catch(ex : Throwable) { + log.trace(ex, "getScanResults() failed.") + ns_list.error_status = ex.withCaption("getScanResults() failed.") + return 10000L + } + + // スキャン範囲内にない場合、定期的にスキャン開始 + if(! found_in_scan) { + ns_list.force_status = + context.getString(R.string.wifi_target_ssid_not_scanned, setting.target_ssid) + val now = SystemClock.elapsedRealtime() + val remain = timeLastWiFiScan + setting.wifiScanInterval - now + return if(remain > 0L) { + val lastSeenBefore = if(lastSeen == null) null else now - (lastSeen / 1000L) + logStatic.d("${setting.target_ssid} is not found in latest scan result(${lastSeenBefore}ms before). next scan is start after ${remain}ms.") + min(remain, 3000L) + } else try { + timeLastWiFiScan = now + wifiManager.startScan() + log.d(R.string.wifi_scan_start) + 3000L + } catch(ex : Throwable) { + log.trace(ex, "startScan() failed.") + ns_list.error_status = ex.withCaption("startScan() failed.") + 10000L + } + } else { + val now = SystemClock.elapsedRealtime() + val remain = timeLastWiFiApChange + setting.wifiChangeApInterval - now + return if(remain > 0L) { + logStatic.d("wait ${remain}ms before force change WiFi AP") + min(remain, 3000L) + } else { + timeLastWiFiApChange = now + try { + // 先に既存接続を無効にする + for(wc in wifiManager.configuredNetworks) { + if(wc.networkId == target_config.networkId) continue + val ssid = wc.SSID.filterSsid() + when(wc.status) { + WifiConfiguration.Status.CURRENT -> { + log.v("${ssid}から切断させます") + wifiManager.disableNetwork(wc.networkId) + } + + WifiConfiguration.Status.ENABLED -> { + log.v("${ssid}への自動接続を無効化します") + wifiManager.disableNetwork(wc.networkId) + } + } + } + + val target_ssid = target_config.SSID.filterSsid() + log.i("${target_ssid}への接続を試みます") + wifiManager.enableNetwork(target_config.networkId, true) + 1000L + } catch(ex : Throwable) { + log.trace(ex, "disableNetwork() or enableNetwork() failed.") + ns_list.error_status = + ex.withCaption("disableNetwork() or enableNetwork() failed.") + 10000L + } + } + } + } + + // 接続先が見つかったら0L + // またはリトライまでの時間(ミリ秒)を返す + private fun checkNetwork() : Long { + + val ns_list = NetworkStatusList() + + try { + + // 現在のネットワーク接続を列挙する if(Build.VERSION.SDK_INT >= 23) { var active_handle : Long? = null val an = connectivityManager.activeNetwork @@ -545,229 +811,22 @@ class NetworkTracker( ns_list.afterAddAll() last_other_active.set(ns_list.other_active) - // テザリングモードの処理 - when(setting.target_type) { + // ターゲット種別により、テザリング用とAP用の処理に分かれる + return when(setting.target_type) { + Pref.TARGET_TYPE_FLASHAIR_STA -> { - return detectTetheringClient(ns_list) { "${it}command.cgi?op=108" } + detectTetheringClient(ns_list) { "${it}command.cgi?op=108" } } Pref.TARGET_TYPE_PQI_AIR_CARD_TETHER -> { - return detectTetheringClient(ns_list) { "${it}cgi-bin/get_config.pl" } + detectTetheringClient(ns_list) { "${it}cgi-bin/get_config.pl" } } - } - - val wifi_status = requireNotNull(ns_list.wifi_status) - - // Wi-Fiが無効なら有効にする - try { - wifi_status.strWifiStatus = "?" - if(! wifiManager.isWifiEnabled) { - wifi_status.strWifiStatus = context.getString(R.string.not_enabled) - return if(setting.force_wifi) { - wifiManager.isWifiEnabled = true - 1000L - } else { - Long.MAX_VALUE - } - } - } catch(ex : Throwable) { - log.trace(ex, "setWifiEnabled() failed.") - ns_list.error_status = ex.withCaption("setWifiEnabled() failed.") - return 10000L - } - - // Wi-Fiの現在の状態を取得する - val info : WifiInfo? - var current_supp_state : SupplicantState? = null - var current_ssid : String? = null - try { - info = wifiManager.connectionInfo - if(info != null) { - current_supp_state = info.supplicantState - current_ssid = info.ssid?.filterSsid() - } - } catch(ex : Throwable) { - log.trace(ex, "getConnectionInfo() failed.") - ns_list.error_status = ex.withCaption("getConnectionInfo() failed.") - return 10000L - } - - // 設定済みのAPを列挙する - var current_network_id = 0 - var target_config : WifiConfiguration? = null - var priority_max = 0 - try { - wifi_status.strWifiStatus = context.getString(R.string.no_ap_associated) - val wc_list = wifiManager.configuredNetworks - ?: return 5000L // getConfiguredNetworks() はたまにnullを返す - - for(wc in wc_list) { - val ssid = wc.SSID.filterSsid() - - val p = getPriority(wc) - - if(p > priority_max) { - priority_max = p - } - - // 目的のAPを覚えておく - if(ssid == setting.target_ssid) { - target_config = wc - } - - // 接続中のAPの情報 - if(ssid == current_ssid) { - current_network_id = wc.networkId - // - var strState = Utils.toCamelCase(current_supp_state?.toString() ?: "?") - if("Completed" == strState) strState = "Connected" - - wifi_status.strWifiStatus = "$ssid,$strState" - - // AP強制ではないなら、何かアクティブな接続が生きていればOK - if(! setting.force_wifi && current_supp_state == SupplicantState.COMPLETED) { - return 0L - } - } - } - // 列挙終了 - when(setting.force_wifi) { - false -> { - // AP強制ではない場合、接続中のAPがなければ待機して再確認 - // 多分通信状況ブロードキャストで起こされる - return 10000L - } - - true -> if(target_config == null) { - // 指定されたSSIDはこの端末に設定されていない - ns_list.force_status = - context.getString( - R.string.wifi_target_ssid_not_found, - setting.target_ssid - ) - return Long.MAX_VALUE - - } + else -> { + checkWiFiAp(ns_list) } - } catch(ex : Throwable) { - log.trace(ex, "getConfiguredNetworks() failed.") - ns_list.error_status = ex.withCaption("getConfiguredNetworks() failed.") - return 10000L - } - - if(Build.VERSION.SDK_INT < 26) { - // API level 25まではAPの優先順位を変えることができた - val error = updatePriority(target_config, priority_max) - if(error != null) ns_list.error_status = error } - // 目的のAPが選択されていた場合 - if(current_ssid != null && current_network_id == target_config.networkId) { - when(current_supp_state) { - SupplicantState.ASSOCIATING, - SupplicantState.ASSOCIATED, - SupplicantState.AUTHENTICATING, - SupplicantState.FOUR_WAY_HANDSHAKE, - SupplicantState.GROUP_HANDSHAKE -> - // 現在のstateが何か作業中なら、余計なことはしないがOKでもない - return 500L - - SupplicantState.COMPLETED -> { - return if(ns_list.other_active.isNotEmpty()) { - // 認証はできたが他の接続がアクティブならさらに待機する - 1000L - } else { - // 他の接続もないしこれでOKだと思う - 0L - } - } - - else -> { - // fall - } - } - } - - // スキャン結果に目的のSSIDがあるか? - var lastSeen : Long? = null - var found_in_scan = false - try { - for(result in wifiManager.scanResults) { - if(Build.VERSION.SDK_INT >= 17) { - if(lastSeen == null || result.timestamp > lastSeen) { - lastSeen = result.timestamp - } - } - if(setting.target_ssid == result.SSID.filterSsid()) { - found_in_scan = true - break - } - } - } catch(ex : Throwable) { - log.trace(ex, "getScanResults() failed.") - ns_list.error_status = ex.withCaption("getScanResults() failed.") - return 10000L - } - - // スキャン範囲内にない場合、定期的にスキャン開始 - if(! found_in_scan) { - ns_list.force_status = - context.getString(R.string.wifi_target_ssid_not_scanned, setting.target_ssid) - val now = SystemClock.elapsedRealtime() - val remain = timeLastWiFiScan + setting.wifiScanInterval - now - return if(remain > 0L) { - val lastSeenBefore = if(lastSeen == null) null else now - (lastSeen / 1000L) - logStatic.d("${setting.target_ssid} is not found in latest scan result(${lastSeenBefore}ms before). next scan is start after ${remain}ms.") - min(remain, 3000L) - } else try { - timeLastWiFiScan = now - wifiManager.startScan() - log.d(R.string.wifi_scan_start) - 3000L - } catch(ex : Throwable) { - log.trace(ex, "startScan() failed.") - ns_list.error_status = ex.withCaption("startScan() failed.") - 10000L - } - } else { - val now = SystemClock.elapsedRealtime() - val remain = timeLastWiFiApChange + setting.wifiChangeApInterval - now - return if(remain > 0L) { - logStatic.d("wait ${remain}ms before force change WiFi AP") - min(remain, 3000L) - } else { - timeLastWiFiApChange = now - try { - // 先に既存接続を無効にする - for(wc in wifiManager.configuredNetworks) { - if(wc.networkId == target_config.networkId) continue - val ssid = wc.SSID.filterSsid() - when(wc.status) { - WifiConfiguration.Status.CURRENT -> { - log.v("${ssid}から切断させます") - wifiManager.disableNetwork(wc.networkId) - } - - WifiConfiguration.Status.ENABLED -> { - log.v("${ssid}への自動接続を無効化します") - wifiManager.disableNetwork(wc.networkId) - } - } - } - - val target_ssid = target_config.SSID.filterSsid() - log.i("${target_ssid}への接続を試みます") - wifiManager.enableNetwork(target_config.networkId, true) - 1000L - } catch(ex : Throwable) { - log.trace(ex, "disableNetwork() or enableNetwork() failed.") - ns_list.error_status = - ex.withCaption("disableNetwork() or enableNetwork() failed.") - 10000L - } - } - } } finally { // 状態の変化があればログに出力する @@ -792,7 +851,7 @@ class NetworkTracker( } } - internal inner class Worker : WorkerBase() { + private inner class Worker : WorkerBase() { override fun cancel(reason : String) : Boolean { val rv = super.cancel(reason) @@ -804,10 +863,11 @@ class NetworkTracker( } override fun run() { + while(! isCancelled) { - val result = try { - keep_ap() + val remain = try { + checkNetwork() } catch(ex : Throwable) { log.trace(ex, "network check failed.") log.e(ex, "network check failed.") @@ -816,13 +876,14 @@ class NetworkTracker( if(isCancelled) break - val bConnected = result <= 0L - if(bConnected != bLastConnected.get()) { - // 接続状態の変化 - bLastConnected.set(bConnected) + // 接続状態の変化 + val bConnected = remain <= 0L + if(bConnected != bLastConnected) { + bLastConnected = bConnected Utils.runOnMainThread { + if(isDisposed) return@runOnMainThread try { - if(! is_dispose) callback.onConnectionStatus(true, "Wi-Fi tracker") + callback.onConnectionStatus(true, "Wi-Fi tracker") } catch(ex : Throwable) { log.trace(ex, "connection event handling failed.") log.e(ex, "connection event handling failed.") @@ -830,7 +891,7 @@ class NetworkTracker( } } - waitEx(if(result <= 0L) 5000L else result) + waitEx(if(remain <= 0L) 5000L else remain) } } }