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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
@@ -307,19 +398,21 @@
-
@@ -328,25 +421,29 @@
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 3ec4110..5e4e718 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -23,6 +23,7 @@
Target Typeを指定してください
Target URL
+ 接続先のURLを指定
Target URLを指定してください
Local Folder
@@ -31,10 +32,11 @@
Local Folder にはスマホ上の、画像をダウンロードして保存するフォルダを指定します\n\nAndroid 4.4.x の場合、このアプリからSDカードに書き込むことはできません。「Pictures」の下あたりにFADownloader用のフォルダを作成してください。\n
ファイル拡張子
+ ファイル拡張子を空白区切りで指定
ファイル拡張子を1つ以上指定してください
- Repeat Interval
- Repeat Intervalを1以上で指定してください
+ Repeatの間隔
+ Repeatの間隔を1以上で指定してください
アプリに権限が必要です
参考画像
@@ -66,15 +68,19 @@
FlashAirは更新されてません
FlashAir更新ステータスのデータエラー
+ PQI Air Card
+ PQI Air Card (テザリング)
+
GeoTagging Mode
GeoTagging Modeを設定すると、スマホから位置情報を取得してJPEGファイルのEXIF情報に位置データを付与します。\nサービス起動中はずっと位置情報を要求し続けます。\n位置精度が高いモードほどバッテリー消費が大きいことに注意してください。\n\nGoogle Play サービスの位置情報APIを利用しているため、Playサービスのない端末では位置情報の埋め込みは使えません。
位置取得頻度(希望)
+ 位置取得頻度(希望)には位置情報を更新する時間間隔の希望値を秒数で指定します。\n短い方が位置情報の精度は良くなりますが、バッテリー消費は大きくなります。\n\n端末やPlayサービスにより調整が行われるため、希望通りの時間間隔になるとは限りません。\n
+
位置取得頻度(最短)
+ 位置取得頻度(最短)には位置情報を更新する時間間隔の最低値を秒数で指定します。\n少ない数字ほど位置情報の精度は良くなりますが、バッテリー消費も大きくなります。
- 位置取得頻度(希望)には位置情報を更新する時間間隔の希望値を秒数で指定します。\n短い方が位置情報の精度は良くなりますが、バッテリー消費は大きくなります。\n\n端末やPlayサービスにより調整が行われるため、希望通りの時間間隔になるとは限りません。\n
- 位置取得頻度(最短)には位置情報を更新する時間間隔の最低値を秒数で指定します。\n少ない数字ほど位置情報の精度は良くなりますが、バッテリー消費も大きくなります。
接続先ホスト名を解決できません。もしかして:端末のWi-Fiから接続先のWi-Fi APに接続していない
接続先ホストが見つからない
@@ -97,21 +103,29 @@
位置取得頻度には1以上の整数を指定してください
位置取得を開始しました
位置情報設定の精度変更要求が失敗しました
- このアプリは sephiroth74氏の Android-Exif-Extended ライブラリを(SAF対応のために少し改造して)使用しています。\nhttps://github.com/sephiroth74/Android-Exif-Extended\nThis software is licensed under the Apache 2 license, quoted below.\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\nhttp://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n
+ 少し待機しましたが位置情報を取得できません。JPEG画像への位置情報の付与を行えません。
+
+ このアプリは sephiroth74氏の Android-Exif-Extended ライブラリを(SAF対応のために少し改造して)使用しています。\nhttps://github.com/sephiroth74/Android-Exif-Extended\nThis software is licensed under the Apache 2 license, quoted below.\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\nhttp://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n
+
位置取得を終了しました
変更した設定はREPEAT/ONCEを押すと反映されます
広告非表示
その他
OSSライセンス情報
- Wi-Fi AP半強制
SSIDを指定してください
+
+ Wi-Fi AP半強制
+ Wi-Fi AP半強制をONにしてWi-Fi APのSSIDを入力すると、Wi-Fi接続がより安定します。\n・Wi-FiがOFFになっていたらONにします\n・指定したSSIDの優先度を上げます\n・指定したSSIDと現在の接続先が異なる場合、Wi-Fi APのスキャンを定期的に行います\n・指定したSSIDがスキャン結果に含まれる場合、そのWi-Fi APに接続先を切り替えます\n
+
Wi-Fi APのSSID
+ SSIDを入力
+ Wi-Fi AP %1$s への接続設定がありません
+ Wi-Fiスキャン結果に%1$sがありません
+
現在 Wi-Fiが有効ではありません
Wi-Fiスキャンを開始しました
- Wi-Fi AP %1$s への接続設定がありません
- Wi-Fiスキャン結果に%1$sがありません
フォルダ選択
「%1$s」を選択
上のフォルダ階層へ
@@ -125,8 +139,11 @@
Playストアがありません
フォルダは既に存在します
フォルダ作成に失敗しました.
+
フォルダの名前
フォルダの名前を指定してください
+ フォルダの名前
+
Target URLに到達できません
通信状態が悪いようです
ダウンロード履歴
@@ -135,36 +152,49 @@
SSIDの選択
ダウンロード中断
ダウンロード完了
+ %1$dファイルをダウンロードしました
+
未取得
残り
送る
見る
進捗
- 見る/送るの際にファイルをコピー
- ダウンロード履歴から「見る/送る」で外部アプリを起動する際、Android OS バージョンによっては SDカードなどの non-primari storage へのアクセスが制限されるため、外部アプリがそのファイルを扱えないことがあります。\nこの設定をONにすると、外部アプリを開く際に、ファイルを端末のダウンロードフォルダにコピーして、コピー後のファイルURIを外部アプリに渡すようにします。\nデフォルトはOFFです。
- ダウンロード履歴に表示されるサムネイルを、Exifの回転情報を使って自動回転します。デフォルトはONです。
サムネイル
- サムネイルの自動回転
+
+
通信状態: %1$s
接続先ファイル一覧のデータエラー
接続先ファイル一覧の取得中
Wi-Fi以外の接続 %1$s がActiveになっています。OFFにするとTargetに接続できるかもしれません
ログを全て消去します。よろしいですか?
ログファイルの準備中…
- PQI Air Card
- PQI Air Card (テザリング)
- %1$dファイルをダウンロードしました
新しいファイル
+
+ 見る/送るの際にファイルをコピー
+ ONにすると、ダウンロード履歴から「見る/送る」で外部アプリを起動する際、Android OS バージョンによっては SDカードなどの non-primari storage へのアクセスが制限されるため、外部アプリがそのファイルを扱えないことがあります。\nこの設定をONにすると、外部アプリを開く際に、ファイルを端末のダウンロードフォルダにコピーして、コピー後のファイルURIを外部アプリに渡すようにします。\nデフォルトはOFFです。
+
+ サムネイルの自動回転
+ ONにすると、ダウンロード履歴に表示されるサムネイルを、Exifの回転情報を使って自動回転します。デフォルトはONです。
+
読み取り専用属性のついたファイルだけを転送
ONにすると、プロテクト(リードオンリー属性)のついたファイルだけを転送対象にします。 現時点ではFlashAirのみこの機能に対応しています
+
ダウンロード履歴に名前があるファイルはスキップ
- ダウンロード履歴に名前があるファイルはダウンロードしません。
- 少し待機しましたが位置情報を取得できません。JPEG画像への位置情報の付与を行えません。
- URL of target SD card.
- space separated list of file extension(s).
- seconds of repeat scan interval
- seconds of desired location update interval
- seconds of minimun location update interval
- Wi-Fi SSID of SD card
- folder name
+ ONにすると、ダウンロード履歴に名前があるファイルはダウンロードしません。
+
+
+ UDPばら撒き間隔 (テザリング時)
+ テザリングモードでは、このアプリはデバイスのアドレスを検出するためにローカルネットワーク内の全てのアドレスにUDPパケットをばら撒きます。\nその時間間隔をこの設定で変更できます。単位は秒数、デフォルトは3です。
+
+ テスト接続のタイムアウト (テザリング時)
+ テザリングモードでは、このアプリはデバイスへのHTTP接続をテストします。しかしこのテストは通信状況などの理由により待たされた挙句失敗する場合があります。\nそのタイムアウトをこの設定で変更できます。単位は秒数、デフォルトは15です。
+
+ Wi-Fiスキャン間隔 (Wi-Fi AP半強制時)
+ Wi-Fi AP半強制モードでは、指定されたAPが過去のWi-Fiスキャン結果に含まれない場合にこのアプリは定期的にWi-Fiスキャンの開始を行います。\nしかし頻繁なWi-Fiスキャンは端末のバッテリーを浪費します。\nその時間間隔をこの設定で変更できます。単位は秒数、デフォルトは10です。
+
+ Wi-Fi AP変更間隔
+ Wi-Fi AP半強制モードでは、指定されたAPがスキャン結果に含まれるが選択されていない場合にこのアプリは定期的にW-Fi APの変更を試みます。\nその時間間隔をこの設定で変更できます。単位は秒数、デフォルトは5です。
+
+ 秒数を指定
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b4a9153..14ff880 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -123,8 +123,8 @@ FADownloaderはテザリング内部のLANにUDPパケットをばらまいた
location update interval(desired)
location update interval(min)
- location interval min には位置情報を更新する時間間隔の最低値を秒数で指定します。\n少ない数字ほど位置情報の精度は良くなりますが、バッテリー消費も大きくなります。
- location interval desired には位置情報を更新する時間間隔の希望値を秒数で指定します。\n短い方が位置情報の精度は良くなりますが、バッテリー消費は大きくなります。\n\n端末やPlayサービスにより調整が行われるため、希望通りの時間間隔になるとは限りません。\n
+ location interval min には位置情報を更新する時間間隔の最低値を秒数で指定します。\n少ない数字ほど位置情報の精度は良くなりますが、バッテリー消費も大きくなります。
+ location interval desired には位置情報を更新する時間間隔の希望値を秒数で指定します。\n短い方が位置情報の精度は良くなりますが、バッテリー消費は大きくなります。\n\n端末やPlayサービスにより調整が行われるため、希望通りの時間間隔になるとは限りません。\n
location setting change unavailable
location setting returns unknown status %1$d
@@ -141,16 +141,16 @@ FADownloaderはテザリング内部のLANにUDPパケットをばらまいた
location last known. location.time=%1$s
location update start.
OSS license information
- このアプリは sephiroth74氏の Android-Exif-Extended ライブラリを(SAF対応のために少し改造して)使用しています。\nhttps://github.com/sephiroth74/Android-Exif-Extended\nThis software is licensed under the Apache 2 license, quoted below.\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\nhttp://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n
+ このアプリは sephiroth74氏の Android-Exif-Extended ライブラリを(SAF対応のために少し改造して)使用しています。\nhttps://github.com/sephiroth74/Android-Exif-Extended\nThis software is licensed under the Apache 2 license, quoted below.\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\nhttp://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n
location update end
変更した設定はREPEAT/ONCEを押すと反映されます
remove ad
Force Wi-Fi AP
- Wi-Fi強制機能をONにしてWi-Fi AP SSIDを入力すると、Wi-Fi接続がより安定します。\n・Wi-FiがOFFになっていたらONにします\n・指定したSSIDの優先度を上げます\n・指定したSSIDと現在の接続先が異なる場合、Wi-Fi APのスキャンを定期的に行います\n・指定したSSIDがスキャン結果に含まれる場合、そのWi-Fi APに接続先を切り替えます\n
+ Enables this option and input SSID of target SD-LAN card, then this app try to force use specified SSID.\n・Enables Wi-Fi if disabled.\n・Start Wi-Fi scan if missing target SSID is not found in scan result.\n・Select specified SSID if it is found in scan result, but it is not selected.\n\nnotice: this option may waste battery while target AP is not available.
Wi-Fi AP SSID
- 「Wi-Fi AP半強制」を有効にすると編集可能になります。Wi-Fi AP SSIDにはターゲットのWi-Fi APのSSIDを指定してください
+ This setting is pair with \"Force Wi-Fi AP\".\nPlease input SSID of target SD-LAN card. then this app try to force use specified SSID.
SSID is empty.
無効
@@ -191,8 +191,8 @@ FADownloaderはテザリング内部のLANにUDPパケットをばらまいた
thumbnail
auto rotate thumbnails
copy before view/send
- ダウンロード履歴に表示されるサムネイルを、Exifの回転情報を使って自動回転します。デフォルトはONです。
- ダウンロード履歴から「見る/送る」で外部アプリを起動する際、Android OS バージョンによっては SDカードなどの non-primari storage へのアクセスが制限されるため、外部アプリがそのファイルを扱えないことがあります。\nこの設定をONにすると、外部アプリを開く際に、ファイルを端末のダウンロードフォルダにコピーして、コピー後のファイルURIを外部アプリに渡すようにします。\nデフォルトはOFFです。
+ ダウンロード履歴に表示されるサムネイルを、Exifの回転情報を使って自動回転します。デフォルトはONです。
+ ダウンロード履歴から「見る/送る」で外部アプリを起動する際、Android OS バージョンによっては SDカードなどの non-primari storage へのアクセスが制限されるため、外部アプリがそのファイルを扱えないことがあります。\nこの設定をONにすると、外部アプリを開く際に、ファイルを端末のダウンロードフォルダにコピーして、コピー後のファイルURIを外部アプリに渡すようにします。\nデフォルトはOFFです。
network status: %1$s
@@ -213,10 +213,18 @@ FADownloaderはテザリング内部のLANにUDPパケットをばらまいた
Waited for a while, but failed to acquire location information. Can not update location information for JPEG image.
URL of target SD card.
space separated list of file extension(s).
- seconds of repeat scan interval
- seconds of desired location update interval
- seconds of minimun location update interval
Wi-Fi SSID of SD card
folder name
+ UDP spray interval (tethering mode)
+ In tethering mode, this app sends UDP packets to all addressed in local network for detecting the target device.\nThis setting can change its intervals.\nUnit is seconds, default is 3.
+ Test connection timeout(tethering mode)
+ In tethering mode, this app test HTTP connection to detected device. but sometimes this test will failed by socket timeout.\nThis setting can change its timeouts.\nUnit is seconds, default is 15.
+ Wi-Fi scan interval (force Wi-Fi AP mode)
+ In force Wi-Fi AP mode, this app starts Wi-Fi scan to detect target AP if it\'s is not found in previous scan result.\nBut frequently Wi-Fi scan wastes battery.\nThis setting can change its intervals.\nUnit is seconds, default is 10.
+ Wi-Fi change AP interval (force Wi-Fi AP mode)
+ In force Wi-Fi AP mode, this app change Wi-Fi AP to target AP if it\'s is found in scan result, but it\'s not selected.\nThis setting can change its intervals.\nUnit is seconds, default is 5.
+
+ second(s)
+
diff --git a/checkMissingTranslation.pl b/checkMissingTranslation.pl
new file mode 100644
index 0000000..104dfd4
--- /dev/null
+++ b/checkMissingTranslation.pl
@@ -0,0 +1,102 @@
+#!/usr/bin/perl --
+use XML::Parser;
+use strict;
+use warnings;
+use File::Find;
+use XML::Simple;
+use Data::Dump qw(dump);
+
+my $xml = XML::Simple->new;
+
+my $master_name = "_master";
+
+my @files;
+
+find(sub{
+ return if not -f $_;
+ ($_ eq "strings.xml") and push @files,$File::Find::name;
+},"app/src/main/res/");
+
+@files or die "missing string files.\n";
+
+my %langs;
+for my $file(@files){
+ my $lang;
+ if( $file =~ m|values-([^/]+)| ){
+ $lang = $1;
+ }else{
+ $lang=$master_name;
+ }
+ my $data = $xml->XMLin($file);
+ if( not $data->{string} or ($data->{string}{content} and not ref $data->{string}{content} )){
+ die "!! please make at least 2 string entries in $file\n";
+ }
+
+ my %names;
+ while(my($name,$o)=each %{$data->{string}}){
+ $names{$name}=$o->{content};
+ }
+ $langs{ $lang } = \%names;
+}
+
+my $hasError = 0;
+
+my $master = $langs{ $master_name };
+$master or die "missing master languages.\n";
+my %params;
+while(my($name,$value)=each %$master){
+ my @params = $value =~ /(%\d+\$[\d\.]*[sdxf])/g;
+ $params{$name} = join ',', sort @params;
+}
+
+
+my %missing;
+my %allNames;
+for my $lang ( sort keys %langs ){
+ my $names = $langs{$lang};
+ while(my($name,$value)=each %$names){
+ $allNames{$name}=1;
+ if(not $master->{$name} ){
+ $missing{$name} =1;
+ }
+ my @params = $value =~ /(%\d+\$[\d\.]*[sdxf])/g;
+ my $params = join ',', sort @params;
+ my $master_params = $params{$name} // '';
+ if( $params ne $master_params){
+ $hasError =1;
+ print "!! ($lang)$name : parameter mismatch. master=$master_params, found=$params\n";
+ }
+
+ # 残りの部分に%が登場したらエラー
+ my $sv = $value;
+ $sv =~ s/(%\d+\$[\d\.]*[sdxf])//g;
+ if( $sv =~ /%/ && not $sv=~/:%/ ){
+ $hasError =1;
+ print "!! ($lang)$name : broken param: $sv // $value\n";
+ }
+
+ # エスケープされていないシングルクォートがあればエラー
+ if( $value =~ m/(?