diff --git a/.idea/dictionaries/tateisu.xml b/.idea/dictionaries/tateisu.xml index 94d6a32..ca10fce 100644 --- a/.idea/dictionaries/tateisu.xml +++ b/.idea/dictionaries/tateisu.xml @@ -10,6 +10,7 @@ dcim emlsgvh enetunreach + errored fadownloader filelist firebase diff --git a/.idea/misc.xml b/.idea/misc.xml index 93713f5..13c4629 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -24,7 +24,7 @@ - + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6d0946f..11c68ab 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,10 +33,6 @@ android:exported="false" /> - - diff --git a/app/src/main/java/jp/juggler/fadownloader/ActMain.kt b/app/src/main/java/jp/juggler/fadownloader/ActMain.kt index 6b649f6..e45e403 100644 --- a/app/src/main/java/jp/juggler/fadownloader/ActMain.kt +++ b/app/src/main/java/jp/juggler/fadownloader/ActMain.kt @@ -206,7 +206,7 @@ open class ActMain : AppCompatActivity(), View.OnClickListener { R.id.btnStop -> download_stop_button() - R.id.btnModeHelp -> openHelp(R.layout.help_mode) + R.id.btnModeHelp -> openHelpLayout(R.layout.help_mode) } } @@ -528,9 +528,8 @@ open class ActMain : AppCompatActivity(), View.OnClickListener { stopService(intent) try { - val pi = Utils.createAlarmPendingIntent(this) val am = getSystemService(Context.ALARM_SERVICE) as? AlarmManager - am?.cancel(pi) + am?.cancel(Receiver1.piAlarm(this)) } catch(ex : Throwable) { log.trace(ex, "createAlarmPendingIntent failed.") } @@ -616,12 +615,6 @@ open class ActMain : AppCompatActivity(), View.OnClickListener { return } - val interval = Pref.uiInterval.getIntOrNull(pref) ?: - 1 - if(repeat && interval < 1) { - Utils.showToast(this, true, getString(R.string.repeat_interval_not_ok)) - return - } - val file_type = Pref.uiFileType(pref).trim() if(file_type.isEmpty()) { Utils.showToast(this, true, getString(R.string.file_type_empty)) @@ -634,34 +627,27 @@ open class ActMain : AppCompatActivity(), View.OnClickListener { return } - var location_update_interval_desired = LocationTracker.DEFAULT_INTERVAL_DESIRED - var location_update_interval_min = LocationTracker.DEFAULT_INTERVAL_MIN + + fun validSeconds(v : Int?) : Boolean { + return v != null && v > 0 + } + + if(repeat) { + if(! validSeconds(Pref.uiInterval.getIntOrNull(pref))) { + Utils.showToast(this, true, getString(R.string.repeat_interval_not_ok)) + return + } + } if(location_mode != LocationTracker.NO_LOCATION_UPDATE) { - fun x1000(v : Int?) = if(v != null) { - v.toLong() * 1000L - } else { - - 1L + if(! validSeconds(Pref.uiLocationIntervalDesired.getIntOrNull(pref))) { + Utils.showToast(this, true, getString(R.string.location_update_interval_not_ok)) + return } - - location_update_interval_desired = - x1000(Pref.uiLocationIntervalDesired.getIntOrNull(pref)) - location_update_interval_min = x1000(Pref.uiLocationIntervalMin.getIntOrNull(pref)) - - when { - ! repeat -> { - } - - location_update_interval_desired < 1000L -> { - Utils.showToast(this, true, getString(R.string.location_update_interval_not_ok)) - return - } - - location_update_interval_min < 1000L -> { - Utils.showToast(this, true, getString(R.string.location_update_interval_not_ok)) - return - } + if(! validSeconds(Pref.uiLocationIntervalMin.getIntOrNull(pref))) { + Utils.showToast(this, true, getString(R.string.location_update_interval_not_ok)) + return } } @@ -678,9 +664,6 @@ open class ActMain : AppCompatActivity(), View.OnClickListener { } } - val protected_only = Pref.uiProtectedOnly(pref) - val skip_already_download = Pref.uiSkipAlreadyDownload(pref) - // 最後に押したボタンを覚えておく pref.edit() .put(Pref.lastMode, if(repeat) Pref.LAST_MODE_REPEAT else Pref.LAST_MODE_ONCE) @@ -691,28 +674,32 @@ open class ActMain : AppCompatActivity(), View.OnClickListener { val intent = Intent(this, DownloadService::class.java) intent.action = DownloadService.ACTION_START - intent.putExtra(DownloadService.EXTRA_TARGET_TYPE, target_type) - intent.putExtra(DownloadService.EXTRA_REPEAT, repeat) + intent.put(pref, Pref.uiTetherSprayInterval) + intent.put(pref, Pref.uiTetherTestConnectionTimeout) + intent.put(pref, Pref.uiWifiChangeApInterval) + intent.put(pref, Pref.uiWifiScanInterval) + intent.put(pref, Pref.uiLocationIntervalDesired) + intent.put(pref, Pref.uiLocationIntervalMin) + intent.put(pref, Pref.uiInterval) + + intent.put(pref, Pref.uiProtectedOnly) + intent.put(pref, Pref.uiSkipAlreadyDownload) + intent.put(pref, Pref.uiForceWifi) + intent.put(pref, Pref.uiRepeat) + intent.put(pref, Pref.uiLocationMode) + + intent.put(ssid, Pref.uiSsid) + intent.put(folder_uri, Pref.uiFolderUri) + intent.put(file_type, Pref.uiFileType) + + intent.put(target_type, Pref.uiTargetType) intent.putExtra(DownloadService.EXTRA_TARGET_URL, target_url) - intent.putExtra(DownloadService.EXTRA_LOCAL_FOLDER, folder_uri) - intent.putExtra(DownloadService.EXTRA_INTERVAL, interval) - intent.putExtra(DownloadService.EXTRA_FILE_TYPE, file_type) - intent.putExtra( - DownloadService.EXTRA_LOCATION_INTERVAL_DESIRED, - location_update_interval_desired - ) - intent.putExtra(DownloadService.EXTRA_LOCATION_INTERVAL_MIN, location_update_interval_min) - intent.putExtra(DownloadService.EXTRA_LOCATION_MODE, location_mode) - intent.putExtra(DownloadService.EXTRA_FORCE_WIFI, force_wifi) - intent.putExtra(DownloadService.EXTRA_SSID, ssid) - intent.putExtra(DownloadService.EXTRA_PROTECTED_ONLY, protected_only) - intent.putExtra(DownloadService.EXTRA_SKIP_ALREADY_DOWNLOAD, skip_already_download) startService(intent) } - internal fun openHelp(layout_id : Int) { + internal fun openHelpLayout(layout_id : Int) { val v = layoutInflater.inflate(layout_id, null, false) val d = Dialog(this) d.requestWindowFeature(Window.FEATURE_NO_TITLE) diff --git a/app/src/main/java/jp/juggler/fadownloader/DownloadService.kt b/app/src/main/java/jp/juggler/fadownloader/DownloadService.kt index a462432..abdc8c7 100644 --- a/app/src/main/java/jp/juggler/fadownloader/DownloadService.kt +++ b/app/src/main/java/jp/juggler/fadownloader/DownloadService.kt @@ -1,9 +1,7 @@ package jp.juggler.fadownloader import android.annotation.SuppressLint -import android.app.AlarmManager import android.app.NotificationManager -import android.app.PendingIntent import android.app.Service import android.content.Context import android.content.Intent @@ -20,31 +18,18 @@ import jp.juggler.fadownloader.tracker.NetworkTracker import jp.juggler.fadownloader.tracker.WorkerTracker import jp.juggler.fadownloader.util.LogWriter import jp.juggler.fadownloader.util.NotificationHelper -import jp.juggler.fadownloader.util.Utils class DownloadService : Service() { - + companion object { internal const val ACTION_BROADCAST_RECEIVED = "broadcast_received" internal const val EXTRA_BROADCAST_INTENT = "broadcast_intent" internal const val ACTION_START = "start" - internal const val EXTRA_REPEAT = "repeat" internal const val EXTRA_TARGET_URL = "uri" - internal const val EXTRA_LOCAL_FOLDER = "folder_uri" - internal const val EXTRA_INTERVAL = "intervalSeconds" - internal const val EXTRA_FILE_TYPE = "file_type" - internal const val EXTRA_LOCATION_INTERVAL_DESIRED = "location_interval_desired" - internal const val EXTRA_LOCATION_INTERVAL_MIN = "location_interval_min" - internal const val EXTRA_LOCATION_MODE = "location_mode" - internal const val EXTRA_FORCE_WIFI = "force_wifi" - internal const val EXTRA_SSID = "ssid" - internal const val EXTRA_TARGET_TYPE = "target_type" internal const val NOTIFICATION_ID_SERVICE = 1 - const val EXTRA_PROTECTED_ONLY = "protected_only" - const val EXTRA_SKIP_ALREADY_DOWNLOAD = "skip_already_download" internal var service_instance : DownloadService? = null @@ -55,16 +40,17 @@ class DownloadService : Service() { val sb = StringBuilder() sb.append(context.getString(R.string.service_running)) sb.append("WakeLock=") - .append(if(service.wake_lock !!.isHeld) "ON" else "OFF") + .append(if(service.wake_lock?.isHeld == true) "ON" else "OFF") .append(", ") .append("WiFiLock=") - .append(if(service.wifi_lock !!.isHeld) "ON" else "OFF") + .append(if(service.wifi_lock?.isHeld == true) "ON" else "OFF") .append(", ") .append("Location=") .append(service.location_tracker.status) .append(", ") .append("Network=") - service.wifi_tracker.getStatus(sb) + .append(service.wifi_tracker.getStatus()) + sb.append('\n') val worker = service.worker_tracker.worker @@ -156,14 +142,7 @@ class DownloadService : Service() { wifi_tracker.dispose() if(cancel_alarm_on_destroy) { - try { - val pi = Utils.createAlarmPendingIntent(this) - val am = getSystemService(Context.ALARM_SERVICE) as? AlarmManager - am?.cancel(pi) - } catch(ex : Throwable) { - log.trace(ex,"cancel_alarm_on_destroy failed.") - } - + Receiver1.cancelAlarm(this) } wake_lock?.release() @@ -189,7 +168,7 @@ class DownloadService : Service() { ACTION_START -> { worker_tracker.start(intent) } - + ACTION_BROADCAST_RECEIVED -> { val broadcast_intent = intent.getParcelableExtra(EXTRA_BROADCAST_INTENT) if(broadcast_intent != null) { @@ -260,7 +239,7 @@ class DownloadService : Service() { } internal fun onThreadEnd(complete_and_no_repeat : Boolean) { - if(! is_alive ) return + if(! is_alive) return if(complete_and_no_repeat) { this@DownloadService.cancel_alarm_on_destroy = true @@ -271,12 +250,12 @@ class DownloadService : Service() { } } - internal fun addHiddenDownloadCount(count : Long, log : LogWriter) { - NewFileService.addHiddenDownloadCount(this, count, log) + internal fun addHiddenDownloadCount(count : Long) { + NewFileWidget.addHiddenDownloadCount(this, count) } fun hasHiddenDownloadCount() : Boolean { - return NewFileService.hasHiddenDownloadCount(this) + return NewFileWidget.hasHiddenDownloadCount(this) } private fun setServiceNotification(status : String) { @@ -290,8 +269,7 @@ class DownloadService : Service() { "ServiceRunning", "FA Downloader service", "this notification is shown while FA Downloader service is active.", - NotificationManager.IMPORTANCE_LOW, - log + NotificationManager.IMPORTANCE_LOW ) NotificationCompat.Builder(this, channel.id) } else { @@ -303,9 +281,8 @@ class DownloadService : Service() { builder.setContentText(status) builder.setOngoing(true) - val intent = Intent(this, ActMain::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) - val pi = PendingIntent.getActivity(applicationContext, 567, intent, 0) + val pi = Receiver1.piActivity(this) + builder.setContentIntent(pi) startForeground(NOTIFICATION_ID_SERVICE, builder.build()) diff --git a/app/src/main/java/jp/juggler/fadownloader/DownloadWorker.kt b/app/src/main/java/jp/juggler/fadownloader/DownloadWorker.kt index 0c602dd..cff8efc 100644 --- a/app/src/main/java/jp/juggler/fadownloader/DownloadWorker.kt +++ b/app/src/main/java/jp/juggler/fadownloader/DownloadWorker.kt @@ -25,6 +25,7 @@ import jp.juggler.fadownloader.targets.FlashAir import jp.juggler.fadownloader.targets.PentaxKP import jp.juggler.fadownloader.targets.PqiAirCard import jp.juggler.fadownloader.tracker.LocationTracker +import jp.juggler.fadownloader.tracker.NetworkTracker import jp.juggler.fadownloader.util.* class DownloadWorker : WorkerBase { @@ -38,6 +39,14 @@ class DownloadWorker : WorkerBase { internal val reFileType = Pattern.compile("(\\S+)") internal const val MACRO_WAIT_UNTIL = "%WAIT_UNTIL%" + + fun x1000Safe(v : Int) : Long { + return if(v > 0) { + v.toLong() * 1000L + } else { + 1000L + } + } } interface Callback { @@ -50,7 +59,7 @@ class DownloadWorker : WorkerBase { fun onThreadStart() - fun onThreadEnd(worker:DownloadWorker,complete_and_no_repeat : Boolean) + fun onThreadEnd(worker : DownloadWorker, complete_and_no_repeat : Boolean) fun onAllFileCompleted(count : Long) @@ -58,7 +67,7 @@ class DownloadWorker : WorkerBase { } val callback : Callback - + internal var disposed = false private val service : DownloadService @@ -70,10 +79,9 @@ class DownloadWorker : WorkerBase { val file_type : String val log : LogWriter val file_type_list : ArrayList - private val force_wifi : Boolean - private val ssid : String? val target_type : Int private val location_setting : LocationTracker.Setting + private val network_setting : NetworkTracker.Setting val protected_only : Boolean private val skip_already_download : Boolean @@ -87,13 +95,13 @@ class DownloadWorker : WorkerBase { // SSID強制が指定されていない、または接続中のWi-FiのSSIDが指定されたものと同じなら真 private val isValidSsid : Boolean - get() = if(! force_wifi) { + get() = if(! network_setting.force_wifi) { true } else { val wm = service.applicationContext.getSystemService(Context.WIFI_SERVICE) as? WifiManager - this.ssid == wm?.connectionInfo?.ssid?.replace("\"", "") + network_setting.target_ssid == wm?.connectionInfo?.ssid?.filterSsid() } @Suppress("DEPRECATION") @@ -143,48 +151,56 @@ class DownloadWorker : WorkerBase { fc > 0L -> { val bc = queued_byte_count.get() val bcm = queued_byte_count_max.get() - "$s\n${service.getString(R.string.progress)} ${if(bcm <= 0) 0 else 100L * (bcm - bc) / bcm}%, ${service.getString(R.string.remain)} ${fc}file ${Utils.formatBytes(bc)}byte" + "$s\n${service.getString(R.string.progress)} ${if(bcm <= 0) 0 else 100L * (bcm - bc) / bcm}%, ${service.getString( + R.string.remain + )} ${fc}file ${Utils.formatBytes(bc)}byte" } - s?.contains(MACRO_WAIT_UNTIL) ==true -> { + + s?.contains(MACRO_WAIT_UNTIL) == true -> { var remain = wait_until.get() - SystemClock.elapsedRealtime() if(remain < 0L) remain = 0L s.replace(MACRO_WAIT_UNTIL, Utils.formatTimeDuration(remain)) } + else -> s } } - - constructor(service : DownloadService, intent : Intent, callback : Callback) { this.service = service this.callback = callback this.log = LogWriter(service) log.i(R.string.thread_ctor_params) - this.repeat = intent.getBooleanExtra(DownloadService.EXTRA_REPEAT, false) + + this.target_type = Pref.uiTargetType(intent) this.target_url = intent.getStringExtra(DownloadService.EXTRA_TARGET_URL) - this.folder_uri = intent.getStringExtra(DownloadService.EXTRA_LOCAL_FOLDER) - this.intervalSeconds = intent.getIntExtra(DownloadService.EXTRA_INTERVAL, 86400) - this.file_type = intent.getStringExtra(DownloadService.EXTRA_FILE_TYPE) - this.force_wifi = intent.getBooleanExtra(DownloadService.EXTRA_FORCE_WIFI, false) - this.ssid = intent.getStringExtra(DownloadService.EXTRA_SSID) - this.target_type = intent.getIntExtra(DownloadService.EXTRA_TARGET_TYPE, 0) - this.protected_only = intent.getBooleanExtra(DownloadService.EXTRA_PROTECTED_ONLY, false) - this.skip_already_download = - intent.getBooleanExtra(DownloadService.EXTRA_SKIP_ALREADY_DOWNLOAD, false) - this.location_setting = LocationTracker.Setting() - location_setting.interval_desired = intent.getLongExtra( - DownloadService.EXTRA_LOCATION_INTERVAL_DESIRED, - LocationTracker.DEFAULT_INTERVAL_DESIRED + this.file_type = Pref.uiFileType(intent).trim() + + this.folder_uri = Pref.uiFolderUri(intent) + this.repeat = Pref.uiRepeat(intent) + this.intervalSeconds = Pref.uiInterval.getInt(intent) + this.protected_only = Pref.uiProtectedOnly(intent) + this.skip_already_download = Pref.uiSkipAlreadyDownload(intent) + + this.network_setting = NetworkTracker.Setting( + force_wifi =Pref.uiForceWifi(intent), + target_ssid = Pref.uiSsid(intent), + target_type = this.target_type, + target_url = this.target_url, + + tetherSprayInterval = x1000Safe(Pref.uiTetherSprayInterval.getInt(intent)), + tetherTestConnectionTimeout = x1000Safe(Pref.uiTetherTestConnectionTimeout.getInt(intent)), + wifiChangeApInterval = x1000Safe(Pref.uiWifiChangeApInterval.getInt(intent)), + wifiScanInterval = x1000Safe(Pref.uiWifiScanInterval.getInt(intent)) ) - location_setting.interval_min = intent.getLongExtra( - DownloadService.EXTRA_LOCATION_INTERVAL_MIN, - LocationTracker.DEFAULT_INTERVAL_MIN + + this.location_setting = LocationTracker.Setting( + mode = Pref.uiLocationMode(intent), + interval_desired = x1000Safe(Pref.uiLocationIntervalDesired.getInt(intent)), + interval_min = x1000Safe(Pref.uiLocationIntervalMin.getInt(intent)) ) - location_setting.mode = - intent.getIntExtra(DownloadService.EXTRA_LOCATION_MODE, LocationTracker.DEFAULT_MODE) Pref.pref(service).edit() .put(Pref.workerRepeat, repeat) @@ -196,10 +212,14 @@ class DownloadWorker : WorkerBase { .put(Pref.workerLocationIntervalDesired, location_setting.interval_desired) .put(Pref.workerLocationIntervalMin, location_setting.interval_min) .put(Pref.workerLocationMode, location_setting.mode) - .put(Pref.workerForceWifi, force_wifi) - .put(Pref.workerSsid, ssid) + .put(Pref.workerForceWifi, network_setting.force_wifi) + .put(Pref.workerSsid, network_setting.target_ssid) .put(Pref.workerProtectedOnly, protected_only) .put(Pref.workerSkipAlreadyDownload, skip_already_download) + .put(Pref.workerTetherSprayInterval, network_setting.tetherSprayInterval) + .put(Pref.workerTetherTestConnectionTimeout, network_setting.tetherTestConnectionTimeout) + .put(Pref.workerWifiChangeApInterval, network_setting.wifiChangeApInterval) + .put(Pref.workerWifiScanInterval, network_setting.wifiScanInterval) .apply() this.file_type_list = file_type_parse() @@ -220,20 +240,27 @@ class DownloadWorker : WorkerBase { this.folder_uri = Pref.workerFolderUri(pref) this.intervalSeconds = Pref.workerInterval(pref) this.file_type = Pref.workerFileType(pref) + this.target_type = Pref.workerTargetType(pref) + this.protected_only =Pref.workerProtectedOnly(pref) + this.skip_already_download =Pref.workerSkipAlreadyDownload(pref) + + this.network_setting = NetworkTracker.Setting( + force_wifi = Pref.workerForceWifi(pref), + target_ssid =Pref.workerSsid(pref), + target_type = this.target_type, + target_url = this.target_url, + + tetherSprayInterval = Pref.workerTetherSprayInterval(pref), + tetherTestConnectionTimeout = Pref.workerTetherTestConnectionTimeout(pref), + wifiChangeApInterval = Pref.workerWifiChangeApInterval(pref), + wifiScanInterval = Pref.workerWifiScanInterval(pref) + ) - this.force_wifi = - Pref.workerForceWifi(pref) // pref.getBoolean(Pref.WORKER_FORCE_WIFI, false) - this.ssid = Pref.workerSsid(pref) //pref.getString(Pref.WORKER_SSID, null) - this.target_type = Pref.workerTargetType(pref) //pref.getInt(Pref.WORKER_TARGET_TYPE, 0) - this.protected_only = - Pref.workerProtectedOnly(pref) //pref.getBoolean(Pref.WORKER_PROTECTED_ONLY, false) - this.skip_already_download = - Pref.workerSkipAlreadyDownload(pref) //pref.getBoolean(Pref.WORKER_SKIP_ALREADY_DOWNLOAD, false) - - this.location_setting = LocationTracker.Setting() - location_setting.interval_desired = Pref.workerLocationIntervalDesired(pref) - location_setting.interval_min = Pref.workerLocationIntervalMin(pref) - location_setting.mode = Pref.workerLocationMode(pref) + this.location_setting = LocationTracker.Setting( + mode = Pref.workerLocationMode(pref), + interval_desired = Pref.workerLocationIntervalDesired(pref), + interval_min = Pref.workerLocationIntervalMin(pref) + ) this.file_type_list = file_type_parse() @@ -242,7 +269,7 @@ class DownloadWorker : WorkerBase { } private fun init() { - service.wifi_tracker.updateSetting(force_wifi, ssid, target_type, target_url) + service.wifi_tracker.updateSetting(network_setting) service.location_tracker.updateSetting(location_setting) } @@ -252,7 +279,7 @@ class DownloadWorker : WorkerBase { try { client.cancel(log) } catch(ex : Throwable) { - log.trace(ex,"cancel failed") + log.trace(ex, "cancel failed") } return rv @@ -267,7 +294,7 @@ class DownloadWorker : WorkerBase { .replace("\\\\\\*".toRegex(), ".*?") list.add(Pattern.compile("$spec\\z", Pattern.CASE_INSENSITIVE)) } catch(ex : Throwable) { - log.trace(ex,"file_type_parse failed.") + log.trace(ex, "file_type_parse failed.") log.e( R.string.file_type_parse_error, m.group(1), @@ -290,8 +317,7 @@ class DownloadWorker : WorkerBase { wait_until.set(SystemClock.elapsedRealtime() + remain) try { - val pi = Utils.createAlarmPendingIntent(service) - + val pi = Receiver1.piAlarm(service) val am = service.getSystemService(Context.ALARM_SERVICE) as? AlarmManager when { @@ -310,8 +336,8 @@ class DownloadWorker : WorkerBase { else -> am.set(AlarmManager.RTC_WAKEUP, now + remain, pi) } } catch(ex : Throwable) { - log.trace(ex,"待機の設定に失敗") - log.e(ex,"待機の設定に失敗") + log.trace(ex, "待機の設定に失敗") + log.e(ex, "待機の設定に失敗") } cancel(service.getString(R.string.wait_alarm, Utils.formatTimeDuration(remain))) @@ -389,7 +415,7 @@ class DownloadWorker : WorkerBase { log.e(ex, "exif mangling failed.") // 変更失敗 - em = ErrorAndMessage(true, ex.withCaption( "exif mangling failed.")) + em = ErrorAndMessage(true, ex.withCaption("exif mangling failed.")) } if(em != null) return em @@ -439,7 +465,7 @@ class DownloadWorker : WorkerBase { } catch(ex : Throwable) { log.trace(ex, "exif mangling failed.") log.e(ex, "exif mangling failed.") - return ErrorAndMessage(true, ex.withCaption( "exif mangling failed.")) + return ErrorAndMessage(true, ex.withCaption("exif mangling failed.")) } finally { if(local_temp != null && bDeleteTempFile) { try { @@ -457,13 +483,13 @@ class DownloadWorker : WorkerBase { // ローカルにあるファイルのサイズが指定以上ならスキップする val localLength = local_file.length(log) - if( localLength >= size){ + if(localLength >= size) { return true } if(skip_already_download) { val name = local_file.name - if( name?.isNotEmpty()==true) { + if(name?.isNotEmpty() == true) { service.contentResolver.query( DownloadRecord.meta.content_uri, null, DownloadRecord.COL_NAME + "=?", @@ -608,10 +634,10 @@ class DownloadWorker : WorkerBase { } fun checkHostError() { - if(client.last_error ?.contains("UnknownHostException") == true) { + if(client.last_error?.contains("UnknownHostException") == true) { client.last_error = service.getString(R.string.target_host_error) cancel(service.getString(R.string.target_host_error_short)) - } else if(client.last_error ?.contains("ENETUNREACH") == true ) { + } else if(client.last_error?.contains("ENETUNREACH") == true) { client.last_error = service.getString(R.string.target_unreachable) cancel(service.getString(R.string.target_unreachable)) } @@ -646,24 +672,18 @@ class DownloadWorker : WorkerBase { callback.onThreadStart() // 古いアラームがあれば除去 - try { - val pi = Utils.createAlarmPendingIntent(service) - val am = service.getSystemService(Context.ALARM_SERVICE) as? AlarmManager - am?.cancel(pi) - } catch(ex : Throwable) { - log.trace(ex,"alarm cancel failed") - } + Receiver1.cancelAlarm(service) // 位置情報を得られるまで待機 - run{ + run { val timeEnd = SystemClock.elapsedRealtime() + 5000L - while( !isCancelled + while(! isCancelled && service.location_tracker.isUpdateRequired && service.location_tracker.location == null - ){ - val remain =timeEnd -SystemClock.elapsedRealtime() - if(remain<=0L){ - log.w( R.string.location_wait_timeout) + ) { + val remain = timeEnd - SystemClock.elapsedRealtime() + if(remain <= 0L) { + log.w(R.string.location_wait_timeout) break } Thread.sleep(1000L) @@ -694,7 +714,7 @@ class DownloadWorker : WorkerBase { } setStatus(false, service.getString(R.string.thread_end)) callback.releaseWakeLock() - callback.onThreadEnd(this@DownloadWorker,complete_and_no_repeat) + callback.onThreadEnd(this@DownloadWorker, complete_and_no_repeat) } } diff --git a/app/src/main/java/jp/juggler/fadownloader/NewFileService.kt b/app/src/main/java/jp/juggler/fadownloader/NewFileService.kt deleted file mode 100644 index 6dc5ff8..0000000 --- a/app/src/main/java/jp/juggler/fadownloader/NewFileService.kt +++ /dev/null @@ -1,138 +0,0 @@ -package jp.juggler.fadownloader - -import android.app.IntentService -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.os.Build -import android.support.v4.app.NotificationCompat -import android.support.v4.app.NotificationManagerCompat -import jp.juggler.fadownloader.util.LogWriter -import jp.juggler.fadownloader.util.NotificationHelper - -class NewFileService : IntentService("DownloadCountService") { - - companion object { - - internal const val NOTIFICATION_ID_DOWNLOAD_COMPLETE = 2 - internal const val ACTION_TAP = "tap" - private const val ACTION_DELETE = "delete" - - fun hasHiddenDownloadCount(context : Context) : Boolean { - val previous_count = Pref.downloadCompleteCountHidden(Pref.pref(context)) - return previous_count > 0L - } - - fun addHiddenDownloadCount(context : Context, delta : Long,log:LogWriter) { - val pref = Pref.pref(context) - var hidden_count =Pref.downloadCompleteCountHidden(pref) - if(hidden_count < 0L) hidden_count = 0L - - when { - // ダウンロードしたファイルが増えた際に呼ばれる - delta > 0L -> { - hidden_count += delta - pref.edit().put(Pref.downloadCompleteCountHidden, hidden_count).apply() - return - } - // スキャン完了時にdelta==0で呼ばれる - // 通知を更新する - hidden_count > 0 -> { - // 表示中のカウント値 - var count = Pref.downloadCompleteCount(pref) - if(count < 0L) count = 0L - // 増えた分を追加する - count += hidden_count - hidden_count = 0L - // 値を保存する - pref.edit() - .put(Pref.downloadCompleteCountHidden, hidden_count) - .put(Pref.downloadCompleteCount, count) - .apply() - // 通知を表示する - showCount(context, count,log) - } - } - } - - private fun showCount(context : Context, count : Long,log:LogWriter) { - if(count <= 0L) return - - val builder = if(Build.VERSION.SDK_INT >= 26) { - // Android 8 から、通知のスタイルはユーザが管理することになった - // NotificationChannel を端末に登録しておけば、チャネルごとに管理画面が作られる - val channel = NotificationHelper.createNotificationChannel( - context, - "NewFileDownloaded", - "New file downloaded", - "this notification is shown when new file was downloaded.", - NotificationManager.IMPORTANCE_DEFAULT, - log - ) - NotificationCompat.Builder(context, channel.id) - } else { - NotificationCompat.Builder(context, "not_used") - } - builder.setSmallIcon(R.drawable.ic_service) - builder.setContentTitle(context.getString(R.string.app_name)) - builder.setContentText( - context.getString( - R.string.download_complete_notification, - count - ) - ) - builder.setTicker(context.getString(R.string.download_complete_notification, count)) - builder.setWhen(System.currentTimeMillis()) - builder.setDefaults(NotificationCompat.DEFAULT_ALL) - builder.setAutoCancel(true) - - run { - val intent = Intent(context, NewFileService::class.java) - intent.action = ACTION_TAP - val pi = PendingIntent.getService( - context, - 567, - intent, - PendingIntent.FLAG_UPDATE_CURRENT - ) - builder.setContentIntent(pi) - } - run { - val intent = Intent(context, NewFileService::class.java) - intent.action = ACTION_DELETE - val pi = PendingIntent.getService( - context, - 568, - intent, - PendingIntent.FLAG_UPDATE_CURRENT - ) - builder.setDeleteIntent(pi) - } - - NotificationManagerCompat.from(context) - .notify(NOTIFICATION_ID_DOWNLOAD_COMPLETE, builder.build()) - NewFileWidget.update(context) - } - } - - // 通知をタップ/消去した時に呼ばれる - override fun onHandleIntent(intentArg : Intent?) { - // 通知タップならアプリの画面を開く - if(ACTION_TAP == intentArg?.action) { - val intent = Intent(this, ActMain::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) - intent.putExtra(ActMain.EXTRA_TAB, ActMain.TAB_RECORD) - startActivity(intent) - } - // 通知を消去する - NotificationManagerCompat.from(this).cancel(NOTIFICATION_ID_DOWNLOAD_COMPLETE) - // カウント表示した分をクリアする - Pref.pref(this).edit().put(Pref.downloadCompleteCount, 0).apply() - - // ウィジェットがあればカウントを更新する - NewFileWidget.update(this) - } - - -} diff --git a/app/src/main/java/jp/juggler/fadownloader/NewFileWidget.kt b/app/src/main/java/jp/juggler/fadownloader/NewFileWidget.kt index f8a3b44..6590a3c 100644 --- a/app/src/main/java/jp/juggler/fadownloader/NewFileWidget.kt +++ b/app/src/main/java/jp/juggler/fadownloader/NewFileWidget.kt @@ -1,54 +1,152 @@ package jp.juggler.fadownloader -import android.app.PendingIntent +import android.app.NotificationManager import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProvider import android.content.ComponentName import android.content.Context -import android.content.Intent +import android.os.Build +import android.support.v4.app.NotificationCompat +import android.support.v4.app.NotificationManagerCompat import android.widget.RemoteViews +import jp.juggler.fadownloader.util.NotificationHelper class NewFileWidget : AppWidgetProvider() { - override fun onUpdate( - context : Context, - appWidgetManager : AppWidgetManager, - appWidgetIds : IntArray - ) { + + companion object { + private const val NOTIFICATION_ID_DOWNLOAD_COMPLETE = 2 - if(appWidgetIds.isEmpty()) return - val c_name = ComponentName(context, NewFileWidget::class.java) + @Synchronized + fun hasHiddenDownloadCount(context : Context) : Boolean { + val previous_count = Pref.downloadCompleteCountHidden(Pref.pref(context)) + return previous_count > 0L + } - val count = Pref.downloadCompleteCount(Pref.pref(context)) + @Synchronized + fun addHiddenDownloadCount(context : Context, delta : Long) { + val pref = Pref.pref(context) + var hidden_count = Pref.downloadCompleteCountHidden(pref) + if(hidden_count < 0L) hidden_count = 0L + + when { + // ダウンロードしたファイルが増えた際に呼ばれる + delta > 0L -> { + hidden_count += delta + pref.edit().put(Pref.downloadCompleteCountHidden, hidden_count).apply() + return + } + // スキャン完了時にdelta==0で呼ばれる + // 通知を更新する + hidden_count > 0 -> { + // 表示中のカウント値 + var count = Pref.downloadCompleteCount(pref) + if(count < 0L) count = 0L + // 増えた分を追加する + count += hidden_count + hidden_count = 0L + // 値を保存する + pref.edit() + .put(Pref.downloadCompleteCountHidden, hidden_count) + .put(Pref.downloadCompleteCount, count) + .apply() + + // 通知を表示する + showNotification(context, count) + + // ウィジェットを更新する + NewFileWidget.showWidget(context, count) + } + } + } - appWidgetManager.updateAppWidget(c_name, createRemoteViews(context, count)) - } - - companion object { + private fun showNotification(context : Context, count : Long) { + if(count <= 0L) return + + val builder = if(Build.VERSION.SDK_INT >= 26) { + // Android 8 から、通知のスタイルはユーザが管理することになった + // NotificationChannel を端末に登録しておけば、チャネルごとに管理画面が作られる + val channel = NotificationHelper.createNotificationChannel( + context, + "NewFileDownloaded", + "New file downloaded", + "this notification is shown when new file was downloaded.", + NotificationManager.IMPORTANCE_DEFAULT + ) + NotificationCompat.Builder(context, channel.id) + } else { + NotificationCompat.Builder(context, "not_used") + } + builder.setSmallIcon(R.drawable.ic_service) + builder.setContentTitle(context.getString(R.string.app_name)) + builder.setContentText( + context.getString( + R.string.download_complete_notification, + count + ) + ) + builder.setTicker(context.getString(R.string.download_complete_notification, count)) + builder.setWhen(System.currentTimeMillis()) + builder.setDefaults(NotificationCompat.DEFAULT_ALL) + builder.setAutoCancel(true) + + builder.setContentIntent( Receiver1.piNewFileTap(context)) + + + builder.setDeleteIntent( Receiver1.piNewFileNotificationDelete(context)) + + + + NotificationManagerCompat + .from(context) + .notify(NOTIFICATION_ID_DOWNLOAD_COMPLETE, builder.build()) + + } - fun update(context : Context) { + private fun showWidget(context : Context, count : Long) { val c_name = ComponentName(context, NewFileWidget::class.java) val appWidgetManager = AppWidgetManager.getInstance(context) val appWidgetIds = appWidgetManager.getAppWidgetIds(c_name) if(appWidgetIds == null || appWidgetIds.isEmpty()) return - val count = Pref.downloadCompleteCount( Pref.pref(context) ) - appWidgetManager.updateAppWidget(c_name, createRemoteViews(context, count)) } private fun createRemoteViews(context : Context, count : Long) : RemoteViews { - // NewFileService を ACTION_TAP つきで呼び出すPendingIntent - val intent = Intent(context, NewFileService::class.java) - intent.action = NewFileService.ACTION_TAP - val pendingIntent = PendingIntent.getService(context, 569, intent, 0) - // RemoteViewsを調整 val views = RemoteViews(context.packageName, R.layout.new_file_widget) views.setTextViewText(R.id.tvCount, java.lang.Long.toString(count)) - views.setOnClickPendingIntent(R.id.llWidget, pendingIntent) + views.setOnClickPendingIntent(R.id.llWidget, Receiver1.piNewFileTap(context)) return views } + + // レシーバーから呼ばれる + fun clearDownloadCount(context : Context) { + + // カウント表示した分をクリアする + Pref.pref(context).edit().put(Pref.downloadCompleteCount, 0).apply() + + // 通知を消去する + NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID_DOWNLOAD_COMPLETE) + + // ウィジェットがあれば表示を更新する + NewFileWidget.showWidget(context, 0) + } + } + + override fun onUpdate( + context : Context, + appWidgetManager : AppWidgetManager, + appWidgetIds : IntArray + ) { + + if(appWidgetIds.isEmpty()) return + val c_name = ComponentName(context, NewFileWidget::class.java) + + val count = Pref.downloadCompleteCount(Pref.pref(context)) + + appWidgetManager.updateAppWidget(c_name, createRemoteViews(context, count)) } + } diff --git a/app/src/main/java/jp/juggler/fadownloader/PageOther.kt b/app/src/main/java/jp/juggler/fadownloader/PageOther.kt index 2c13231..d97ae37 100644 --- a/app/src/main/java/jp/juggler/fadownloader/PageOther.kt +++ b/app/src/main/java/jp/juggler/fadownloader/PageOther.kt @@ -44,7 +44,7 @@ class PageOther(activity : Activity, ignored : View) : null ) - R.id.btnOSSLicence -> (activity as ActMain).openHelp(activity.getString(R.string.help_oss_license_long)) + R.id.btnOSSLicence -> (activity as ActMain).openHelp(activity.getString(R.string.oss_license_long_help)) R.id.btnRemoveAd -> (activity as ActMain).startRemoveAdPurchase() diff --git a/app/src/main/java/jp/juggler/fadownloader/PageSetting.kt b/app/src/main/java/jp/juggler/fadownloader/PageSetting.kt index 695f6ab..dfd5503 100644 --- a/app/src/main/java/jp/juggler/fadownloader/PageSetting.kt +++ b/app/src/main/java/jp/juggler/fadownloader/PageSetting.kt @@ -6,6 +6,7 @@ import android.content.Intent import android.content.SharedPreferences import android.net.Uri import android.os.Build +import android.support.annotation.StringRes import android.support.v4.provider.DocumentFile import android.view.View import android.widget.* @@ -18,15 +19,24 @@ class PageSetting(activity : Activity, ignored : View) : PagerAdapterBase.PageViewHolder(activity, ignored), View.OnClickListener { private lateinit var spTargetType : Spinner + private lateinit var spLocationMode : Spinner + internal lateinit var etTargetUrl : EditText + private lateinit var etFileType : EditText + private lateinit var swForceWifi : Switch + private lateinit var etSSID : EditText + private lateinit var tvLocalFolder : TextView + private lateinit var etInterval : EditText - private lateinit var etFileType : EditText - private lateinit var spLocationMode : Spinner private lateinit var etLocationIntervalDesired : EditText private lateinit var etLocationIntervalMin : EditText - private lateinit var swForceWifi : Switch - private lateinit var etSSID : EditText + private lateinit var etTetherSprayInterval : EditText + private lateinit var etTetherTestConnectionTimeout : EditText + private lateinit var etWifiChangeApInterval : EditText + private lateinit var etWifiScanInterval : EditText + + private lateinit var swThumbnailAutoRotate : Switch private lateinit var swCopyBeforeViewSend : Switch private lateinit var swProtectedOnly : Switch @@ -56,6 +66,11 @@ class PageSetting(activity : Activity, ignored : View) : btnSSIDPicker = root.findViewById(R.id.btnSSIDPicker) swProtectedOnly = root.findViewById(R.id.swProtectedOnly) swSkipAlreadyDownload = root.findViewById(R.id.swSkipAlreadyDownload) + etTetherSprayInterval = root.findViewById(R.id.etTetherSprayInterval) + etTetherTestConnectionTimeout = root.findViewById(R.id.etTetherTestConnectionTimeout) + etWifiChangeApInterval = root.findViewById(R.id.etWifiChangeApInterval) + etWifiScanInterval = root.findViewById(R.id.etWifiScanInterval) + root.findViewById(R.id.btnFolderPicker).setOnClickListener(this) root.findViewById(R.id.btnHelpFolderPicker).setOnClickListener(this) @@ -74,6 +89,11 @@ class PageSetting(activity : Activity, ignored : View) : root.findViewById(R.id.btnHelpProtectedOnly).setOnClickListener(this) root.findViewById(R.id.btnHelpSkipAlreadyDownload).setOnClickListener(this) + root.findViewById(R.id.btnTetherSprayIntervalHelp).setOnClickListener(this) + root.findViewById(R.id.btnTetherTestConnectionTimeoutHelp).setOnClickListener(this) + root.findViewById(R.id.btnWifiScanIntervalHelp).setOnClickListener(this) + root.findViewById(R.id.btnWifiChangeApIntervalHelp).setOnClickListener(this) + val location_mode_adapter = ArrayAdapter( activity, android.R.layout.simple_spinner_item ) @@ -176,33 +196,39 @@ class PageSetting(activity : Activity, ignored : View) : e.apply() } + private fun openHelp(@StringRes stringId:Int){ + (activity as ActMain).openHelp(activity.getString(stringId)) + } + override fun onClick(view : View) { when(view.id) { R.id.btnFolderPicker -> folder_pick() R.id.btnSSIDPicker -> ssid_pick() R.id.btnHelpFolderPicker -> if(Build.VERSION.SDK_INT >= LocalFile.DOCUMENT_FILE_VERSION) { - (activity as ActMain).openHelp(R.layout.help_local_folder) + (activity as ActMain).openHelpLayout(R.layout.help_local_folder) } else { - (activity as ActMain).openHelp(activity.getString(R.string.local_folder_help_kitkat)) + openHelp(R.string.local_folder_help_kitkat) } - R.id.btnHelpTargetUrl -> (activity as ActMain).openHelp(activity.getString(R.string.target_url_help)) - R.id.btnIntervalHelp -> (activity as ActMain).openHelp(activity.getString(R.string.repeat_interval_help_text)) - R.id.btnFileTypeHelp -> (activity as ActMain).openHelp(activity.getString(R.string.file_type_help)) - R.id.btnLocationModeHelp -> (activity as ActMain).openHelp(activity.getString(R.string.geo_tagging_mode_help)) - R.id.btnLocationIntervalDesiredHelp -> (activity as ActMain).openHelp( - activity.getString( - R.string.help_location_interval_desired - ) - ) - R.id.btnLocationIntervalMinHelp -> (activity as ActMain).openHelp(activity.getString(R.string.help_location_interval_min)) - R.id.btnForceWifiHelp -> (activity as ActMain).openHelp(activity.getString(R.string.force_wifi_help)) - R.id.btnSSIDHelp -> (activity as ActMain).openHelp(activity.getString(R.string.wifi_ap_ssid_help)) - R.id.btnThumbnailAutoRotateHelp -> (activity as ActMain).openHelp(activity.getString(R.string.help_thumbnail_auto_rotate)) - R.id.btnCopyBeforeViewSendHelp -> (activity as ActMain).openHelp(activity.getString(R.string.help_copy_before_view_send)) - R.id.btnTargetTypeHelp -> (activity as ActMain).openHelp(activity.getString(R.string.target_type_help)) - R.id.btnHelpProtectedOnly -> (activity as ActMain).openHelp(activity.getString(R.string.protected_only_help)) - R.id.btnHelpSkipAlreadyDownload -> (activity as ActMain).openHelp(activity.getString(R.string.skip_already_downloaded_help)) + R.id.btnHelpTargetUrl -> openHelp(R.string.target_url_help) + R.id.btnIntervalHelp -> openHelp(R.string.repeat_interval_help_text) + R.id.btnFileTypeHelp -> openHelp(R.string.file_type_help) + R.id.btnLocationModeHelp -> openHelp(R.string.geo_tagging_mode_help) + + R.id.btnForceWifiHelp ->openHelp(R.string.force_wifi_help) + R.id.btnSSIDHelp -> openHelp(R.string.wifi_ap_ssid_help) + R.id.btnThumbnailAutoRotateHelp -> openHelp(R.string.thumbnail_auto_rotate_help) + R.id.btnCopyBeforeViewSendHelp -> openHelp(R.string.copy_before_view_send_help) + R.id.btnTargetTypeHelp -> openHelp(R.string.target_type_help) + R.id.btnHelpProtectedOnly -> openHelp(R.string.protected_only_help) + R.id.btnHelpSkipAlreadyDownload -> openHelp(R.string.skip_already_downloaded_help) + + R.id.btnLocationIntervalDesiredHelp ->openHelp(R.string.location_interval_desired_help) + R.id.btnLocationIntervalMinHelp ->openHelp(R.string.location_interval_min_help) + R.id.btnTetherSprayIntervalHelp ->openHelp(R.string.tether_spray_interval_help) + 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) } } @@ -225,6 +251,13 @@ class PageSetting(activity : Activity, ignored : View) : etLocationIntervalDesired.setText(Pref.uiLocationIntervalDesired(pref)) etLocationIntervalMin.setText( Pref.uiLocationIntervalMin( pref)) etSSID.setText(Pref.uiSsid(pref)) + etTetherSprayInterval.setText(Pref.uiTetherSprayInterval(pref)) + etTetherTestConnectionTimeout.setText(Pref.uiTetherTestConnectionTimeout(pref)) + etWifiChangeApInterval.setText(Pref.uiWifiChangeApInterval(pref)) + etWifiScanInterval.setText(Pref.uiWifiScanInterval(pref)) + + + // integer var iv = Pref.uiTargetType(pref) @@ -255,6 +288,10 @@ class PageSetting(activity : Activity, ignored : View) : e .put(Pref.uiTargetType, spTargetType.selectedItemPosition) .put(Pref.uiInterval, etInterval.text.toString()) + .put(Pref.uiTetherSprayInterval, etTetherSprayInterval.text.toString()) + .put(Pref.uiTetherTestConnectionTimeout, etTetherTestConnectionTimeout.text.toString()) + .put(Pref.uiWifiChangeApInterval, etWifiChangeApInterval.text.toString()) + .put(Pref.uiWifiScanInterval, etWifiScanInterval.text.toString()) .put(Pref.uiFileType, etFileType.text.toString()) .put(Pref.uiLocationMode, spLocationMode.selectedItemPosition) .put(Pref.uiLocationIntervalDesired, etLocationIntervalDesired.text.toString()) diff --git a/app/src/main/java/jp/juggler/fadownloader/Pref.kt b/app/src/main/java/jp/juggler/fadownloader/Pref.kt index b3dcda6..f9b9a68 100644 --- a/app/src/main/java/jp/juggler/fadownloader/Pref.kt +++ b/app/src/main/java/jp/juggler/fadownloader/Pref.kt @@ -1,6 +1,7 @@ package jp.juggler.fadownloader import android.content.Context +import android.content.Intent import android.content.SharedPreferences import jp.juggler.fadownloader.tracker.LocationTracker @@ -15,17 +16,20 @@ class BooleanPref( ) : BasePref(key) { operator fun invoke(pref : SharedPreferences) = pref.getBoolean(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) } fun SharedPreferences.Editor.put(bp : IntPref, v : Int) : SharedPreferences.Editor = @@ -48,22 +52,53 @@ 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 - // fun getInt(pref : SharedPreferences) = try { - // invoke(pref).trim().toInt() - // } catch(ex : Throwable) { - // defVal.toInt() - // } - +// 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 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:StringPref){ + putExtra(p.key,p(pref)) +} +fun Intent.put(pref:SharedPreferences,p:IntPref){ + putExtra(p.key,p(pref)) } -fun SharedPreferences.Editor.put(bp : StringPref, v : String) : SharedPreferences.Editor = - this.putString(bp.key, v) +fun Intent.put(v:String,p:StringPref){ + putExtra(p.key,v) +} + +fun Intent.put(v:Int,p:IntPref){ + putExtra(p.key,v) +} object Pref { fun pref(context : Context) : SharedPreferences { @@ -80,6 +115,11 @@ 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 + // UI画面に表示されている情報の永続化 val uiRepeat = BooleanPref("ui_repeat", false) val uiForceWifi = BooleanPref("ui_force_wifi", false) @@ -93,10 +133,15 @@ object Pref { val uiLocationMode = IntPref("ui_location_mode", LocationTracker.DEFAULT_MODE) val uiFolderUri = StringPref("ui_folder_uri", "") - val uiInterval = StringPref("ui_interval", "30") 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 uiLocationIntervalDesired = StringPref( "ui_location_interval_desired", (LocationTracker.DEFAULT_INTERVAL_DESIRED / 1000L).toString() @@ -157,6 +202,11 @@ object Pref { 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) + ////////////////////////////////////////////////////////////////////// // 最後に押した動作ボタンとその時刻 diff --git a/app/src/main/java/jp/juggler/fadownloader/Receiver1.kt b/app/src/main/java/jp/juggler/fadownloader/Receiver1.kt index 5425a01..4cb447f 100644 --- a/app/src/main/java/jp/juggler/fadownloader/Receiver1.kt +++ b/app/src/main/java/jp/juggler/fadownloader/Receiver1.kt @@ -1,5 +1,7 @@ package jp.juggler.fadownloader +import android.app.AlarmManager +import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -7,34 +9,118 @@ import android.support.v4.content.ContextCompat import jp.juggler.fadownloader.util.LogTag class Receiver1 : BroadcastReceiver() { - + companion object { private val log = LogTag("Receiver1") + const val ACTION_ALARM = "alarm" + + const val ACTION_NEW_FILE_NOTIFICATION_TAP = "newFileNotificationTap" + + const val ACTION_NEW_FILE_NOTIFICATION_DELETE = "newFileNotificationDelete" + + private fun intentReceiver1(context : Context, action : String) : Intent { + val intent = Intent(context, Receiver1::class.java) + intent.action = action + return intent + } + + fun piActivity(context : Context) : PendingIntent { + val intent = Intent(context, ActMain::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) + return PendingIntent.getActivity( + context, + 565, + intent, + PendingIntent.FLAG_UPDATE_CURRENT + ) + } + + fun piAlarm(context : Context) : PendingIntent { + return PendingIntent.getBroadcast( + context, + 566, + intentReceiver1(context, ACTION_ALARM), + PendingIntent.FLAG_UPDATE_CURRENT + ) + } + + fun piNewFileTap(context : Context) : PendingIntent = + PendingIntent.getBroadcast( + context, + 567, + intentReceiver1(context, Receiver1.ACTION_NEW_FILE_NOTIFICATION_TAP), + PendingIntent.FLAG_UPDATE_CURRENT + ) + + fun piNewFileNotificationDelete(context : Context) : PendingIntent = + PendingIntent.getBroadcast( + context, + 568, + intentReceiver1(context, Receiver1.ACTION_NEW_FILE_NOTIFICATION_DELETE), + PendingIntent.FLAG_UPDATE_CURRENT + ) + + fun cancelAlarm(context:Context){ + try { + val am = context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager + am?.cancel( piAlarm(context)) + } catch(ex : Throwable) { + log.trace(ex, "cancelAlarm failed.") + } + } + + fun openApp(context : Context) { + val intent = Intent(context, ActMain::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) + intent.putExtra(ActMain.EXTRA_TAB, ActMain.TAB_RECORD) + context.startActivity(intent) + } + + fun openService(context : Context, broadcast_intent : Intent) { + val service_intent = Intent(context, DownloadService::class.java) + service_intent.action = DownloadService.ACTION_BROADCAST_RECEIVED + service_intent.putExtra(DownloadService.EXTRA_BROADCAST_INTENT, broadcast_intent) + ContextCompat.startForegroundService(context, service_intent) + } + + } override fun onReceive(context : Context, broadcast_intent : Intent) { try { - val last_mode = Pref.lastMode(Pref.pref(context)) - when(broadcast_intent.action) { + + // ダウンロードファイル数の通知やウィジェットのタップ + ACTION_NEW_FILE_NOTIFICATION_TAP -> { + NewFileWidget.clearDownloadCount(context) + openApp(context) + return + } + + // ダウンロードファイル数の通知をスワイプで消去した + ACTION_NEW_FILE_NOTIFICATION_DELETE -> { + NewFileWidget.clearDownloadCount(context) + return + } + Intent.ACTION_BOOT_COMPLETED -> { - // 繰り返しモード以外では起動時の常駐は行わない - if(last_mode != Pref.LAST_MODE_REPEAT) return + // 繰り返しモードなら端末の起動時に常駐開始する + if(Pref.lastMode(Pref.pref(context)) == Pref.LAST_MODE_REPEAT) { + openService(context, broadcast_intent) + } } ACTION_ALARM -> { + // 実行中ならアラームでサービス再開する // 停止ボタンを押した後はアラームによる起動は行わない - if(last_mode == Pref.LAST_MODE_STOP) return + if(Pref.lastMode(Pref.pref(context)) != Pref.LAST_MODE_STOP) { + openService(context, broadcast_intent) + } } } - - val service_intent = Intent(context, DownloadService::class.java) - service_intent.action = DownloadService.ACTION_BROADCAST_RECEIVED - service_intent.putExtra(DownloadService.EXTRA_BROADCAST_INTENT, broadcast_intent) - ContextCompat.startForegroundService(context, service_intent) } catch(ex : Throwable) { - log.trace(ex,"onReceive failed.") + log.trace(ex, "onReceive failed.") } } } 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 6f8da4f..17b930b 100644 --- a/app/src/main/java/jp/juggler/fadownloader/targets/FlashAir.kt +++ b/app/src/main/java/jp/juggler/fadownloader/targets/FlashAir.kt @@ -6,7 +6,6 @@ import jp.juggler.fadownloader.* import jp.juggler.fadownloader.model.LocalFile import jp.juggler.fadownloader.model.ScanItem import jp.juggler.fadownloader.table.DownloadRecord -import jp.juggler.fadownloader.util.LogTag import jp.juggler.fadownloader.util.Utils import jp.juggler.fadownloader.util.decodeUTF8 import java.io.File @@ -16,7 +15,7 @@ import java.util.regex.Pattern class FlashAir(private val service : DownloadService, internal val thread : DownloadWorker) { companion object { - private val logStatic = LogTag("FlashAir") + // private val logStatic = LogTag("FlashAir") internal val reLine = Pattern.compile("([^\\x0d\\x0a]+)") internal val reAttr = Pattern.compile(",(\\d+),(\\d+),(\\d+),(\\d+)$") @@ -143,12 +142,12 @@ class FlashAir(private val service : DownloadService, internal val thread : Down } } if(!matched) { - logStatic.d("$file_name not match in file_type_list") + // logStatic.d("$file_name not match in file_type_list") continue } // ローカルのファイルサイズを調べて既読スキップ if(thread.checkSkip(local_file, log, size)) { - logStatic.d("$file_name already downloaded.") + // logStatic.d("$file_name already downloaded.") continue } @@ -274,9 +273,9 @@ 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.last_result.get() - val air_url = service.wifi_tracker.last_flash_air_url.get() - if(tracker_last_result && air_url != null) { + val tracker_last_result = service.wifi_tracker.bLastConnected.get() + val air_url = service.wifi_tracker.lastTargetUrl.get() + if(tracker_last_result && air_url.isNotEmpty() ) { thread.target_url = air_url break } diff --git a/app/src/main/java/jp/juggler/fadownloader/targets/PentaxKP.kt b/app/src/main/java/jp/juggler/fadownloader/targets/PentaxKP.kt index 16109a6..2e5b741 100644 --- a/app/src/main/java/jp/juggler/fadownloader/targets/PentaxKP.kt +++ b/app/src/main/java/jp/juggler/fadownloader/targets/PentaxKP.kt @@ -429,7 +429,7 @@ class PentaxKP(private val service : DownloadService, internal val thread : Down log.e(ex, "WebSocket connection failed(2).") val active_other = service.wifi_tracker.otherActive - if( active_other?.isNotEmpty() ==true ) { + if( active_other.isNotEmpty() ) { log.w(R.string.other_active_warning, active_other) } 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 ef0eec2..d9b5895 100644 --- a/app/src/main/java/jp/juggler/fadownloader/targets/PqiAirCard.kt +++ b/app/src/main/java/jp/juggler/fadownloader/targets/PqiAirCard.kt @@ -298,9 +298,9 @@ class PqiAirCard( if(thread.target_type == Pref.TARGET_TYPE_PQI_AIR_CARD_TETHER) { while(! thread.isCancelled) { - val tracker_last_result = service.wifi_tracker.last_result.get() - val air_url = service.wifi_tracker.last_flash_air_url.get() - if(tracker_last_result && air_url != null) { + val tracker_last_result = service.wifi_tracker.bLastConnected.get() + val air_url = service.wifi_tracker.lastTargetUrl.get() + if(tracker_last_result && air_url.isNotEmpty()) { thread.target_url = air_url break } 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 56f0ea2..f3459e6 100644 --- a/app/src/main/java/jp/juggler/fadownloader/tracker/LocationTracker.kt +++ b/app/src/main/java/jp/juggler/fadownloader/tracker/LocationTracker.kt @@ -32,17 +32,17 @@ class LocationTracker( } class Setting( - internal var mode : Int = 0, + internal val mode : Int = 0, // Sets the desired intervalSeconds for active location updates. This intervalSeconds is // inexact. You may not receive updates at all if no location sources are available, or // you may receive them slower than requested. You may also receive updates faster than // requested if other applications are requesting location at a faster intervalSeconds. - var interval_desired : Long = 0, + val interval_desired : Long = 0, // Sets the fastest rate for active location updates. // This intervalSeconds is exact, and your application will never receive updates faster than this value. - var interval_min : Long = 0 + val interval_min : Long = 0 ) { internal val isUpdateRequired : Boolean 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 3635a10..f0412ad 100644 --- a/app/src/main/java/jp/juggler/fadownloader/tracker/NetworkTracker.kt +++ b/app/src/main/java/jp/juggler/fadownloader/tracker/NetworkTracker.kt @@ -15,50 +15,91 @@ import android.os.SystemClock import jp.juggler.fadownloader.Pref import jp.juggler.fadownloader.R import jp.juggler.fadownloader.util.* -import org.apache.commons.io.IOUtils -import java.io.ByteArrayOutputStream -import java.io.File -import java.io.FileInputStream import java.net.* import java.util.* import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicReference import java.util.regex.Pattern +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 : (is_connected : Boolean, cause : String) -> Unit +) : BroadcastReceiver() { companion object { private val logStatic = LogTag("NetworkTracker") - const val WIFI_SCAN_INTERVAL = 10000 + private const val TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED" - //////////////////////////////////////////////////////////////////////// + private val reIPAddr = Pattern.compile("(\\d+\\.\\d+\\.\\d+\\.\\d+)") - internal fun readStringFile(path : String) : String? { - try { - FileInputStream(File(path)).use{fis-> - val bao = ByteArrayOutputStream() - IOUtils.copy(fis, bao) - return (bao.toByteArray() as ByteArray).decodeUTF8() - } - } catch(ex : Throwable) { - logStatic.trace(ex,"readStringFile") - return null + private val reNotV4Address = "[^\\d.]+".toRegex() + + private val reLastDigits = "\\d+$".toRegex() + + private val reArp = + Pattern.compile("(\\d+\\.\\d+\\.\\d+\\.\\d+)\\s*(0x\\d+)\\s*(0x\\d+)\\s*([0-9A-Fa-f:]+)") + + } + + 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() { + + 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) } } - internal fun buildCurrentStatus(ns_list : ArrayList) : String { - Collections.sort(ns_list, Comparator { a, b -> - if(a.is_active && ! b.is_active) return@Comparator - 1 - if(! a.is_active && b.is_active) 1 else a.type_name !!.compareTo(b.type_name !!) + 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 ns_list) { + for(ns in this) { if(sb.isNotEmpty()) sb.append(" / ") if(ns.is_active) sb.append("(Active)") if(ns.strWifiStatus != null) { @@ -74,157 +115,232 @@ class NetworkTracker( return sb.toString() } - internal val reIPAddr = Pattern.compile("(\\d+\\.\\d+\\.\\d+\\.\\d+)") - internal val reArp = - Pattern.compile("(\\d+\\.\\d+\\.\\d+\\.\\d+)\\s*(0x\\d+)\\s*(0x\\d+)\\s*([0-9A-Fa-f:]+)") + var force_status : String? = null + var error_status : String? = null } - internal interface UrlChecker { - fun checkUrl(url : String?) : Boolean + // API 26以降でpriorityは使えなくなった + @Suppress("DEPRECATION") + private fun getPriority(wc : WifiConfiguration) : Int { + return wc.priority } + // 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 ); + } + } + } catch(ex : Throwable) { + log.trace(ex, "updateNetwork() or saveConfiguration() failed.") + return ex.withCaption("updateNetwork() or saveConfiguration() failed.") + } + return null + } - internal val wifiManager : WifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager - internal val cm : ConnectivityManager = context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + private val wifiManager = + context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager + private val connectivityManager = + context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - internal var worker : Worker? = null + private val isTetheringEnabled : Boolean + get() { + try { + val rv = wifiManager.javaClass.getMethod("isWifiApEnabled").invoke(wifiManager) + if(rv is Boolean) return rv + log.e("isWifiApEnabled returns $rv") + } catch(ex : Throwable) { + log.trace(ex, "isTetheringEnabled") + } + return false + } - @Volatile - internal var is_dispose = false + private val tetheringAddress : String? + get() { + try { + val en = NetworkInterface.getNetworkInterfaces() + while(en.hasMoreElements()) { + val ni = en.nextElement() + try { + if(! ni.isUp) continue + if(ni.isLoopback) continue + if(ni.isVirtual) continue + if(ni.isPointToPoint) continue + if(ni.hardwareAddress == null) continue + val eip = ni.inetAddresses + while(eip.hasMoreElements()) { + val addr = eip.nextElement() + if(addr.address.size == 4) { + return addr.hostAddress.replace(reNotV4Address, "") + } + } + } catch(ex : SocketException) { + log.trace(ex, "wiFiAPAddress") + } + } + } catch(ex : SocketException) { + log.trace(ex, "wiFiAPAddress") + } + + return null + } - private val receiver : BroadcastReceiver = object : BroadcastReceiver() { - override fun onReceive(context : Context, intent : Intent) { - if(is_dispose) return - worker ?.notifyEx() + // ネットワークアドレス(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") + } + + } + socket.close() + } catch(ex : Throwable) { + log.trace(ex, "sprayUDPPacket") } + + log.v("sent UDP packet to '$ip_base*' time=${Utils.formatTimeDuration(SystemClock.elapsedRealtime() - start)}") } + private var worker : Worker? = null + @Volatile - internal var force_wifi : Boolean = false - @Volatile - internal var target_ssid : String? = null - @Volatile - internal var target_type : Int = 0 // カードはAPモードではなくSTAモードもしくはインターネット同時接続もーどで動作している - - @Volatile - internal var target_url : String? = null + internal var is_dispose = false //////////////////////////////////////////////////////////////////////// - val last_result = AtomicBoolean() - val last_flash_air_url = AtomicReference() - internal val last_current_status = AtomicReference() + private var timeLastSpray = 0L + private var timeLastWiFiScan : Long = 0 + private var timeLastWiFiApChange : Long = 0 - internal val last_other_active = AtomicReference() + val bLastConnected = AtomicBoolean() - val otherActive : String? - get() = last_other_active.get() + private var last_force_status = AtomicReferenceNotNull("") + private var last_error_status = AtomicReferenceNotNull("") + private var last_current_status = AtomicReferenceNotNull("") + private var last_other_active = AtomicReferenceNotNull("") - internal val urlChecker_FlashAir : UrlChecker = object : - UrlChecker { - override fun checkUrl(url : String?) : Boolean { - return checkUrl_sub(url, url + "command.cgi?op=108") - } - } + val lastTargetUrl = AtomicReferenceNotNull("") - internal val urlChecker_PqiAirCard : UrlChecker = object : - UrlChecker { - override fun checkUrl(url : String?) : Boolean { - return checkUrl_sub(url, url + "cgi-bin/get_config.pl") - } - } + val otherActive : String + get() = last_other_active.get() - init { - - context.registerReceiver(receiver, IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) - context.registerReceiver(receiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) - worker = Worker() - worker !!.start() + 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() { is_dispose = true - context.unregisterReceiver(receiver) - if(worker != null) { - worker !!.cancel("disposed") - worker = null - } + context.unregisterReceiver(this) + worker?.cancel("disposed") + worker = null } - fun updateSetting(force_wifi : Boolean, ssid : String?, target_type : Int, target_url : String?) { - if(is_dispose) return - this.force_wifi = force_wifi - this.target_ssid = ssid - this.target_type = target_type - this.target_url = target_url - if(worker != null) worker !!.notifyEx() + 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,") + } + } + log.d(sb.toString()) + } + if(! is_dispose) worker?.notifyEx() } - internal class NetworkStatus { + class Setting( + val force_wifi : Boolean = false, + val target_ssid : String = "", + val target_type : Int = 0, + val target_url : String = "http://flashair/", - var is_active : Boolean = false - var type_name : String? = null - var sub_name : String? = null - var strWifiStatus : String? = null + 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 + worker?.notifyEx() } - internal class NetworkStateList : ArrayList() { - - var wifi_status : NetworkStatus? = null - var other_active : String? = null - - fun eachNetworkInfo(is_active : Boolean, ni : NetworkInfo) { - val is_wifi = ni.type == ConnectivityManager.TYPE_WIFI - if(! is_wifi && ! ni.isConnected) return - val ns = NetworkStatus() - this.add(ns) - if(is_wifi) wifi_status = ns - ns.type_name = ni.typeName - ns.sub_name = ni.subtypeName - - if(is_active) { - ns.is_active = true - if(! is_wifi) other_active = ns.type_name - } - } - - fun afterAllNetwork() { - if(wifi_status == null) { - val ws = NetworkStatus() - ws.type_name = "WIFI" - wifi_status = ws - this.add(ws) - } - } + fun getStatus() : String { + return requireNotNull(last_current_status.get()) } - fun getStatus(sb : StringBuilder) { - sb.append(last_current_status) + 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") + } - internal fun checkUrl_sub(target_url : String?, check_url : String) : Boolean { - target_url ?: return false - + private fun checkUrl_sub(target_url : String, check_url : String) : Boolean { + var bFound = false try { val urlObject = URL(check_url) val conn = urlObject.openConnection() as HttpURLConnection try { conn.doInput = true - conn.connectTimeout = 30000 - conn.readTimeout = 30000 + 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 != last_flash_air_url.get()) { + if(target_url != lastTargetUrl.get()) { log.i("target detected. %s", target_url) } - last_flash_air_url.set(target_url) + lastTargetUrl.set(target_url) bFound = true } } finally { @@ -235,132 +351,59 @@ class NetworkTracker( } } catch(ex : Throwable) { - log.trace(ex,"failed: $check_url") - log.e(ex, check_url) + when(ex) { + + is ConnectException ->{ + } + + is SocketTimeoutException -> { + // 通信エラーをトレースするとキリがないのでしない + log.w(ex.withCaption(check_url)) + } + + else -> { + log.trace(ex, check_url) + log.e(ex, check_url) + } + } } return bFound } - internal inner class Worker : WorkerBase() { + // 接続先が見つかったら0L + // またはリトライまでの秒数を返す + private fun detectTetheringClient(env : NetworkStateList,url_checker : (String) -> Boolean) : Long { - private val isWifiAPEnabled : Boolean - get() { - try { - return wifiManager.javaClass.getMethod("isWifiApEnabled").invoke(wifiManager) as Boolean - } catch(ex : Throwable) { - log.trace(ex,"isWifiAPEnabled") - } - - return false + // 設定で指定されたURLを最初に試す + // ターゲットURLにIPアドレスが書かれているなら、それを最初に試す + if(reIPAddr.matcher(setting.target_url).find()) { + if(url_checker(setting.target_url)) { + return 0L } + } - private val wiFiAPAddress : String? - get() { - try { - val en = NetworkInterface.getNetworkInterfaces() - while(en.hasMoreElements()) { - val ni = en.nextElement() - try { - if(! ni.isUp) continue - if(ni.isLoopback) continue - if(ni.isVirtual) continue - if(ni.isPointToPoint) continue - if(ni.hardwareAddress == null) continue - val eip = ni.inetAddresses - while(eip.hasMoreElements()) { - val addr = eip.nextElement() - if(addr.address.size == 4) { - return addr.hostAddress.replace("[^\\d.]+".toRegex(), "") - } - } - } catch(ex : SocketException) { - log.trace(ex,"wiFiAPAddress") - } - } - } catch(ex : SocketException) { - log.trace(ex,"wiFiAPAddress") - } - - return null - } - - private val priority_list = LinkedList() - - private var last_force_status : String? = null - private var last_error_status : String? = null - private var last_wifi_ap_change : Long = 0 - private var last_wifi_scan_start : Long = 0 - - override fun cancel(reason : String) : Boolean { - val rv = super.cancel(reason) - try { - this.interrupt() - } catch(ignored : Throwable) { - } - - return rv + if(! isTetheringEnabled) { + env.error_status = "Wi-Fi Tethering is not enabled." + // TETHER_STATE_CHANGED があるのでリトライ間隔は長めでもよさそう + return 5000L } - 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") - } - - } - socket.close() - } catch(ex : Throwable) { - log.trace(ex,"sprayUDPPacket") - } - - log.d("sent UDP packet to '$ip_base*' time=${Utils.formatTimeDuration(SystemClock.elapsedRealtime() - start)}") + val tethering_address = tetheringAddress + if(tethering_address == null) { + env.error_status= "missing Wi-Fi Tethering IP address." + return 1000L } - private fun detectTetheringClient(url_checker : UrlChecker) : Boolean { - - // 設定で指定されたURLを最初に試す - // ただしURLにIPアドレスが書かれている場合のみ - if(reIPAddr.matcher(target_url).find()) { - if(url_checker.checkUrl(target_url)) { - return true - } - } - - if(! isWifiAPEnabled) { - log.d("Wi-Fi Tethering is not enabled.") - return false - } - - val tethering_address = wiFiAPAddress - if(tethering_address == null) { - log.w("missing Wi-Fi Tethering IP address.") - return false - } - val ip_base = tethering_address.replace("\\d+$".toRegex(), "") - - // ARPテーブルの読み出し - val strArp = - readStringFile("/proc/net/arp") - if(strArp == null) { - log.e("can not read ARP table.") - return false - } + // "XXX.XXX.XXX." + val ip_base = tethering_address.replace(reLastDigits, "") + + // ARPテーブルの読み出し + val strArp = Utils.readStringFile("/proc/net/arp") + if(strArp == null) { + env.error_status = "Can't read ARP table." + } else { + val list = ArrayList() // ARPテーブル中のIPアドレスを確認 val m = reArp.matcher(strArp) while(m.find()) { @@ -372,336 +415,373 @@ class NetworkTracker( if(item_mac == "00:00:00:00:00:00" || ! item_ip.startsWith(ip_base)) continue - if(url_checker.checkUrl("http://$item_ip/")) { - return true + list.add(item_ip) + } + if( list.isEmpty() ) { + env.error_status = "missing devices in ARP table." + }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 + } } } - - // カードが見つからない場合 - // 直接ARPリクエストを投げるのは難しい?ので - // UDPパケットをばらまく - // 次回以降の確認で効果があるといいな + } + + // カードが見つからない場合 + // 直接ARPリクエストを投げるのは難しい?のでUDPパケットをばらまく + // 次回以降の確認で効果ARPテーブルを読めれば良いのだが…。 + val now = SystemClock.elapsedRealtime() + val remain = timeLastSpray + setting.tetherSprayInterval - now + return if(remain > 0L) { + remain + } else { + timeLastSpray = now sprayUDPPacket(tethering_address, ip_base) - - return false + 1000L } + } + + + + private fun keep_ap() : Long { - private fun keep_ap() : Boolean { - if(isCancelled) return false - - val ns_list = NetworkStateList() - var force_status : String? = null - var error_status : String? = null - try { - if(Build.VERSION.SDK_INT >= 23) { - var active_handle : Long? = null - val an = cm.activeNetwork - if(an != null) { - active_handle = an.networkHandle - } - val src_list = cm.allNetworks - if(src_list != null) { - for(n in src_list) { - val is_active = - active_handle != null && active_handle == n.networkHandle - val ni = cm.getNetworkInfo(n) - ns_list.eachNetworkInfo(is_active, ni) - } - } - } else { - var active_name : String? = null - val ani = cm.activeNetworkInfo - if(ani != null) { - active_name = ani.typeName - } - @Suppress("DEPRECATION") - val src_list = cm.allNetworkInfo - if(src_list != null) { - for(ni in src_list) { - val is_active = active_name != null && active_name == ni.typeName - ns_list.eachNetworkInfo(is_active, ni) - } + val ns_list = NetworkStateList() + try { + if(Build.VERSION.SDK_INT >= 23) { + var active_handle : Long? = null + val an = connectivityManager.activeNetwork + if(an != null) { + active_handle = an.networkHandle + } + val src_list = connectivityManager.allNetworks + if(src_list != null) { + for(n in src_list) { + val is_active = + active_handle != null && active_handle == n.networkHandle + val ni = connectivityManager.getNetworkInfo(n) + ns_list.addNetworkInfo(is_active, ni) } } - ns_list.afterAllNetwork() - last_other_active.set(ns_list.other_active) - - if(target_type == Pref.TARGET_TYPE_FLASHAIR_STA) { - // FlashAir STAモードの時の処理 - return detectTetheringClient(urlChecker_FlashAir) - } else if(target_type == Pref.TARGET_TYPE_PQI_AIR_CARD_TETHER) { - // PQI Air Card Tethering モードの時の処理 - return detectTetheringClient(urlChecker_PqiAirCard) + } else { + var active_name : String? = null + val ani = connectivityManager.activeNetworkInfo + if(ani != null) { + active_name = ani.typeName } - - // Wi-Fiが無効なら有効にする - try { - ns_list.wifi_status !!.strWifiStatus = "?" - if(! wifiManager.isWifiEnabled) { - ns_list.wifi_status !!.strWifiStatus = - context.getString(R.string.not_enabled) - if(force_wifi) wifiManager.isWifiEnabled = true - return false + @Suppress("DEPRECATION") + val src_list = connectivityManager.allNetworkInfo + if(src_list != null) { + for(ni in src_list) { + val is_active = active_name != null && active_name == ni.typeName + ns_list.addNetworkInfo(is_active, ni) } - } catch(ex : Throwable) { - log.trace(ex,"setWifiEnabled() failed.") - error_status = ex.withCaption( "setWifiEnabled() failed.") - return false + } + } + ns_list.afterAddAll() + last_other_active.set(ns_list.other_active) + + // テザリングモードの処理 + when(setting.target_type) { + Pref.TARGET_TYPE_FLASHAIR_STA -> { + return detectTetheringClient(ns_list,urlChecker_FlashAir) } - // 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 - val sv = info.ssid - current_ssid = sv?.replace("\"", "") + Pref.TARGET_TYPE_PQI_AIR_CARD_TETHER -> { + return detectTetheringClient(ns_list,urlChecker_PqiAirCard) + } + } + + 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,"getConnectionInfo() failed.") - error_status = ex.withCaption( "getConnectionInfo() failed.") - return false } + } 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) - // 設定済みのAPを列挙する - var current_network_id = 0 - var target_config : WifiConfiguration? = null - var priority_max = 0 - try { - ns_list.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 wc_list = wifiManager.configuredNetworks - if(wc_list == null) { - // getConfiguredNetworks() はたまにnullを返す - return false - } else { - for(wc in wc_list) { - val ssid = wc.SSID.replace("\"", "") - - val p = getPriority(wc) - - if(p > priority_max) { - priority_max = p - } - - // 目的のAPを覚えておく - if(target_ssid != null && target_ssid == ssid) { - target_config = wc - } - - // 接続中のAPの情報 - if(ssid == current_ssid) { - current_network_id = wc.networkId - // - var strState = current_supp_state?.toString() ?: "?" - strState = Utils.toCamelCase(strState) - if("Completed" == strState) strState = "Connected" - ns_list.wifi_status !!.strWifiStatus = "$ssid,$strState" - if(! force_wifi) { - // AP強制ではないなら、何かアクティブな接続が生きていればOK - return current_supp_state == SupplicantState.COMPLETED - } - } - } + 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" - if(! force_wifi) { - // AP強制ではない場合、接続中のAPがなければNGを返す - return false - } else if(target_config == null) { - force_status = - context.getString(R.string.wifi_target_ssid_not_found, target_ssid) - return false + wifi_status.strWifiStatus = "$ssid,$strState" + + // AP強制ではないなら、何かアクティブな接続が生きていればOK + if(! setting.force_wifi && current_supp_state == SupplicantState.COMPLETED) { + return 0L } } - - } catch(ex : Throwable) { - log.trace(ex,"getConfiguredNetworks() failed.") - error_status = ex.withCaption( "getConfiguredNetworks() failed.") - return false } - - if( Build.VERSION.SDK_INT < 26){ - val error = updatePriority(target_config,priority_max) - if(error != null) error_status = error - } - - // 目的のAPが選択されていた場合 - if(current_ssid != null && current_network_id == target_config.networkId) { - when(current_supp_state) { - SupplicantState.COMPLETED -> - // その接続の認証が終わっていて、他の種類の接続がActiveでなければOK - return ns_list.other_active == null - SupplicantState.ASSOCIATING, SupplicantState.ASSOCIATED, SupplicantState.AUTHENTICATING, SupplicantState.FOUR_WAY_HANDSHAKE, SupplicantState.GROUP_HANDSHAKE -> - // 現在のstateが何か作業中なら、余計なことはしないがOKでもない - return false - else->{} + // 列挙終了 + 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 + } } - - // スキャン範囲内に目的の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(target_ssid != null && target_ssid == result.SSID.replace("\"", "")) { - found_in_scan = true - break + } 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 } } - } catch(ex : Throwable) { - log.trace(ex,"getScanResults() failed.") - error_status = ex.withCaption( "getScanResults() failed.") - return false + + else -> { + // fall + } } - - // スキャン範囲内にない場合、定期的にスキャン開始 - if(! found_in_scan) { - try { - force_status = context.getString(R.string.wifi_target_ssid_not_scanned, target_ssid) - val now = SystemClock.elapsedRealtime() - val remain = last_wifi_scan_start + WIFI_SCAN_INTERVAL - now - if( remain > 0L){ - val lastSeenBefore = if(lastSeen==null) null else now - (lastSeen/1000L) - logStatic.d("$target_ssid is not found in latest scan result(${lastSeenBefore}ms before). next scan is start after ${remain}ms.") - }else{ - last_wifi_scan_start = now - wifiManager.startScan() - log.d(R.string.wifi_scan_start) + } + + // スキャン結果に目的の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 } - } catch(ex : Throwable) { - log.trace(ex,"startScan() failed.") - error_status = ex.withCaption( "startScan() failed.") } - return false + 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 = last_wifi_ap_change + 5000L - now - if( remain > 0L){ + 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") - }else{ - last_wifi_ap_change = now - + min(remain, 3000L) + } else { + timeLastWiFiApChange = now try { // 先に既存接続を無効にする for(wc in wifiManager.configuredNetworks) { - if(wc.networkId != target_config.networkId) { - val ssid = wc.SSID.replace("\"", "") - if(wc.status == WifiConfiguration.Status.CURRENT) { - log.i("%sから切断させます", ssid) + 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) - } else if(wc.status == WifiConfiguration.Status.ENABLED) { - log.i("%sへの自動接続を無効化します", ssid) + } + + WifiConfiguration.Status.ENABLED -> { + log.v("${ssid}への自動接続を無効化します") wifiManager.disableNetwork(wc.networkId) } } } - val target_ssid = target_config.SSID.replace("\"", "") - log.i("%s への接続を試みます", target_ssid) + val target_ssid = target_config.SSID.filterSsid() + log.i("${target_ssid}への接続を試みます") wifiManager.enableNetwork(target_config.networkId, true) - - return false - + 1000L } catch(ex : Throwable) { - log.trace(ex,"disableNetwork() or enableNetwork() failed.") - error_status = - ex.withCaption( "disableNetwork() or enableNetwork() failed.") + log.trace(ex, "disableNetwork() or enableNetwork() failed.") + ns_list.error_status = ex.withCaption("disableNetwork() or enableNetwork() failed.") + 10000L } - - } - - return false - } finally { - val current_status = - buildCurrentStatus( - ns_list - ) - if(current_status != last_current_status.get()) { - last_current_status.set(current_status) - log.d(context.getString(R.string.network_status, current_status)) - } - - if(error_status != null && error_status != last_error_status) { - last_error_status = error_status - log.e(error_status) - } - - if(force_status != null && force_status != last_force_status) { - last_force_status = force_status - log.w(force_status) } } + } finally { + // 状態の変化があればログに出力する + + val current_status = ns_list.buildCurrentStatus() + + if(current_status != last_current_status.get()) { + last_current_status.set(current_status) + log.d(context.getString(R.string.network_status, current_status)) + } + + val error_status = ns_list.error_status + if(error_status != null && error_status != last_error_status.get()) { + last_error_status.set(error_status) + log.e(error_status) + } + + val force_status = ns_list.force_status + if(force_status != null && force_status != last_force_status.get()) { + last_force_status.set(force_status) + log.w(force_status) + } } + } + + internal inner class Worker : WorkerBase() { - // API 26以降でpriorityは使えなくなった - @Suppress("DEPRECATION") - private fun getPriority(wc:WifiConfiguration):Int{ - return wc.priority - } - - // API 26以降でpriorityは使えなくなった - @Suppress("DEPRECATION") - private fun updatePriority(target_config:WifiConfiguration,priority_max:Int) :String? { + override fun cancel(reason : String) : Boolean { + val rv = super.cancel(reason) + try { - // 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 ); - } - } - } catch(ex : Throwable) { - log.trace(ex,"updateNetwork() or saveConfiguration() failed.") - return ex.withCaption( "updateNetwork() or saveConfiguration() failed.") + this.interrupt() + } catch(ignored : Throwable) { } - return null + + return rv } override fun run() { while(! isCancelled) { - var result : Boolean - try { - result = keep_ap() - if(isCancelled) break + + val result = try { + keep_ap() } catch(ex : Throwable) { - log.trace(ex,"network check failed.") + log.trace(ex, "network check failed.") log.e(ex, "network check failed.") - result = false + 5000L } - if(result != last_result.get()) { - last_result.set(result) - Utils.runOnMainThread{ + if(isCancelled) break + + val bConnected = result <= 0L + if(bConnected != bLastConnected.get()) { + // 接続状態の変化 + bLastConnected.set(bConnected) + Utils.runOnMainThread { try { - if(!is_dispose) callback(true, "Wi-Fi tracker") + if(! is_dispose) callback(true, "Wi-Fi tracker") } catch(ex : Throwable) { - log.trace(ex,"connection event handling failed.") + log.trace(ex, "connection event handling failed.") log.e(ex, "connection event handling failed.") } } } - val next = if(result) 5000L else 1000L - waitEx(next) + + waitEx(if(result <= 0L) 5000L else result) } } - - } - } diff --git a/app/src/main/java/jp/juggler/fadownloader/tracker/WorkerTracker.kt b/app/src/main/java/jp/juggler/fadownloader/tracker/WorkerTracker.kt index 8084bd2..9e5413a 100644 --- a/app/src/main/java/jp/juggler/fadownloader/tracker/WorkerTracker.kt +++ b/app/src/main/java/jp/juggler/fadownloader/tracker/WorkerTracker.kt @@ -60,7 +60,7 @@ class WorkerTracker( } override fun onAllFileCompleted(count : Long) { - service.addHiddenDownloadCount(count,log) + service.addHiddenDownloadCount(count) } override fun hasHiddenDownloadCount() : Boolean { diff --git a/app/src/main/java/jp/juggler/fadownloader/util/AtomicReferenceNotNull.kt b/app/src/main/java/jp/juggler/fadownloader/util/AtomicReferenceNotNull.kt new file mode 100644 index 0000000..2f1e014 --- /dev/null +++ b/app/src/main/java/jp/juggler/fadownloader/util/AtomicReferenceNotNull.kt @@ -0,0 +1,18 @@ +package jp.juggler.fadownloader.util + +import java.util.concurrent.atomic.AtomicReference + +class AtomicReferenceNotNull(value : V) { + + private val ar = AtomicReference(value) + + fun get() : V = requireNotNull(ar.get()) + fun set(value : V) = ar.set(value) + fun lazySet(value : V) = ar.lazySet(value) + fun compareAndSet(expect : V, update : V) = ar.compareAndSet(expect, update) + fun weakCompareAndSet(expect : V, update : V) = ar.weakCompareAndSet(expect, update) + fun getAndSet(newValue : V) : V = requireNotNull(ar.getAndSet(newValue)) + override fun toString() : String { + return get().toString() + } +} \ No newline at end of file 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 48d23f6..4ed9c59 100644 --- a/app/src/main/java/jp/juggler/fadownloader/util/LogTag.kt +++ b/app/src/main/java/jp/juggler/fadownloader/util/LogTag.kt @@ -15,10 +15,14 @@ class LogTag(category:String){ private val tag = "$TAG:$category" + fun v(fmt:String,vararg args:Any?){ + Log.v(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)) } diff --git a/app/src/main/java/jp/juggler/fadownloader/util/NotificationHelper.kt b/app/src/main/java/jp/juggler/fadownloader/util/NotificationHelper.kt index 819c754..38e0040 100644 --- a/app/src/main/java/jp/juggler/fadownloader/util/NotificationHelper.kt +++ b/app/src/main/java/jp/juggler/fadownloader/util/NotificationHelper.kt @@ -7,6 +7,7 @@ import android.content.Context object NotificationHelper { + private val log = LogTag("NotificationHelper") @TargetApi(26) fun createNotificationChannel( @@ -14,7 +15,6 @@ object NotificationHelper { , name : String // The user-visible name of the channel. , description : String? // The user-visible description of the channel. , importance : Int - , log : LogWriter ) : NotificationChannel { val notification_manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager? diff --git a/app/src/main/java/jp/juggler/fadownloader/util/Utils.kt b/app/src/main/java/jp/juggler/fadownloader/util/Utils.kt index 5e63355..2e8967d 100644 --- a/app/src/main/java/jp/juggler/fadownloader/util/Utils.kt +++ b/app/src/main/java/jp/juggler/fadownloader/util/Utils.kt @@ -1,9 +1,7 @@ package jp.juggler.fadownloader.util import android.annotation.SuppressLint -import android.app.PendingIntent import android.content.Context -import android.content.Intent import android.content.res.Resources import android.database.Cursor import android.net.Uri @@ -14,13 +12,10 @@ import android.util.SparseBooleanArray import android.util.SparseIntArray import android.webkit.MimeTypeMap import android.widget.Toast -import jp.juggler.fadownloader.Receiver1 +import org.apache.commons.io.IOUtils import org.w3c.dom.Element import org.w3c.dom.NamedNodeMap -import java.io.ByteArrayInputStream -import java.io.File -import java.io.FileInputStream -import java.io.IOException +import java.io.* import java.security.MessageDigest import java.text.DecimalFormat import java.util.* @@ -125,12 +120,7 @@ object Utils { // return sb.toString(); } - fun createAlarmPendingIntent(context : Context) : PendingIntent { - val i = Intent(context.applicationContext, Receiver1::class.java) - i.action = Receiver1.ACTION_ALARM - return PendingIntent.getBroadcast(context.applicationContext, 0, i, 0) - } - + // 文字列と整数の変換 @Suppress("unused") fun parse_int(v : String, defVal : Int) : Int { @@ -527,6 +517,18 @@ object Utils { return null } + internal fun readStringFile(path : String) : String? { + try { + FileInputStream(File(path)).use{fis-> + val bao = ByteArrayOutputStream() + IOUtils.copy(fis, bao) + return bao.toByteArray().decodeUTF8() + } + } catch(ex : Throwable) { + log.trace(ex,"readStringFile") + return null + } + } } // 文字列とバイト列の変換 @@ -580,4 +582,6 @@ fun Throwable.withCaption(resources : Resources, string_id : Int, vararg args : return "$text : ${javaClass.simpleName} $message" } -fun Throwable.withCaption() = withCaption("?") \ No newline at end of file +fun Throwable.withCaption() = withCaption("?") + +fun String.filterSsid() =replace("\"", "") diff --git a/app/src/main/res/layout/act_main.xml b/app/src/main/res/layout/act_main.xml index 58a33d8..f1eec70 100644 --- a/app/src/main/res/layout/act_main.xml +++ b/app/src/main/res/layout/act_main.xml @@ -8,7 +8,7 @@ android:layout_height="match_parent" android:orientation="vertical" tools:context="jp.juggler.fadownloader.ActMain" - tools:ignore="SpUsage" + tools:ignore="SpUsage,UnusedAttribute" > diff --git a/app/src/main/res/layout/page_setting.xml b/app/src/main/res/layout/page_setting.xml index 8f2c8a0..9397751 100644 --- a/app/src/main/res/layout/page_setting.xml +++ b/app/src/main/res/layout/page_setting.xml @@ -1,6 +1,7 @@