diff --git a/app/src/main/java/jp/juggler/fadownloader/ActMain.kt b/app/src/main/java/jp/juggler/fadownloader/ActMain.kt index e45e403..2988aca 100644 --- a/app/src/main/java/jp/juggler/fadownloader/ActMain.kt +++ b/app/src/main/java/jp/juggler/fadownloader/ActMain.kt @@ -684,6 +684,7 @@ open class ActMain : AppCompatActivity(), View.OnClickListener { intent.put(pref, Pref.uiProtectedOnly) intent.put(pref, Pref.uiSkipAlreadyDownload) + intent.put(pref, Pref.uiStopWhenTetheringOff) intent.put(pref, Pref.uiForceWifi) intent.put(pref, Pref.uiRepeat) intent.put(pref, Pref.uiLocationMode) diff --git a/app/src/main/java/jp/juggler/fadownloader/DownloadService.kt b/app/src/main/java/jp/juggler/fadownloader/DownloadService.kt index abdc8c7..623a178 100644 --- a/app/src/main/java/jp/juggler/fadownloader/DownloadService.kt +++ b/app/src/main/java/jp/juggler/fadownloader/DownloadService.kt @@ -96,16 +96,22 @@ class DownloadService : Service() { this.log = log log.d(getString(R.string.service_start)) - val pm = applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager - wake_lock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, packageName) - wake_lock !!.setReferenceCounted(false) + val pm = applicationContext.getSystemService(Context.POWER_SERVICE) + as PowerManager + + wake_lock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, packageName).apply{ + setReferenceCounted(false) + } - val wm = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager - wifi_lock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL, packageName) - wifi_lock !!.setReferenceCounted(false) + val wm = applicationContext.getSystemService(Context.WIFI_SERVICE) + as WifiManager + + wifi_lock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL, packageName).apply { + setReferenceCounted(false) + } - mNotificationManager = - applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + mNotificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) + as NotificationManager setServiceNotification(getString(R.string.service_idle)) @@ -116,16 +122,26 @@ class DownloadService : Service() { media_tracker = MediaScannerTracker(this, log) wifi_tracker = - NetworkTracker(this, log) { is_connected, cause -> - if(is_connected) { - val last_mode = Pref.lastMode(Pref.pref(this@DownloadService)) - if(last_mode != Pref.LAST_MODE_STOP) { - worker_tracker.wakeup(cause) - } + NetworkTracker(this, log, wifi_tracker_callback) + + worker_tracker = WorkerTracker(this, log) + } + + private val wifi_tracker_callback = object:NetworkTracker.Callback{ + override fun onConnectionStatus(is_connected : Boolean, cause : String) { + if(is_connected) { + val last_mode = Pref.lastMode(Pref.pref(this@DownloadService)) + if(last_mode != Pref.LAST_MODE_STOP) { + worker_tracker.wakeup(cause) } } + } - worker_tracker = WorkerTracker(this, log) + override fun onTetheringOff() { + log.e(R.string.stop_when_tethering_off) + this@DownloadService.cancel_alarm_on_destroy = true + stopSelf() + } } override fun onDestroy() { diff --git a/app/src/main/java/jp/juggler/fadownloader/DownloadWorker.kt b/app/src/main/java/jp/juggler/fadownloader/DownloadWorker.kt index cff8efc..e886b43 100644 --- a/app/src/main/java/jp/juggler/fadownloader/DownloadWorker.kt +++ b/app/src/main/java/jp/juggler/fadownloader/DownloadWorker.kt @@ -193,7 +193,9 @@ class DownloadWorker : WorkerBase { tetherSprayInterval = x1000Safe(Pref.uiTetherSprayInterval.getInt(intent)), tetherTestConnectionTimeout = x1000Safe(Pref.uiTetherTestConnectionTimeout.getInt(intent)), wifiChangeApInterval = x1000Safe(Pref.uiWifiChangeApInterval.getInt(intent)), - wifiScanInterval = x1000Safe(Pref.uiWifiScanInterval.getInt(intent)) + wifiScanInterval = x1000Safe(Pref.uiWifiScanInterval.getInt(intent)), + + stopWhenTetheringOff = Pref.uiStopWhenTetheringOff(intent) ) this.location_setting = LocationTracker.Setting( @@ -220,6 +222,7 @@ class DownloadWorker : WorkerBase { .put(Pref.workerTetherTestConnectionTimeout, network_setting.tetherTestConnectionTimeout) .put(Pref.workerWifiChangeApInterval, network_setting.wifiChangeApInterval) .put(Pref.workerWifiScanInterval, network_setting.wifiScanInterval) + .put(Pref.workerStopWhenTetheringOff, network_setting.stopWhenTetheringOff) .apply() this.file_type_list = file_type_parse() @@ -253,7 +256,9 @@ class DownloadWorker : WorkerBase { tetherSprayInterval = Pref.workerTetherSprayInterval(pref), tetherTestConnectionTimeout = Pref.workerTetherTestConnectionTimeout(pref), wifiChangeApInterval = Pref.workerWifiChangeApInterval(pref), - wifiScanInterval = Pref.workerWifiScanInterval(pref) + wifiScanInterval = Pref.workerWifiScanInterval(pref), + + stopWhenTetheringOff = Pref.workerStopWhenTetheringOff(pref) ) this.location_setting = LocationTracker.Setting( diff --git a/app/src/main/java/jp/juggler/fadownloader/PageSetting.kt b/app/src/main/java/jp/juggler/fadownloader/PageSetting.kt index dfd5503..3ab0765 100644 --- a/app/src/main/java/jp/juggler/fadownloader/PageSetting.kt +++ b/app/src/main/java/jp/juggler/fadownloader/PageSetting.kt @@ -41,6 +41,7 @@ class PageSetting(activity : Activity, ignored : View) : private lateinit var swCopyBeforeViewSend : Switch private lateinit var swProtectedOnly : Switch private lateinit var swSkipAlreadyDownload : Switch + private lateinit var swStopWhenTetheringOff : Switch private lateinit var btnSSIDPicker : View internal var bLoading : Boolean = false @@ -66,6 +67,7 @@ class PageSetting(activity : Activity, ignored : View) : btnSSIDPicker = root.findViewById(R.id.btnSSIDPicker) swProtectedOnly = root.findViewById(R.id.swProtectedOnly) swSkipAlreadyDownload = root.findViewById(R.id.swSkipAlreadyDownload) + swStopWhenTetheringOff = root.findViewById(R.id.swStopWhenTetheringOff) etTetherSprayInterval = root.findViewById(R.id.etTetherSprayInterval) etTetherTestConnectionTimeout = root.findViewById(R.id.etTetherTestConnectionTimeout) etWifiChangeApInterval = root.findViewById(R.id.etWifiChangeApInterval) @@ -93,6 +95,7 @@ class PageSetting(activity : Activity, ignored : View) : root.findViewById(R.id.btnTetherTestConnectionTimeoutHelp).setOnClickListener(this) root.findViewById(R.id.btnWifiScanIntervalHelp).setOnClickListener(this) root.findViewById(R.id.btnWifiChangeApIntervalHelp).setOnClickListener(this) + root.findViewById(R.id.btnStopWhenTetheringOffHelp).setOnClickListener(this) val location_mode_adapter = ArrayAdapter( activity, android.R.layout.simple_spinner_item @@ -229,6 +232,7 @@ class PageSetting(activity : Activity, ignored : View) : R.id.btnTetherTestConnectionTimeoutHelp -> openHelp(R.string.tether_test_connection_timeout_help) R.id.btnWifiScanIntervalHelp -> openHelp(R.string.wifi_scan_interval_help) R.id.btnWifiChangeApIntervalHelp -> openHelp(R.string.wifi_change_ap_interval_help) + R.id.btnStopWhenTetheringOffHelp -> openHelp(R.string.stop_when_tethering_off_help) } } @@ -244,6 +248,7 @@ class PageSetting(activity : Activity, ignored : View) : swCopyBeforeViewSend.isChecked = Pref.uiCopyBeforeSend(pref) swProtectedOnly.isChecked = Pref.uiProtectedOnly(pref) swSkipAlreadyDownload.isChecked = Pref.uiSkipAlreadyDownload(pref) + swStopWhenTetheringOff.isChecked = Pref.uiStopWhenTetheringOff(pref) // string etInterval.setText(Pref.uiInterval(pref)) @@ -300,6 +305,8 @@ class PageSetting(activity : Activity, ignored : View) : .put(Pref.uiSsid, etSSID.text.toString()) .put(Pref.uiProtectedOnly, swProtectedOnly.isChecked) .put(Pref.uiSkipAlreadyDownload, swSkipAlreadyDownload.isChecked) + .put(Pref.uiStopWhenTetheringOff, swStopWhenTetheringOff.isChecked) + // .apply() は呼び出し側で行う } diff --git a/app/src/main/java/jp/juggler/fadownloader/Pref.kt b/app/src/main/java/jp/juggler/fadownloader/Pref.kt index f9b9a68..55b1cd5 100644 --- a/app/src/main/java/jp/juggler/fadownloader/Pref.kt +++ b/app/src/main/java/jp/juggler/fadownloader/Pref.kt @@ -16,20 +16,19 @@ class BooleanPref( ) : BasePref(key) { operator fun invoke(pref : SharedPreferences) = pref.getBoolean(key, defVal) - operator fun invoke(intent:Intent) = intent.getBooleanExtra(key, defVal) + operator fun invoke(intent : Intent) = intent.getBooleanExtra(key, defVal) } fun SharedPreferences.Editor.put(bp : BooleanPref, v : Boolean) : SharedPreferences.Editor = this.putBoolean(bp.key, v) - class IntPref( key : String, val defVal : Int ) : BasePref(key) { operator fun invoke(pref : SharedPreferences) = pref.getInt(key, defVal) - operator fun invoke(intent:Intent) = intent.getIntExtra(key, defVal) + operator fun invoke(intent : Intent) = intent.getIntExtra(key, defVal) } fun SharedPreferences.Editor.put(bp : IntPref, v : Int) : SharedPreferences.Editor = @@ -52,52 +51,54 @@ class StringPref( ) : BasePref(key) { operator fun invoke(pref : SharedPreferences) : String = pref.getString(key, defVal) ?: defVal - operator fun invoke(intent:Intent) : String = intent.getStringExtra(key) ?: defVal + operator fun invoke(intent : Intent) : String = intent.getStringExtra(key) ?: defVal -// fun getInt(pref : SharedPreferences) = try { -// invoke(pref).trim().toInt() -// } catch(ex : Throwable) { -// defVal.toInt() -// } - - fun getInt(intent:Intent) = try { + // fun getInt(pref : SharedPreferences) = try { + // invoke(pref).trim().toInt() + // } catch(ex : Throwable) { + // defVal.toInt() + // } + + fun getInt(intent : Intent) = try { invoke(intent).trim().toInt() } catch(ex : Throwable) { defVal.toInt() } - + fun getIntOrNull(pref : SharedPreferences) = try { invoke(pref).trim().toInt() } catch(ex : Throwable) { null } -// fun getIntOrNull(intent:Intent) = try { -// invoke(intent).trim().toInt() -// } catch(ex : Throwable) { -// null -// } + // fun getIntOrNull(intent:Intent) = try { + // invoke(intent).trim().toInt() + // } catch(ex : Throwable) { + // null + // } } fun SharedPreferences.Editor.put(p : StringPref, v : String) : SharedPreferences.Editor = this.putString(p.key, v) -fun Intent.put(pref:SharedPreferences,p:BooleanPref){ - putExtra(p.key,p(pref)) +fun Intent.put(pref : SharedPreferences, p : BooleanPref) { + putExtra(p.key, p(pref)) } -fun Intent.put(pref:SharedPreferences,p:StringPref){ - putExtra(p.key,p(pref)) + +fun Intent.put(pref : SharedPreferences, p : StringPref) { + putExtra(p.key, p(pref)) } -fun Intent.put(pref:SharedPreferences,p:IntPref){ - putExtra(p.key,p(pref)) + +fun Intent.put(pref : SharedPreferences, p : IntPref) { + putExtra(p.key, p(pref)) } -fun Intent.put(v:String,p:StringPref){ - putExtra(p.key,v) +fun Intent.put(v : String, p : StringPref) { + putExtra(p.key, v) } -fun Intent.put(v:Int,p:IntPref){ - putExtra(p.key,v) +fun Intent.put(v : Int, p : IntPref) { + putExtra(p.key, v) } object Pref { @@ -115,10 +116,15 @@ object Pref { const val LAST_MODE_ONCE = 1 const val LAST_MODE_REPEAT = 2 - private const val WIFI_AP_CHANGE_INTERVAL = 5000L - private const val WIFI_SCAN_INTERVAL = 10000L - private const val TETHER_SPRAY_INTERVAL = 3000L - private const val TETHER_TEST_CONNECTION_TIMEOUT = 15000L + private const val DEFAULT_WIFI_AP_CHANGE_INTERVAL = 5000L + private const val DEFAULT_WIFI_SCAN_INTERVAL = 10000L + private const val DEFAULT_TETHER_SPRAY_INTERVAL = 3000L + private const val DEFAULT_TETHER_TEST_CONNECTION_TIMEOUT = 15000L + + private const val DEFAULT_LOCATION_MODE = LocationTracker.NO_LOCATION_UPDATE + private const val DEFAULT_LOCATION_INTERVAL_DESIRED = 1000L * 3600 + private const val DEFAULT_LOCATION_INTERVAL_MIN = 1000L * 300 + // UI画面に表示されている情報の永続化 val uiRepeat = BooleanPref("ui_repeat", false) @@ -127,41 +133,71 @@ object Pref { val uiCopyBeforeSend = BooleanPref("ui_copy_before_view_send", false) val uiProtectedOnly = BooleanPref("ui_protected_only", false) val uiSkipAlreadyDownload = BooleanPref("ui_skip_already_download", false) + val uiStopWhenTetheringOff = BooleanPref("uiStopWhenTetheringOff", false) val uiLastPage = IntPref("ui_last_page", 0) val uiTargetType = IntPref("ui_target_type", - 1) - val uiLocationMode = IntPref("ui_location_mode", LocationTracker.DEFAULT_MODE) + val uiLocationMode = IntPref("ui_location_mode", DEFAULT_LOCATION_MODE) val uiFolderUri = StringPref("ui_folder_uri", "") val uiFileType = StringPref("ui_file_type", ".jp*") val uiSsid = StringPref("ui_ssid", "") - val uiInterval = StringPref("ui_interval", "30") - val uiTetherSprayInterval = StringPref("uiTetherSprayInterval", (TETHER_SPRAY_INTERVAL/1000L).toString()) - val uiTetherTestConnectionTimeout = StringPref("uiTetherTestConnectionTimeout", (TETHER_TEST_CONNECTION_TIMEOUT/1000L).toString()) - val uiWifiChangeApInterval = StringPref("uiWifiChangeApInterval", (WIFI_AP_CHANGE_INTERVAL/1000L).toString()) - val uiWifiScanInterval = StringPref("uiWifiScanInterval", (WIFI_SCAN_INTERVAL/1000L).toString()) + val uiInterval = StringPref( + "ui_interval", + "30" // unit : second + ) + + val uiTetherSprayInterval = StringPref( + "uiTetherSprayInterval", + (DEFAULT_TETHER_SPRAY_INTERVAL / 1000L).toString() + ) + + val uiTetherTestConnectionTimeout = StringPref( + "uiTetherTestConnectionTimeout", + (DEFAULT_TETHER_TEST_CONNECTION_TIMEOUT / 1000L).toString() + ) + val uiWifiChangeApInterval = StringPref( + "uiWifiChangeApInterval", + (DEFAULT_WIFI_AP_CHANGE_INTERVAL / 1000L).toString() + ) + val uiWifiScanInterval = StringPref( + "uiWifiScanInterval", + (DEFAULT_WIFI_SCAN_INTERVAL / 1000L).toString() + ) val uiLocationIntervalDesired = StringPref( "ui_location_interval_desired", - (LocationTracker.DEFAULT_INTERVAL_DESIRED / 1000L).toString() + (DEFAULT_LOCATION_INTERVAL_DESIRED / 1000L).toString() ) val uiLocationIntervalMin = StringPref( "ui_location_interval_min", - (LocationTracker.DEFAULT_INTERVAL_MIN / 1000L).toString() + (DEFAULT_LOCATION_INTERVAL_MIN / 1000L).toString() ) ////////////////////////////////////////////////////////////////////// - private val uiTargetUrlFlashAirAp = - StringPref("ui_flashair_url", "http://flashair/") // 歴史的な理由でキー名が特別 - private val uiTargetUrlFlashAirSta = StringPref("ui_target_url_1", "http://flashair/") - private val uiTargetUrlPentaxKp = StringPref("ui_target_url_2", "http://192.168.0.1/") - private val uiTargetUrlPentaxPqiAirCard = - StringPref("ui_target_url_pqi_air_card", "http://192.168.1.1/") - private val uiTargetUrlPentaxPqiAirCardTether = - StringPref("ui_target_url_pqi_air_card_tether", "http://AutoDetect/") + private val uiTargetUrlFlashAirAp = StringPref( + "ui_flashair_url", // 歴史的な理由でキー名が特別 + "http://flashair/" + ) + private val uiTargetUrlFlashAirSta = StringPref( + "ui_target_url_1", + "http://AutoDetect/" + ) + private val uiTargetUrlPentaxKp = StringPref( + "ui_target_url_2", + "http://192.168.0.1/" + ) + private val uiTargetUrlPentaxPqiAirCard = StringPref( + "ui_target_url_pqi_air_card", + "http://192.168.1.1/" + ) + private val uiTargetUrlPentaxPqiAirCardTether = StringPref( + "ui_target_url_pqi_air_card_tether", + "http://AutoDetect/" + ) private fun uiTargetUrl(target_type : Int) = when(target_type) { TARGET_TYPE_FLASHAIR_AP -> uiTargetUrlFlashAirAp @@ -183,29 +219,53 @@ object Pref { // 最後にWorkerを手動開始した時の設定 val workerRepeat = BooleanPref("worker_repeat", false) - val workerTargetType = IntPref("worker_target_type", - 1) + val workerForceWifi = BooleanPref("worker_force_wifi", false) + val workerProtectedOnly = BooleanPref("worker_protected_only", false) + val workerSkipAlreadyDownload = BooleanPref("worker_skip_already_download", false) + val workerStopWhenTetheringOff = BooleanPref("workerStopWhenTetheringOff", false) + val workerTargetUrl = StringPref("worker_flashair_url", "") val workerFolderUri = StringPref("worker_folder_uri", "") - val workerInterval = IntPref("worker_interval", 86400) val workerFileType = StringPref("worker_file_type", ".jp*") - val workerLocationMode = IntPref("worker_location_mode", LocationTracker.DEFAULT_MODE) + val workerSsid = StringPref("worker_ssid", "") + + val workerTargetType = IntPref("worker_target_type", - 1) + val workerLocationMode = IntPref("worker_location_mode", DEFAULT_LOCATION_MODE) + + val workerInterval = IntPref( + "worker_interval", + 86400 // unit : second + ) + val workerLocationIntervalDesired = LongPref( "worker_location_interval_desired", - LocationTracker.DEFAULT_INTERVAL_DESIRED + DEFAULT_LOCATION_INTERVAL_DESIRED ) + val workerLocationIntervalMin = LongPref( "worker_location_interval_min", - LocationTracker.DEFAULT_INTERVAL_MIN + DEFAULT_LOCATION_INTERVAL_MIN + ) + + val workerTetherSprayInterval = LongPref( + "workerTetherSprayInterval", + DEFAULT_TETHER_SPRAY_INTERVAL ) - val workerForceWifi = BooleanPref("worker_force_wifi", false) - val workerSsid = StringPref("worker_ssid", "") - val workerProtectedOnly = BooleanPref("worker_protected_only", false) - val workerSkipAlreadyDownload = BooleanPref("worker_skip_already_download", false) - val workerTetherSprayInterval = LongPref("workerTetherSprayInterval", TETHER_SPRAY_INTERVAL) - val workerTetherTestConnectionTimeout = LongPref("workerTetherTestConnectionTimeout", TETHER_TEST_CONNECTION_TIMEOUT) - val workerWifiChangeApInterval = LongPref("workerWifiChangeApInterval", WIFI_AP_CHANGE_INTERVAL) - val workerWifiScanInterval = LongPref("workerWifiScanInterval", WIFI_SCAN_INTERVAL) + val workerTetherTestConnectionTimeout = LongPref( + "workerTetherTestConnectionTimeout", + DEFAULT_TETHER_TEST_CONNECTION_TIMEOUT + ) + + val workerWifiChangeApInterval = LongPref( + "workerWifiChangeApInterval", + DEFAULT_WIFI_AP_CHANGE_INTERVAL + ) + + val workerWifiScanInterval = LongPref( + "workerWifiScanInterval", + DEFAULT_WIFI_SCAN_INTERVAL + ) ////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/jp/juggler/fadownloader/tracker/LocationTracker.kt b/app/src/main/java/jp/juggler/fadownloader/tracker/LocationTracker.kt index f3459e6..4e9c76e 100644 --- a/app/src/main/java/jp/juggler/fadownloader/tracker/LocationTracker.kt +++ b/app/src/main/java/jp/juggler/fadownloader/tracker/LocationTracker.kt @@ -23,10 +23,6 @@ class LocationTracker( const val LOCATION_BALANCED = 3 const val LOCATION_HIGH_ACCURACY = 4 - // Setting のデフォルト値 - const val DEFAULT_MODE = NO_LOCATION_UPDATE - const val DEFAULT_INTERVAL_DESIRED = 1000L * 3600 - const val DEFAULT_INTERVAL_MIN = 1000L * 300 internal val date_fmt = SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault()) } 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 f0412ad..5125b46 100644 --- a/app/src/main/java/jp/juggler/fadownloader/tracker/NetworkTracker.kt +++ b/app/src/main/java/jp/juggler/fadownloader/tracker/NetworkTracker.kt @@ -24,7 +24,7 @@ import kotlin.math.min class NetworkTracker( internal val context : Context, internal val log : LogWriter, - internal val callback : (is_connected : Boolean, cause : String) -> Unit + internal val callback : Callback ) : BroadcastReceiver() { companion object { @@ -44,6 +44,11 @@ class NetworkTracker( } + interface Callback{ + fun onConnectionStatus(is_connected : Boolean, cause : String) + fun onTetheringOff() + } + internal class NetworkStatus( var type_name : String, var sub_name : String? = null, @@ -233,26 +238,40 @@ class NetworkTracker( log.v("sent UDP packet to '$ip_base*' time=${Utils.formatTimeDuration(SystemClock.elapsedRealtime() - start)}") } - private var worker : Worker? = null + 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 - @Volatile - internal 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 lastTargetUrl = AtomicReferenceNotNull("") - val otherActive : String get() = last_other_active.get() @@ -276,12 +295,12 @@ class NetworkTracker( if(intent.action == TETHER_STATE_CHANGED) { val sb = StringBuilder("TETHER_STATE_CHANGED. ") val extras = intent.extras - for( key in extras.keySet()){ + for(key in extras.keySet()) { val v = extras[key] - when(v){ + when(v) { is ArrayList<*> -> sb.append("$key=[${v.joinToString("/")}],") is Array<*> -> sb.append("$key=[${v.joinToString("/")}],") - else->sb.append("$key=$v,") + else -> sb.append("$key=$v,") } } log.d(sb.toString()) @@ -289,23 +308,14 @@ class NetworkTracker( if(! is_dispose) worker?.notifyEx() } - 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 - ) - - var setting : Setting = Setting() - fun updateSetting(setting : Setting) { if(is_dispose) return this.setting = setting + timeLastTargetDetected =0L + timeLastSpray =0L + timeLastWiFiApChange =0L + timeLastWiFiScan = 0L + lastTargetUrl.set("") worker?.notifyEx() } @@ -313,88 +323,146 @@ class NetworkTracker( return requireNotNull(last_current_status.get()) } - private val urlChecker_FlashAir : (String) -> Boolean = { url -> - checkUrl_sub(url, "${url}command.cgi?op=108") - } - - private val urlChecker_PqiAirCard : (String) -> Boolean = { url -> - checkUrl_sub(url, "${url}cgi-bin/get_config.pl") - } - - private fun checkUrl_sub(target_url : String, check_url : String) : Boolean { + private class UrlTester( + val setting : Setting, + val log : LogWriter, + val targetUrl : String, + val checkUrl : String, + val callback : (tester : UrlTester) -> Unit + ) : WorkerBase() { - var bFound = false - try { - val urlObject = URL(check_url) - val conn = urlObject.openConnection() as HttpURLConnection + override fun cancel(reason : String) : Boolean { + val rv = super.cancel(reason) 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) { - log.e("HTTP error %s. url=%s", resCode, check_url) - } else { - if(target_url != lastTargetUrl.get()) { - log.i("target detected. %s", target_url) + this.interrupt() + } catch(ignored : Throwable) { + } + return rv + } + + override fun run() { + val timeStart = SystemClock.elapsedRealtime() + var bFound = false + var error : Throwable? = null + try { + try { + val urlObject = URL(checkUrl) + val conn = urlObject.openConnection() as HttpURLConnection + 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) { + } + } - lastTargetUrl.set(target_url) - bFound = true + } catch(ex : Throwable) { + error = ex } } finally { - try { - conn.disconnect() - } catch(ignored : Throwable) { + val time = SystemClock.elapsedRealtime() - timeStart + when(error) { + null -> { + + } + + is ConnectException -> { + // このエラーは500ms程度で出る。 + // ARPテーブルにあるアドレスだが実際にはネットワーク上に相手が存在しない。 + // 頻出するのでログを出力しない + logStatic.w(error.withCaption("time=${time}ms, url=$checkUrl")) + } + + is SocketTimeoutException -> { + // タイムアウトは設定がおかしい場合やネットワークが不調な場合に発生する + // ユーザはエラーログを見て設定値を変更することができる + log.w(error.withCaption("time=${time}ms, url=$checkUrl")) + } + + else -> { + log.trace(error, "time=${time}ms, url=$checkUrl") + log.e(error.withCaption("time=${time}ms, url=$checkUrl")) + } } - + if(bFound) callback(this) } - } catch(ex : Throwable) { - when(ex) { - - is ConnectException ->{ - } - - is SocketTimeoutException -> { - // 通信エラーをトレースするとキリがないのでしない - log.w(ex.withCaption(check_url)) - } - - else -> { - log.trace(ex, check_url) - log.e(ex, check_url) + } + } + + 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) } + lastTargetUrl.set(tester.targetUrl) + timeLastTargetDetected = SystemClock.elapsedRealtime() + worker?.notifyEx() } } - - return bFound + } + + private fun startTestUrl(targetUrl : String, getCheckUrl : (String) -> String) { + val checkUrl = getCheckUrl(targetUrl) + synchronized(testerMap) { + var tester = testerMap[checkUrl] + if(tester?.isAlive == true) return + tester = NetworkTracker.UrlTester(setting, log, targetUrl, checkUrl, onUrlTestComplete) + testerMap[checkUrl] = tester + tester.start() + } } // 接続先が見つかったら0L // またはリトライまでの秒数を返す - private fun detectTetheringClient(env : NetworkStateList,url_checker : (String) -> Boolean) : Long { + private fun detectTetheringClient( + env : NetworkStateList, + getCheckUrl : (String) -> String + ) : Long { - // 設定で指定されたURLを最初に試す - // ターゲットURLにIPアドレスが書かれているなら、それを最初に試す - if(reIPAddr.matcher(setting.target_url).find()) { - if(url_checker(setting.target_url)) { - return 0L - } + val now = SystemClock.elapsedRealtime() + if(now - timeLastTargetDetected < 10000L) { + // ターゲットが見つかってからしばらくの間は検出を行わない + return 0L } if(! isTetheringEnabled) { env.error_status = "Wi-Fi Tethering is not enabled." + + if(setting.stopWhenTetheringOff){ + Utils.runOnMainThread { + callback.onTetheringOff() + } + } + // TETHER_STATE_CHANGED があるのでリトライ間隔は長めでもよさそう return 5000L } val tethering_address = tetheringAddress if(tethering_address == null) { - env.error_status= "missing Wi-Fi Tethering IP address." + env.error_status = "missing Wi-Fi Tethering IP address." return 1000L } + if(reIPAddr.matcher(setting.target_url).find()) { + // 設定で指定されたURLにIPアドレスが書かれているなら、それを試す + startTestUrl(setting.target_url, getCheckUrl) + } + + // "XXX.XXX.XXX." val ip_base = tethering_address.replace(reLastDigits, "") @@ -417,26 +485,12 @@ class NetworkTracker( list.add(item_ip) } - if( list.isEmpty() ) { + if(list.isEmpty()) { env.error_status = "missing devices in ARP table." - }else{ + } else { env.force_status = "devices: ${list.joinToString(",")}" - - // 直前までに接続していたデバイスを優先的に確認する - val lastUrl = lastTargetUrl.get() - for( item_ip in list) { - val url = "http://$item_ip/" - if( url == lastUrl && url_checker(url) ){ - return 0L - } - } - - // 次にそれ以外のデバイスを確認する - for( item_ip in list) { - val url = "http://$item_ip/" - if( url != lastUrl && url_checker(url) ){ - return 0L - } + for(item_ip in list) { + startTestUrl("http://$item_ip/", getCheckUrl) } } } @@ -444,7 +498,6 @@ class NetworkTracker( // カードが見つからない場合 // 直接ARPリクエストを投げるのは難しい?のでUDPパケットをばらまく // 次回以降の確認で効果ARPテーブルを読めれば良いのだが…。 - val now = SystemClock.elapsedRealtime() val remain = timeLastSpray + setting.tetherSprayInterval - now return if(remain > 0L) { remain @@ -455,8 +508,6 @@ class NetworkTracker( } } - - private fun keep_ap() : Long { val ns_list = NetworkStateList() @@ -497,11 +548,11 @@ class NetworkTracker( // テザリングモードの処理 when(setting.target_type) { Pref.TARGET_TYPE_FLASHAIR_STA -> { - return detectTetheringClient(ns_list,urlChecker_FlashAir) + return detectTetheringClient(ns_list) { "${it}command.cgi?op=108" } } Pref.TARGET_TYPE_PQI_AIR_CARD_TETHER -> { - return detectTetheringClient(ns_list,urlChecker_PqiAirCard) + return detectTetheringClient(ns_list) { "${it}cgi-bin/get_config.pl" } } } @@ -711,7 +762,8 @@ class NetworkTracker( 1000L } catch(ex : Throwable) { log.trace(ex, "disableNetwork() or enableNetwork() failed.") - ns_list.error_status = ex.withCaption("disableNetwork() or enableNetwork() failed.") + ns_list.error_status = + ex.withCaption("disableNetwork() or enableNetwork() failed.") 10000L } } @@ -744,12 +796,10 @@ class NetworkTracker( override fun cancel(reason : String) : Boolean { val rv = super.cancel(reason) - try { this.interrupt() } catch(ignored : Throwable) { } - return rv } @@ -772,7 +822,7 @@ class NetworkTracker( bLastConnected.set(bConnected) Utils.runOnMainThread { try { - if(! is_dispose) callback(true, "Wi-Fi tracker") + if(! is_dispose) callback.onConnectionStatus(true, "Wi-Fi tracker") } catch(ex : Throwable) { log.trace(ex, "connection event handling failed.") log.e(ex, "connection event handling failed.") diff --git a/app/src/main/java/jp/juggler/fadownloader/util/LogTag.kt b/app/src/main/java/jp/juggler/fadownloader/util/LogTag.kt index 4ed9c59..8dbffd4 100644 --- a/app/src/main/java/jp/juggler/fadownloader/util/LogTag.kt +++ b/app/src/main/java/jp/juggler/fadownloader/util/LogTag.kt @@ -15,26 +15,27 @@ class LogTag(category:String){ private val tag = "$TAG:$category" - fun v(fmt:String,vararg args:Any?){ - Log.v(tag,format(fmt,args)) - } + //////////////////////// + + fun e(fmt:String,vararg args:Any?)=Log.e(tag,format(fmt,args)) + + fun w(fmt:String,vararg args:Any?)=Log.w(tag,format(fmt,args)) + + fun i(fmt:String,vararg args:Any?)=Log.i(tag,format(fmt,args)) - fun d(fmt:String,vararg args:Any?){ - Log.d(tag,format(fmt,args)) - } + fun d(fmt:String,vararg args:Any?)=Log.d(tag,format(fmt,args)) - fun e(fmt:String,vararg args:Any?){ - Log.e(tag,format(fmt,args)) - } + fun v(fmt:String,vararg args:Any?)=Log.v(tag,format(fmt,args)) + + //////////////////////// + + fun e(ex:Throwable,fmt:String,vararg args:Any?)=Log.e(tag,ex.withCaption(fmt,*args)) + + fun w(ex:Throwable,fmt:String,vararg args:Any?)=Log.w(tag,ex.withCaption(fmt,*args)) - fun e(ex:Throwable,fmt:String,vararg args:Any?){ - Log.e(tag,ex.withCaption(fmt,*args)) - } + //////////////////////// fun trace(ex:Throwable,fmt:String,vararg args:Any?){ Log.e(tag,format(fmt,args),ex) } - - - } diff --git a/app/src/main/res/layout/page_setting.xml b/app/src/main/res/layout/page_setting.xml index 9397751..03a6e73 100644 --- a/app/src/main/res/layout/page_setting.xml +++ b/app/src/main/res/layout/page_setting.xml @@ -261,6 +261,27 @@ + + + + + + +