diff --git a/.idea/dictionaries/tateisu.xml b/.idea/dictionaries/tateisu.xml
index ca10fce..e26e720 100644
--- a/.idea/dictionaries/tateisu.xml
+++ b/.idea/dictionaries/tateisu.xml
@@ -8,6 +8,7 @@
cqypc
customtabs
dcim
+ dinglisch
emlsgvh
enetunreach
errored
@@ -22,7 +23,10 @@
ioexception
mobilesdk
realtime
+ requery
sephiroth
+ tasker
+ twofortyfouram
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index d4a7f30..0c53332 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -10,8 +10,8 @@ android {
minSdkVersion min_sdk_version
targetSdkVersion target_sdk_version
- versionCode 21
- versionName "1.14.1"
+ versionCode 22
+ versionName "1.14.2"
applicationId "jp.juggler.fadownloader"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 11c68ab..d0de03d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -49,9 +49,19 @@
android:name="jp.juggler.fadownloader.Receiver1"
android:exported="true"
>
+
+
+
+
+
+
+
+
+
+
@@ -67,11 +77,39 @@
>
-
+
+
+
+
+
+
+
+
+
+
+
+
+
= LocalFile.DOCUMENT_FILE_VERSION) {
- val folder = DocumentFile.fromTreeUri(this, Uri.parse(sv))
- if(folder != null) {
- if(folder.exists() && folder.canWrite()) {
- folder_uri = sv
- }
- }
- } else {
- folder_uri = sv
- }
- }
- if(folder_uri.isEmpty()) {
- Utils.showToast(this, true, getString(R.string.local_folder_not_ok))
- return
- }
-
- val file_type = Pref.uiFileType(pref).trim()
- if(file_type.isEmpty()) {
- Utils.showToast(this, true, getString(R.string.file_type_empty))
- return
- }
-
- val location_mode = Pref.uiLocationMode(pref)
- if(location_mode < 0 || location_mode > LocationTracker.LOCATION_HIGH_ACCURACY) {
- Utils.showToast(this, true, getString(R.string.location_mode_invalid))
- return
+ val error = Receiver1.actionStart(this)
+ if( error != null) {
+ Utils.showToast(this, true, error)
}
-
-
- 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) {
-
- if(! validSeconds(Pref.uiLocationIntervalDesired.getIntOrNull(pref))) {
- 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
- }
- }
-
- val force_wifi = Pref.uiForceWifi(pref)
-
- val ssid : String
- if(! force_wifi) {
- ssid = ""
- } else {
- ssid = Pref.uiSsid(pref).trim()
- if(ssid.isEmpty()) {
- Utils.showToast(this, true, getString(R.string.ssid_empty))
- return
- }
- }
-
- // 最後に押したボタンを覚えておく
- pref.edit()
- .put(Pref.lastMode, if(repeat) Pref.LAST_MODE_REPEAT else Pref.LAST_MODE_ONCE)
- .put(Pref.lastModeUpdate, System.currentTimeMillis())
- .apply()
-
- // 転送サービスを開始
- val intent = Intent(this, DownloadService::class.java)
- intent.action = DownloadService.ACTION_START
-
- 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.uiStopWhenTetheringOff)
- 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)
-
-
- startService(intent)
}
internal fun openHelpLayout(layout_id : Int) {
diff --git a/app/src/main/java/jp/juggler/fadownloader/ActTaskerSettingAction.kt b/app/src/main/java/jp/juggler/fadownloader/ActTaskerSettingAction.kt
new file mode 100644
index 0000000..3504ec9
--- /dev/null
+++ b/app/src/main/java/jp/juggler/fadownloader/ActTaskerSettingAction.kt
@@ -0,0 +1,114 @@
+package jp.juggler.fadownloader
+
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.support.v7.app.AppCompatActivity
+import android.view.View
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.Spinner
+import jp.juggler.fadownloader.util.LogTag
+
+class ActTaskerSettingAction : AppCompatActivity(), View.OnClickListener {
+
+ companion object {
+ val log = LogTag("ActTaskerSettingAction")
+
+ const val STATE_ACTION = "action"
+ }
+
+ private lateinit var spAction : Spinner
+
+ override fun onCreate(savedInstanceState : Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ try {
+ val callingApplicationLabel = packageManager.getApplicationLabel(
+ packageManager.getApplicationInfo(callingPackage, 0)
+ )
+ title = if(callingApplicationLabel?.isNotEmpty() == true) {
+ "$callingApplicationLabel > ${getString(R.string.tasker_setting_action)}"
+ }else{
+ getString(R.string.tasker_setting_action)
+ }
+ } catch(ex : PackageManager.NameNotFoundException) {
+ log.e(ex, "Calling package couldn't be found")
+ }
+
+
+ setContentView(R.layout.act_tasker_setting_action)
+
+ spAction = findViewById(R.id.spAction)
+ val btnSave : View = findViewById(R.id.btnSave)
+
+ btnSave.setOnClickListener(this)
+
+ initSpinner(
+ spAction,
+ arrayOf(
+ getString(R.string.repeat),
+ getString(R.string.once),
+ getString(R.string.stop)
+ ),
+ null
+ )
+ if(savedInstanceState != null) {
+ spAction.setSelection(savedInstanceState.getInt(STATE_ACTION, 0))
+ } else {
+ val b = intent?.extras
+ val actionInt = b?.getInt(Receiver1.EXTRA_ACTION, - 1)
+ when(actionInt) {
+ Pref.LAST_MODE_REPEAT -> spAction.setSelection(0)
+ Pref.LAST_MODE_ONCE -> spAction.setSelection(1)
+ Pref.LAST_MODE_STOP -> spAction.setSelection(2)
+ }
+
+ }
+ }
+
+ override fun onSaveInstanceState(outState : Bundle?) {
+ outState ?: return
+ super.onSaveInstanceState(outState)
+ outState.putInt(STATE_ACTION, spAction.selectedItemPosition)
+ }
+
+ override fun onClick(v : View?) {
+ when(v?.id) {
+ R.id.btnSave -> {
+ val actionInt = when(spAction.selectedItemPosition) {
+ 0 -> Pref.LAST_MODE_REPEAT
+ 1 -> Pref.LAST_MODE_ONCE
+ else -> Pref.LAST_MODE_STOP
+ }
+ val actionString = when(spAction.selectedItemPosition) {
+ 0 -> "REPEAT"
+ 1 -> "ONCE"
+ else -> "STOP"
+ }
+ val b = Bundle()
+ b.putInt(Receiver1.EXTRA_ACTION, actionInt)
+ val intent = Intent()
+ intent.putExtra(Receiver1.EXTRA_TASKER_BUNDLE, b)
+ intent.putExtra(
+ "com.twofortyfouram.locale.intent.extra.BLURB",
+ "action=$actionString"
+ )
+ setResult(RESULT_OK, intent)
+ finish()
+ }
+ }
+ }
+
+ private fun initSpinner(
+ sp : Spinner,
+ choices : Array,
+ action : AdapterView.OnItemSelectedListener?
+ ) {
+ val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item)
+ adapter.setDropDownViewResource(R.layout.spinner_dropdown)
+ adapter.addAll(*choices)
+ sp.adapter = adapter
+ sp.onItemSelectedListener = action
+ }
+}
diff --git a/app/src/main/java/jp/juggler/fadownloader/ActTaskerSettingCondition.kt b/app/src/main/java/jp/juggler/fadownloader/ActTaskerSettingCondition.kt
new file mode 100644
index 0000000..9b4e730
--- /dev/null
+++ b/app/src/main/java/jp/juggler/fadownloader/ActTaskerSettingCondition.kt
@@ -0,0 +1,54 @@
+package jp.juggler.fadownloader
+
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.support.v7.app.AppCompatActivity
+import android.view.View
+import jp.juggler.fadownloader.util.LogTag
+
+class ActTaskerSettingCondition : AppCompatActivity(), View.OnClickListener {
+
+ companion object {
+ val log = LogTag("ActTaskerSettingCondition")
+
+ }
+
+ override fun onCreate(savedInstanceState : Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ try {
+ val callingApplicationLabel = packageManager.getApplicationLabel(
+ packageManager.getApplicationInfo(callingPackage, 0)
+ )
+ title = if(callingApplicationLabel?.isNotEmpty() == true) {
+ "$callingApplicationLabel > ${getString(R.string.tasker_setting_condition)}"
+ }else{
+ getString(R.string.tasker_setting_condition)
+ }
+ } catch(ex : PackageManager.NameNotFoundException) {
+ log.e(ex, "Calling package couldn't be found")
+ }
+
+ setContentView(R.layout.act_tasker_setting_condition)
+
+ findViewById(R.id.btnSave).setOnClickListener(this)
+
+ }
+
+ override fun onClick(v : View?) {
+ when(v?.id) {
+ R.id.btnSave -> {
+ val b = Bundle()
+ val intent = Intent()
+ intent.putExtra(Receiver1.EXTRA_TASKER_BUNDLE, b)
+ intent.putExtra(
+ "com.twofortyfouram.locale.intent.extra.BLURB",
+ getString(R.string.is_alive_service)
+ )
+ setResult(RESULT_OK, intent)
+ finish()
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/jp/juggler/fadownloader/DownloadService.kt b/app/src/main/java/jp/juggler/fadownloader/DownloadService.kt
index 623a178..965d34b 100644
--- a/app/src/main/java/jp/juggler/fadownloader/DownloadService.kt
+++ b/app/src/main/java/jp/juggler/fadownloader/DownloadService.kt
@@ -31,6 +31,7 @@ class DownloadService : Service() {
internal const val NOTIFICATION_ID_SERVICE = 1
+ @Volatile
internal var service_instance : DownloadService? = null
fun getStatusForActivity(context : Context) : String {
@@ -125,6 +126,8 @@ class DownloadService : Service() {
NetworkTracker(this, log, wifi_tracker_callback)
worker_tracker = WorkerTracker(this, log)
+
+ Receiver1.sendTaskerQueryRequery(this,ActTaskerSettingCondition::class.java.name)
}
private val wifi_tracker_callback = object:NetworkTracker.Callback{
@@ -174,6 +177,8 @@ class DownloadService : Service() {
service_instance = null
+ Receiver1.sendTaskerQueryRequery(this,ActTaskerSettingCondition::class.java.name)
+
super.onDestroy()
}
diff --git a/app/src/main/java/jp/juggler/fadownloader/Receiver1.kt b/app/src/main/java/jp/juggler/fadownloader/Receiver1.kt
index 4cb447f..610ed68 100644
--- a/app/src/main/java/jp/juggler/fadownloader/Receiver1.kt
+++ b/app/src/main/java/jp/juggler/fadownloader/Receiver1.kt
@@ -5,18 +5,24 @@ import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
+import android.net.Uri
+import android.os.Build
import android.support.v4.content.ContextCompat
+import android.support.v4.provider.DocumentFile
+import jp.juggler.fadownloader.model.LocalFile
+import jp.juggler.fadownloader.tracker.LocationTracker
import jp.juggler.fadownloader.util.LogTag
+import jp.juggler.fadownloader.util.Utils
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 {
@@ -28,7 +34,7 @@ class Receiver1 : BroadcastReceiver() {
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(
+ return PendingIntent.getActivity(
context,
565,
intent,
@@ -52,19 +58,19 @@ class Receiver1 : BroadcastReceiver() {
intentReceiver1(context, Receiver1.ACTION_NEW_FILE_NOTIFICATION_TAP),
PendingIntent.FLAG_UPDATE_CURRENT
)
-
+
fun piNewFileNotificationDelete(context : Context) : PendingIntent =
- PendingIntent.getBroadcast(
+ PendingIntent.getBroadcast(
context,
568,
intentReceiver1(context, Receiver1.ACTION_NEW_FILE_NOTIFICATION_DELETE),
PendingIntent.FLAG_UPDATE_CURRENT
)
- fun cancelAlarm(context:Context){
+ fun cancelAlarm(context : Context) {
try {
val am = context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
- am?.cancel( piAlarm(context))
+ am?.cancel(piAlarm(context))
} catch(ex : Throwable) {
log.trace(ex, "cancelAlarm failed.")
}
@@ -84,13 +90,198 @@ class Receiver1 : BroadcastReceiver() {
ContextCompat.startForegroundService(context, service_intent)
}
+ fun actionStop(context : Context) {
+ val pref = Pref.pref(context)
+ pref.edit()
+ .put(Pref.lastMode, Pref.LAST_MODE_STOP)
+ .put(Pref.lastModeUpdate, System.currentTimeMillis())
+ .apply()
+ val intent = Intent(context, DownloadService::class.java)
+ context.stopService(intent)
+ cancelAlarm(context)
+ }
+ private fun actionStart(context : Context,repeat:Boolean ){
+ Pref.pref(context).edit().put(Pref.uiRepeat,repeat).apply()
+ val error = actionStart(context)
+ if(error != null){
+ Utils.showToast(context,true,error)
+ }
+ }
+
+ fun actionStart(context : Context) : String? {
+ val pref = Pref.pref(context)
+
+ // LocationSettingを確認する前のrepeat引数の値を思い出す
+ val repeat = Pref.uiRepeat(pref)
+
+ // 設定から値を読んでバリデーション
+ val target_type = Pref.uiTargetType(pref)
+ if(target_type < 0) {
+ return context.getString(R.string.target_type_invalid)
+ }
+
+ val target_url = Pref.loadTargetUrl(pref, target_type)
+ if(target_url.isEmpty()) {
+ return context.getString(R.string.target_url_not_ok)
+ }
+
+ var folder_uri = ""
+ val sv = Pref.uiFolderUri(pref)
+ if(sv.isNotEmpty()) {
+ if(Build.VERSION.SDK_INT >= LocalFile.DOCUMENT_FILE_VERSION) {
+ val folder = DocumentFile.fromTreeUri(context, Uri.parse(sv))
+ if(folder != null) {
+ if(folder.exists() && folder.canWrite()) {
+ folder_uri = sv
+ }
+ }
+ } else {
+ folder_uri = sv
+ }
+ }
+ if(folder_uri.isEmpty()) {
+ return context.getString(R.string.local_folder_not_ok)
+ }
+
+ val file_type = Pref.uiFileType(pref).trim()
+ if(file_type.isEmpty()) {
+ return context.getString(R.string.file_type_empty)
+ }
+
+ val location_mode = Pref.uiLocationMode(pref)
+ if(location_mode < 0 || location_mode > LocationTracker.LOCATION_HIGH_ACCURACY) {
+ return context.getString(R.string.location_mode_invalid)
+ }
+
+
+ fun validSeconds(v : Int?) : Boolean {
+ return v != null && v > 0
+ }
+
+ if(repeat) {
+ if(! validSeconds(Pref.uiInterval.getIntOrNull(pref))) {
+ return context.getString(R.string.repeat_interval_not_ok)
+ }
+ }
+
+ if(location_mode != LocationTracker.NO_LOCATION_UPDATE) {
+
+ if(! validSeconds(Pref.uiLocationIntervalDesired.getIntOrNull(pref))) {
+ return context.getString(R.string.location_update_interval_not_ok)
+ }
+ if(! validSeconds(Pref.uiLocationIntervalMin.getIntOrNull(pref))) {
+ return context.getString(R.string.location_update_interval_not_ok)
+ }
+ }
+
+ val force_wifi = Pref.uiForceWifi(pref)
+
+ val ssid : String
+ if(! force_wifi) {
+ ssid = ""
+ } else {
+ ssid = Pref.uiSsid(pref).trim()
+ if(ssid.isEmpty()) {
+ return context.getString(R.string.ssid_empty)
+ }
+ }
+
+ // 最後に押したボタンを覚えておく
+ pref.edit()
+ .put(Pref.lastMode, if(repeat) Pref.LAST_MODE_REPEAT else Pref.LAST_MODE_ONCE)
+ .put(Pref.lastModeUpdate, System.currentTimeMillis())
+ .apply()
+
+ // 転送サービスを開始
+ val intent = Intent(context, DownloadService::class.java)
+ intent.action = DownloadService.ACTION_START
+
+ 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.uiStopWhenTetheringOff)
+ 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)
+
+ ContextCompat.startForegroundService(context, intent)
+ return null
+ }
+
+ const val ACTION_TASKER_ACTION = "com.twofortyfouram.locale.intent.action.FIRE_SETTING"
+ const val EXTRA_TASKER_BUNDLE = "com.twofortyfouram.locale.intent.extra.BUNDLE"
+ const val EXTRA_ACTION = "action"
+
+ private fun onTaskerAction(context : Context, intent : Intent) {
+ val b = intent.getBundleExtra(EXTRA_TASKER_BUNDLE)
+ if(b != null) {
+ val actionInt = b.getInt(EXTRA_ACTION, - 1)
+ when(actionInt) {
+ Pref.LAST_MODE_STOP -> actionStop(context)
+ Pref.LAST_MODE_ONCE ->actionStart(context,false)
+ Pref.LAST_MODE_REPEAT ->actionStart(context,true)
+ }
+ }
+ }
+
+ const val ACTION_TASKER_QUERY_CONDITION = "com.twofortyfouram.locale.intent.action.QUERY_CONDITION"
+
+ private const val RESULT_CONDITION_SATISFIED = 16
+ private const val RESULT_CONDITION_UNSATISFIED = 17
+ @Suppress("unused")
+ private const val RESULT_CONDITION_UNKNOWN = 18
+
+ private fun onTaskerQueryCondition():Int {
+ val isServiceAlive = DownloadService.service_instance != null
+ return when( isServiceAlive ){
+ true -> RESULT_CONDITION_SATISFIED
+ false-> RESULT_CONDITION_UNSATISFIED
+ }
+ }
+
+ private const val ACTION_REQUEST_QUERY = "com.twofortyfouram.locale.intent.action.REQUEST_QUERY"
+ private const val EXTRA_ACTIVITY = "com.twofortyfouram.locale.intent.extra.ACTIVITY"
+
+ fun sendTaskerQueryRequery(context:Context ,editActivityClassName:String){
+ try {
+ val intent = Intent(ACTION_REQUEST_QUERY)
+ intent.putExtra(EXTRA_ACTIVITY, editActivityClassName)
+ context.sendBroadcast(intent)
+ }catch(ex:Throwable){
+ log.e(ex,"sendTaskerQueryRequery failed.")
+ }
+ }
}
override fun onReceive(context : Context, broadcast_intent : Intent) {
try {
when(broadcast_intent.action) {
+ ACTION_TASKER_QUERY_CONDITION ->{
+ resultCode = onTaskerQueryCondition()
+ return
+ }
+
+ ACTION_TASKER_ACTION -> {
+ onTaskerAction(context, broadcast_intent)
+ return
+ }
+
// ダウンロードファイル数の通知やウィジェットのタップ
ACTION_NEW_FILE_NOTIFICATION_TAP -> {
NewFileWidget.clearDownloadCount(context)
@@ -123,4 +314,5 @@ class Receiver1 : BroadcastReceiver() {
log.trace(ex, "onReceive failed.")
}
}
+
}
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 027daf8..29b3cab 100644
--- a/app/src/main/java/jp/juggler/fadownloader/tracker/NetworkTracker.kt
+++ b/app/src/main/java/jp/juggler/fadownloader/tracker/NetworkTracker.kt
@@ -103,7 +103,7 @@ class NetworkTracker(
get() = lastOtherActive.get()
private val isDisposed : Boolean
- get() = worker != null
+ get() = worker == null
private val wifiManager =
context.applicationContext.getSystemService(Context.WIFI_SERVICE)
@@ -133,6 +133,7 @@ class NetworkTracker(
if(isDisposed) return
this.setting = setting
+ logStatic.d("updateSetting: targetType=${setting.target_type}")
timeLastTargetDetected = 0L
timeLastSpray = 0L
@@ -463,6 +464,7 @@ class NetworkTracker(
synchronized(testerMap) {
var tester = testerMap[checkUrl]
if(tester?.isAlive == true) return
+ // log.v("${checkUrl}の確認を開始")
tester = NetworkTracker.UrlTester(setting, log, targetUrl, checkUrl, onUrlTestComplete)
testerMap[checkUrl] = tester
tester.start()
@@ -813,6 +815,7 @@ class NetworkTracker(
ns_list.afterAddAll()
lastOtherActive.set(ns_list.other_active)
+ logStatic.d("checkNetwork:targetType =${setting.target_type}")
// ターゲット種別により、テザリング用とAP用の処理に分かれる
return when(setting.target_type) {
@@ -868,7 +871,7 @@ class NetworkTracker(
while(! isCancelled) {
- val remain = try {
+ val remain = try {
checkNetwork()
} catch(ex : Throwable) {
log.trace(ex, "network check failed.")
@@ -893,7 +896,8 @@ class NetworkTracker(
}
}
- waitEx(if(remain <= 0L) 5000L else remain)
+ logStatic.d("run: remain=$remain")
+ waitEx(if(remain <= 0L) 5000L else if(remain > 10000L )10000L else remain)
}
}
}
diff --git a/app/src/main/java/net/dinglisch/android/tasker/TaskerPlugin.java b/app/src/main/java/net/dinglisch/android/tasker/TaskerPlugin.java
new file mode 100644
index 0000000..c1989ef
--- /dev/null
+++ b/app/src/main/java/net/dinglisch/android/tasker/TaskerPlugin.java
@@ -0,0 +1,1041 @@
+//package com.yourcompany.yourcondition;
+//package com.yourcompany.yoursetting;
+package net.dinglisch.android.tasker;
+
+// Constants and functions for Tasker *extensions* to the plugin protocol
+// See Also: http://tasker.dinglisch.net/plugins.html
+
+// Release Notes
+
+// v1.1 20140202
+// added function variableNameValid()
+// fixed some javadoc entries (thanks to David Stone)
+
+// v1.2 20140211
+// added ACTION_EDIT_EVENT
+
+// v1.3 20140227
+// added REQUESTED_TIMEOUT_MS_NONE, REQUESTED_TIMEOUT_MS_MAX and REQUESTED_TIMEOUT_MS_NEVER
+// requestTimeoutMS(): added range check
+
+// v1.4 20140516
+// support for data pass through in REQUEST_QUERY intent
+// some javadoc entries fixed (thanks again David :-))
+
+// v1.5 20141120
+// added RESULT_CODE_FAILED_PLUGIN_FIRST
+// added Setting.VARNAME_ERROR_MESSAGE
+
+// v1.6 20150213
+// added Setting.getHintTimeoutMS()
+// added Host.addHintTimeoutMS()
+
+// v1.7 20160619
+// null check for getCallingActivity() in hostSupportsOnFireVariableReplacement( Activity editActivity )
+
+// v1.8 20161002
+// added hostSupportsKeyEncoding(), setKeyEncoding() and Host.getKeysWithEncoding()
+
+import java.net.URISyntaxException;
+import java.security.SecureRandom;
+import java.util.regex.Pattern;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+public class TaskerPlugin {
+
+ private final static String TAG = "TaskerPlugin";
+
+ private final static String BASE_KEY = "net.dinglisch.android.tasker";
+
+ private final static String EXTRAS_PREFIX = BASE_KEY + ".extras.";
+
+ private final static int FIRST_ON_FIRE_VARIABLES_TASKER_VERSION = 80;
+
+ public final static String VARIABLE_PREFIX = "%";
+
+ // when generating non-repeating integers, look this far back for repeats
+ // see getPositiveNonRepeatingRandomInteger()
+ private final static int RANDOM_HISTORY_SIZE = 100;
+
+ /**
+ * Action that the EditActivity for an event plugin should be launched by
+ */
+ public final static String ACTION_EDIT_EVENT = BASE_KEY + ".ACTION_EDIT_EVENT";
+
+ private final static String VARIABLE_NAME_START_EXPRESSION = "[\\w&&[^_]]";
+ private final static String VARIABLE_NAME_MID_EXPRESSION = "[\\w0-9]+";
+ private final static String VARIABLE_NAME_END_EXPRESSION = "[\\w0-9&&[^_]]";
+
+ public final static String VARIABLE_NAME_MAIN_PART_MATCH_EXPRESSION =
+ VARIABLE_NAME_START_EXPRESSION + VARIABLE_NAME_MID_EXPRESSION + VARIABLE_NAME_END_EXPRESSION
+ ;
+
+ public final static String VARIABLE_NAME_MATCH_EXPRESSION =
+ VARIABLE_PREFIX + "+" +
+ VARIABLE_NAME_MAIN_PART_MATCH_EXPRESSION
+ ;
+
+ private static Pattern VARIABLE_NAME_MATCH_PATTERN = null;
+
+ /**
+ * @see #addVariableBundle(Bundle, Bundle)
+ * @see Host#getVariablesBundle(Bundle)
+ */
+ private final static String EXTRA_VARIABLES_BUNDLE = EXTRAS_PREFIX + "VARIABLES";
+
+ /**
+ * Host capabilities, passed to plugin with edit intents
+ */
+ private final static String EXTRA_HOST_CAPABILITIES = EXTRAS_PREFIX + "HOST_CAPABILITIES";
+
+ /**
+ * @see Setting#hostSupportsVariableReturn(Bundle)
+ */
+ public final static int EXTRA_HOST_CAPABILITY_SETTING_RETURN_VARIABLES = 2;
+
+ /**
+ * @see Condition#hostSupportsVariableReturn(Bundle)
+ */
+ public final static int EXTRA_HOST_CAPABILITY_CONDITION_RETURN_VARIABLES = 4;
+
+ /**
+ * @see Setting#hostSupportsOnFireVariableReplacement(Bundle)
+ */
+ public final static int EXTRA_HOST_CAPABILITY_SETTING_FIRE_VARIABLE_REPLACEMENT = 8;
+
+ /**
+ * @see Setting#hostSupportsVariableReturn(Bundle)
+ */
+ private final static int EXTRA_HOST_CAPABILITY_RELEVANT_VARIABLES = 16;
+
+ public final static int EXTRA_HOST_CAPABILITY_SETTING_SYNCHRONOUS_EXECUTION = 32;
+
+ public final static int EXTRA_HOST_CAPABILITY_REQUEST_QUERY_DATA_PASS_THROUGH = 64;
+
+ public final static int EXTRA_HOST_CAPABILITY_ENCODING_JSON = 128;
+
+ public final static int EXTRA_HOST_CAPABILITY_ALL =
+ EXTRA_HOST_CAPABILITY_SETTING_RETURN_VARIABLES |
+ EXTRA_HOST_CAPABILITY_CONDITION_RETURN_VARIABLES |
+ EXTRA_HOST_CAPABILITY_SETTING_FIRE_VARIABLE_REPLACEMENT |
+ EXTRA_HOST_CAPABILITY_RELEVANT_VARIABLES|
+ EXTRA_HOST_CAPABILITY_SETTING_SYNCHRONOUS_EXECUTION |
+ EXTRA_HOST_CAPABILITY_REQUEST_QUERY_DATA_PASS_THROUGH |
+ EXTRA_HOST_CAPABILITY_ENCODING_JSON
+ ;
+
+ /**
+ * Possible encodings of text in bundle values
+ *
+ * @see #setKeyEncoding(Bundle,String[],Encoding)
+ */
+ public enum Encoding { JSON };
+
+ private final static String BUNDLE_KEY_ENCODING_JSON_KEYS = BASE_KEY + ".JSON_ENCODED_KEYS";
+
+ public static boolean hostSupportsKeyEncoding( Bundle extrasFromHost, Encoding encoding ) {
+ switch ( encoding ) {
+ case JSON:
+ return hostSupports( extrasFromHost, EXTRA_HOST_CAPABILITY_ENCODING_JSON );
+ default:
+ return false;
+ }
+ }
+
+ /**
+ *
+ * Miscellaneous operational hints going one way or the other
+ * @see Setting#hostSupportsVariableReturn(Bundle)
+ */
+
+ private final static String EXTRA_HINTS_BUNDLE = EXTRAS_PREFIX + "HINTS";
+
+ private final static String BUNDLE_KEY_HINT_PREFIX = ".hints.";
+
+ private final static String BUNDLE_KEY_HINT_TIMEOUT_MS = BUNDLE_KEY_HINT_PREFIX + "TIMEOUT";
+
+ /**
+ *
+ * @see #hostSupportsRelevantVariables(Bundle)
+ * @see #addRelevantVariableList(Intent, String[])
+ * @see #getRelevantVariableList(Bundle)
+ */
+ private final static String BUNDLE_KEY_RELEVANT_VARIABLES = BASE_KEY + ".RELEVANT_VARIABLES";
+
+
+ public static boolean hostSupportsRelevantVariables( Bundle extrasFromHost ) {
+ return hostSupports( extrasFromHost, EXTRA_HOST_CAPABILITY_RELEVANT_VARIABLES );
+ }
+
+ /**
+ * Specifies to host which variables might be used by the plugin.
+ *
+ * Used in EditActivity, before setResult().
+ *
+ * @param intentToHost the intent being returned to the host
+ * @param variableNames array of relevant variable names
+ */
+ public static void addRelevantVariableList( Intent intentToHost, String [] variableNames ) {
+ intentToHost.putExtra( BUNDLE_KEY_RELEVANT_VARIABLES, variableNames );
+ }
+
+ /**
+ * Validate a variable name.
+ *
+ * The basic requirement for variables from a plugin is that they must be all lower-case.
+ *
+ * @param varName name to check
+ */
+ public static boolean variableNameValid( String varName ) {
+
+ boolean validFlag = false;
+
+ if ( varName == null )
+ Log.d( TAG, "variableNameValid: null name" );
+ else {
+ if ( VARIABLE_NAME_MATCH_PATTERN == null )
+ VARIABLE_NAME_MATCH_PATTERN = Pattern.compile( VARIABLE_NAME_MATCH_EXPRESSION, 0 );
+
+ if ( VARIABLE_NAME_MATCH_PATTERN.matcher( varName ).matches() ) {
+
+ if ( variableNameIsLocal( varName ) )
+ validFlag = true;
+ else
+ Log.d( TAG, "variableNameValid: name not local: " + varName );
+ }
+ else
+ Log.d( TAG, "variableNameValid: invalid name: " + varName );
+ }
+
+ return validFlag;
+ }
+
+ /**
+ * Allows the plugin/host to indicate to each other a set of variables which they are referencing.
+ * The host may use this to e.g. show a variable selection list in it's UI.
+ * The host should use this if it previously indicated to the plugin that it supports relevant vars
+ *
+ * @param fromHostIntentExtras usually from getIntent().getExtras()
+ * @return variableNames an array of relevant variable names
+ */
+ public static String [] getRelevantVariableList( Bundle fromHostIntentExtras ) {
+
+ String [] relevantVars = (String []) getBundleValueSafe( fromHostIntentExtras, BUNDLE_KEY_RELEVANT_VARIABLES, String [].class, "getRelevantVariableList" );
+
+ if ( relevantVars == null )
+ relevantVars = new String [0];
+
+ return relevantVars;
+ }
+
+ /**
+ * Used by: plugin QueryReceiver, FireReceiver
+ *
+ * Add a bundle of variable name/value pairs.
+ *
+ * Names must be valid Tasker local variable names.
+ * Values must be String, String [] or ArrayList
+ * Null values cause deletion of possible already-existing variables
+ * A null value where the variable does not already exist results in attempted deletion
+ * of any existing array indices (%arr1, %arr2 etc)
+ *
+ * @param resultExtras the result extras from the receiver onReceive (from a call to getResultExtras())
+ * @param variables the variables to send
+ * @see Setting#hostSupportsVariableReturn(Bundle)
+ * @see #variableNameValid(String)
+ */
+ public static void addVariableBundle( Bundle resultExtras, Bundle variables ) {
+ resultExtras.putBundle( EXTRA_VARIABLES_BUNDLE, variables );
+ }
+
+ /**
+ * Used by: plugin EditActivity
+ *
+ * Specify the encoding for a set of bundle keys.
+ *
+ * This is completely optional and currently only necessary if using Setting#setVariableReplaceKeys
+ * where the corresponding values of some of the keys specified are JSON encoded.
+ *
+ * @param resultBundleToHost the bundle being returned to the host
+ * @param keys the keys being returned to the host which are encoded in some way
+ * @param encoding the encoding of the values corresponding to the specified keys
+ * @see #setVariableReplaceKeys(Bundle,String[])
+ * @see #hostSupportsKeyEncoding(Bundle, Encoding)
+ */
+ public static void setKeyEncoding( Bundle resultBundleToHost, String [] keys, Encoding encoding ) {
+ if ( Encoding.JSON.equals( encoding ) )
+ addStringArrayToBundleAsString(
+ keys, resultBundleToHost, BUNDLE_KEY_ENCODING_JSON_KEYS, "setValueEncoding"
+ );
+ else
+ Log.e( TAG, "unknown encoding: " + encoding );
+ }
+
+ // ----------------------------- SETTING PLUGIN ONLY --------------------------------- //
+
+ public static class Setting {
+
+ /**
+ * Variable name into which a description of any error that occurred can be placed
+ * for the user to process.
+ *
+ * Should *only* be set when the BroadcastReceiver result code indicates a failure.
+ *
+ * Note that the user needs to have configured the task to continue after failure of the plugin
+ * action otherwise they will not be able to make use of the error message.
+ *
+ * For use with #addRelevantVariableList(Intent, String[]) and #addVariableBundle(Bundle, Bundle)
+ *
+ */
+ public final static String VARNAME_ERROR_MESSAGE = VARIABLE_PREFIX + "errmsg";
+
+ /**
+ * @see #setVariableReplaceKeys(Bundle, String[])
+ */
+ private final static String BUNDLE_KEY_VARIABLE_REPLACE_STRINGS = EXTRAS_PREFIX + "VARIABLE_REPLACE_KEYS";
+
+ /**
+ * @see #requestTimeoutMS(android.content.Intent, int)
+ */
+ private final static String EXTRA_REQUESTED_TIMEOUT = EXTRAS_PREFIX + "REQUESTED_TIMEOUT";
+
+ /**
+ * @see #requestTimeoutMS(android.content.Intent, int)
+ */
+
+ public final static int REQUESTED_TIMEOUT_MS_NONE = 0;
+
+ /**
+ * @see #requestTimeoutMS(android.content.Intent, int)
+ */
+
+ public final static int REQUESTED_TIMEOUT_MS_MAX = 3599000;
+
+ /**
+ * @see #requestTimeoutMS(android.content.Intent, int)
+ */
+
+ public final static int REQUESTED_TIMEOUT_MS_NEVER = REQUESTED_TIMEOUT_MS_MAX + 1000;
+
+ /**
+ * @see #signalFinish(Context, Intent, int, Bundle)
+ * @see #addCompletionIntent(Intent, Intent,ComponentName, boolean)
+ */
+ private final static String EXTRA_PLUGIN_COMPLETION_INTENT = EXTRAS_PREFIX + "COMPLETION_INTENT";
+
+ /**
+ * @see #signalFinish(Context, Intent, int, Bundle)
+ * @see Host#getSettingResultCode(Intent)
+ */
+ public final static String EXTRA_RESULT_CODE = EXTRAS_PREFIX + "RESULT_CODE";
+
+ /**
+ *
+ * @see #signalFinish(Context, Intent, int, Bundle)
+ * @see #addCompletionIntent(Intent, Intent,ComponentName, boolean)
+ */
+ public final static String EXTRA_CALL_SERVICE_PACKAGE = BASE_KEY + ".EXTRA_CALL_SERVICE_PACKAGE";
+ public final static String EXTRA_CALL_SERVICE = BASE_KEY + ".EXTRA_CALL_SERVICE";
+ public final static String EXTRA_CALL_SERVICE_FOREGROUND = BASE_KEY + ".EXTRA_CALL_SERVICE_FOREGROUND";
+ /**
+ * @see #signalFinish(Context, Intent, int, Bundle)
+ * @see Host#getSettingResultCode(Intent)
+ */
+
+ public final static int RESULT_CODE_OK = Activity.RESULT_OK;
+ public final static int RESULT_CODE_OK_MINOR_FAILURES = Activity.RESULT_FIRST_USER;
+ public final static int RESULT_CODE_FAILED = Activity.RESULT_FIRST_USER + 1;
+ public final static int RESULT_CODE_PENDING = Activity.RESULT_FIRST_USER + 2;
+ public final static int RESULT_CODE_UNKNOWN = Activity.RESULT_FIRST_USER + 3;
+
+ /**
+ * If a plugin wants to define it's own error codes, start numbering them here.
+ * The code will be placed in an error variable (%err in the case of Tasker) for
+ * the user to process after the plugin action.
+ */
+
+ public final static int RESULT_CODE_FAILED_PLUGIN_FIRST = Activity.RESULT_FIRST_USER + 9;
+
+ /**
+ * Used by: plugin EditActivity.
+ *
+ * Indicates to plugin that host will replace variables in specified bundle keys.
+ *
+ * Replacement takes place every time the setting is fired, before the bundle is
+ * passed to the plugin FireReceiver.
+ *
+ * @param extrasFromHost intent extras from the intent received by the edit activity
+ * @see #setVariableReplaceKeys(Bundle, String[])
+ */
+ public static boolean hostSupportsOnFireVariableReplacement( Bundle extrasFromHost ) {
+ return hostSupports( extrasFromHost, EXTRA_HOST_CAPABILITY_SETTING_FIRE_VARIABLE_REPLACEMENT );
+ }
+
+ /**
+ * Used by: plugin EditActivity.
+ *
+ * Description as above.
+ *
+ * This version also includes backwards compatibility with pre 4.2 Tasker versions.
+ * At some point this function will be deprecated.
+ *
+ * @param editActivity the plugin edit activity, needed to test calling Tasker version
+ * @see #setVariableReplaceKeys(Bundle, String[])
+ */
+
+ public static boolean hostSupportsOnFireVariableReplacement( Activity editActivity ) {
+
+ boolean supportedFlag = hostSupportsOnFireVariableReplacement( editActivity.getIntent().getExtras() );
+
+ if ( ! supportedFlag ) {
+
+ ComponentName callingActivity = editActivity.getCallingActivity();
+
+ if ( callingActivity == null )
+ Log.w( TAG, "hostSupportsOnFireVariableReplacement: null callingActivity, defaulting to false" );
+ else {
+ String callerPackage = callingActivity.getPackageName();
+
+ // Tasker only supporteed this from 1.0.10
+ supportedFlag =
+ ( callerPackage.startsWith( BASE_KEY ) ) &&
+ ( getPackageVersionCode( editActivity.getPackageManager(), callerPackage ) > FIRST_ON_FIRE_VARIABLES_TASKER_VERSION )
+ ;
+ }
+ }
+
+ return supportedFlag;
+ }
+
+ public static boolean hostSupportsSynchronousExecution( Bundle extrasFromHost ) {
+ return hostSupports( extrasFromHost, EXTRA_HOST_CAPABILITY_SETTING_SYNCHRONOUS_EXECUTION );
+ }
+
+ /**
+ * Request the host to wait the specified number of milliseconds before continuing.
+ * Note that the host may choose to ignore the request.
+ *
+ * Maximum value is REQUESTED_TIMEOUT_MS_MAX.
+ * Also available are REQUESTED_TIMEOUT_MS_NONE (continue immediately without waiting
+ * for the plugin to finish) and REQUESTED_TIMEOUT_MS_NEVER (wait forever for
+ * a result).
+ *
+ * Used in EditActivity, before setResult().
+ *
+ * @param intentToHost the intent being returned to the host
+ * @param timeoutMS
+ */
+ public static void requestTimeoutMS( Intent intentToHost, int timeoutMS ) {
+ if ( timeoutMS < 0 )
+ Log.w( TAG, "requestTimeoutMS: ignoring negative timeout (" + timeoutMS + ")" );
+ else {
+ if (
+ ( timeoutMS > REQUESTED_TIMEOUT_MS_MAX ) &&
+ ( timeoutMS != REQUESTED_TIMEOUT_MS_NEVER )
+ ) {
+ Log.w( TAG, "requestTimeoutMS: requested timeout " + timeoutMS + " exceeds maximum, setting to max (" + REQUESTED_TIMEOUT_MS_MAX + ")" );
+ timeoutMS = REQUESTED_TIMEOUT_MS_MAX;
+ }
+ intentToHost.putExtra( EXTRA_REQUESTED_TIMEOUT, timeoutMS );
+ }
+ }
+
+ /**
+ * Used by: plugin EditActivity
+ *
+ * Indicates to host which bundle keys should be replaced.
+ *
+ * @param resultBundleToHost the bundle being returned to the host
+ * @param listOfKeyNames which bundle keys to replace variables in when setting fires
+ * @see #hostSupportsOnFireVariableReplacement(Bundle)
+ * @see #setKeyEncoding(Bundle,String[],Encoding)
+ */
+ public static void setVariableReplaceKeys( Bundle resultBundleToHost, String [] listOfKeyNames ) {
+ addStringArrayToBundleAsString(
+ listOfKeyNames, resultBundleToHost, BUNDLE_KEY_VARIABLE_REPLACE_STRINGS,
+ "setVariableReplaceKeys"
+ );
+ }
+
+ /**
+ * Used by: plugin FireReceiver
+ *
+ * Indicates to plugin whether the host will process variables which it passes back
+ *
+ * @param extrasFromHost intent extras from the intent received by the FireReceiver
+ * @see #signalFinish(Context, Intent, int, Bundle)
+ */
+ public static boolean hostSupportsVariableReturn( Bundle extrasFromHost ) {
+ return hostSupports( extrasFromHost, EXTRA_HOST_CAPABILITY_SETTING_RETURN_VARIABLES );
+ }
+
+ /**
+ * Used by: plugin FireReceiver
+ *
+ * Tell the host that the plugin has finished execution.
+ *
+ * This should only be used if RESULT_CODE_PENDING was returned by FireReceiver.onReceive().
+ *
+ * @param originalFireIntent the intent received from the host (via onReceive())
+ * @param resultCode level of success in performing the settings
+ * @param vars any variables that the plugin wants to set in the host
+ * @see #hostSupportsSynchronousExecution(Bundle)
+ */
+ public static boolean signalFinish( Context context, Intent originalFireIntent, int resultCode, Bundle vars ) {
+
+ String errorPrefix = "signalFinish: ";
+
+ boolean okFlag = false;
+
+ String completionIntentString = (String) getExtraValueSafe( originalFireIntent, Setting.EXTRA_PLUGIN_COMPLETION_INTENT, String.class, "signalFinish" );
+
+ if ( completionIntentString != null ) {
+
+ Uri completionIntentUri = null;
+ try {
+ completionIntentUri = Uri.parse( completionIntentString );
+ }
+ // should only throw NullPointer but don't particularly trust it
+ catch ( Exception e ) {
+ Log.w( TAG, errorPrefix + "couldn't parse " + completionIntentString );
+ }
+
+ if ( completionIntentUri != null ) {
+ try {
+ Intent completionIntent = Intent.parseUri( completionIntentString, Intent.URI_INTENT_SCHEME );
+
+ completionIntent.putExtra( EXTRA_RESULT_CODE, resultCode );
+
+ if ( vars != null )
+ completionIntent.putExtra( EXTRA_VARIABLES_BUNDLE, vars );
+
+ String callServicePackage = (String) getExtraValueSafe(completionIntent, Setting.EXTRA_CALL_SERVICE_PACKAGE, String.class, "signalFinish");
+ String callService = (String) getExtraValueSafe(completionIntent, Setting.EXTRA_CALL_SERVICE, String.class, "signalFinish");
+ Boolean foreground = (Boolean) getExtraValueSafe(completionIntent, Setting.EXTRA_CALL_SERVICE_FOREGROUND, Boolean.class, "signalFinish");
+ if (callServicePackage != null && callService != null && foreground != null) {
+ completionIntent.setComponent(new ComponentName(callServicePackage, callService));
+ if (foreground && android.os.Build.VERSION.SDK_INT >= 26) {
+ context.startForegroundService(completionIntent);
+ } else {
+ context.startService(completionIntent);
+ }
+ } else {
+ context.sendBroadcast(completionIntent);
+ }
+
+ okFlag = true;
+ }
+ catch ( URISyntaxException e ) {
+ Log.w( TAG, errorPrefix + "bad URI: " + completionIntentUri );
+ }
+ }
+ }
+
+ return okFlag;
+ }
+
+ /**
+ * Check for a hint on the timeout value the host is using.
+ * Used by: plugin FireReceiver.
+ * Requires Tasker 4.7+
+ *
+ * @param extrasFromHost intent extras from the intent received by the FireReceiver
+ * @return timeoutMS the hosts timeout setting for the action or -1 if no hint is available.
+ *
+ * @see #REQUESTED_TIMEOUT_MS_NONE, REQUESTED_TIMEOUT_MS_MAX, REQUESTED_TIMEOUT_MS_NEVER
+ */
+ public static int getHintTimeoutMS( Bundle extrasFromHost ) {
+
+ int timeoutMS = -1;
+
+ Bundle hintsBundle = (Bundle) TaskerPlugin.getBundleValueSafe( extrasFromHost, EXTRA_HINTS_BUNDLE, Bundle.class, "getHintTimeoutMS" );
+
+ if ( hintsBundle != null ) {
+
+ Integer val = (Integer) getBundleValueSafe( hintsBundle, BUNDLE_KEY_HINT_TIMEOUT_MS, Integer.class, "getHintTimeoutMS" );
+
+ if ( val != null )
+ timeoutMS = val;
+ }
+
+ return timeoutMS;
+ }
+ }
+
+ // ----------------------------- CONDITION/EVENT PLUGIN ONLY --------------------------------- //
+
+ public static class Condition {
+
+ /**
+ * @see #getResultReceiver(Intent)
+ */
+ public final static String EXTRA_RESULT_RECEIVER = BASE_KEY + ".EXTRA_RESULT_RECEIVER";
+ /**
+ * Used by: plugin QueryReceiver
+ *
+ * Indicates to plugin whether the host will process variables which it passes back
+ *
+ * @param extrasFromHost intent extras from the intent received by the QueryReceiver
+ * @see #addVariableBundle(Bundle, Bundle)
+ */
+ public static boolean hostSupportsVariableReturn( Bundle extrasFromHost ) {
+ return hostSupports( extrasFromHost, EXTRA_HOST_CAPABILITY_CONDITION_RETURN_VARIABLES );
+ }
+
+ public static ResultReceiver getResultReceiver(Intent intentFromHost) {
+ if (intentFromHost == null) {
+ return null;
+ }
+ return (ResultReceiver) getExtraValueSafe(intentFromHost, EXTRA_RESULT_RECEIVER, ResultReceiver.class, "getResultReceiver");
+
+ }
+ }
+
+ // ----------------------------- EVENT PLUGIN ONLY --------------------------------- //
+
+ public static class Event {
+
+ public final static String PASS_THROUGH_BUNDLE_MESSAGE_ID_KEY = BASE_KEY + ".MESSAGE_ID";
+
+ private final static String EXTRA_REQUEST_QUERY_PASS_THROUGH_DATA = EXTRAS_PREFIX + "PASS_THROUGH_DATA";
+
+ /**
+ * @param extrasFromHost intent extras from the intent received by the QueryReceiver
+ * @see #addPassThroughData(Intent, Bundle)
+ */
+ public static boolean hostSupportsRequestQueryDataPassThrough( Bundle extrasFromHost ) {
+ return hostSupports( extrasFromHost, EXTRA_HOST_CAPABILITY_REQUEST_QUERY_DATA_PASS_THROUGH );
+ }
+
+ /**
+ * Specify a bundle of data (probably representing whatever change happened in the condition)
+ * which will be included in the QUERY_CONDITION broadcast sent by the host for each
+ * event instance of the plugin.
+ *
+ * The minimal purpose is to enable the plugin to associate a QUERY_CONDITION to the
+ * with the REQUEST_QUERY that caused it.
+ *
+ * Note that for security reasons it is advisable to also store a message ID with the bundle
+ * which can be compared to known IDs on receipt. The host cannot validate the source of
+ * REQUEST_QUERY intents so fake data may be passed. Replay attacks are also possible.
+ * addPassThroughMesssageID() can be used to add an ID if the plugin doesn't wish to add it's
+ * own ID to the pass through bundle.
+ *
+ * Note also that there are several situations where REQUEST_QUERY will not result in a
+ * QUERY_CONDITION intent (e.g. event throttling by the host), so plugin-local data
+ * indexed with a message ID needs to be timestamped and eventually timed-out.
+ *
+ * This function can be called multiple times, each time all keys in data will be added to
+ * that of previous calls.
+ *
+ * @param requestQueryIntent intent being sent to the host
+ * @param data the data to be passed-through
+ * @see #hostSupportsRequestQueryDataPassThrough(Bundle)
+ * @see #retrievePassThroughData(Intent)
+ * @see #addPassThroughMessageID
+ *
+ */
+ public static void addPassThroughData( Intent requestQueryIntent, Bundle data ) {
+
+ Bundle passThroughBundle = retrieveOrCreatePassThroughBundle( requestQueryIntent );
+
+ passThroughBundle.putAll( data );
+ }
+
+ /**
+ * Retrieve the pass through data from a QUERY_REQUEST from the host which was generated
+ * by a REQUEST_QUERY from the plugin.
+ *
+ * Note that if addPassThroughMessageID() was previously called, the data will contain an extra
+ * key TaskerPlugin.Event.PASS_THOUGH_BUNDLE_MESSAGE_ID_KEY.
+ *
+ * @param queryConditionIntent QUERY_REQUEST sent from host
+ * @return data previously added to the REQUEST_QUERY intent
+ * @see #hostSupportsRequestQueryDataPassThrough(Bundle)
+ * @see #addPassThroughData(Intent,Bundle)
+ */
+ public static Bundle retrievePassThroughData( Intent queryConditionIntent ) {
+ return (Bundle) getExtraValueSafe(
+ queryConditionIntent,
+ EXTRA_REQUEST_QUERY_PASS_THROUGH_DATA,
+ Bundle.class,
+ "retrievePassThroughData"
+ );
+ }
+
+ /**
+ * Add a message ID to a REQUEST_QUERY intent which will then be included in the corresponding
+ * QUERY_CONDITION broadcast sent by the host for each event instance of the plugin.
+ *
+ * The minimal purpose is to enable the plugin to associate a QUERY_CONDITION to the
+ * with the REQUEST_QUERY that caused it. It also allows the message to be verified
+ * by the plugin to prevent e.g. replay attacks
+ *
+ * @param requestQueryIntent intent being sent to the host
+ * @return a guaranteed non-repeating within 100 calls message ID
+ * @see #hostSupportsRequestQueryDataPassThrough(Bundle)
+ * @see #retrievePassThroughData(Intent)
+ * @return an ID for the bundle so it can be identified and the caller verified when it is again received by the plugin
+ *
+ */
+ public static int addPassThroughMessageID( Intent requestQueryIntent ) {
+
+ Bundle passThroughBundle = retrieveOrCreatePassThroughBundle( requestQueryIntent );
+
+ int id = getPositiveNonRepeatingRandomInteger();
+
+ passThroughBundle.putInt( PASS_THROUGH_BUNDLE_MESSAGE_ID_KEY, id );
+
+ return id;
+ }
+
+ /*
+ * Retrieve the pass through data from a QUERY_REQUEST from the host which was generated
+ * by a REQUEST_QUERY from the plugin.
+ *
+ * @param queryConditionIntent QUERY_REQUEST sent from host
+ * @return the ID which was passed through by the host, or -1 if no ID was found
+ * @see #hostSupportsRequestQueryDataPassThrough(Bundle)
+ * @see #addPassThroughData(Intent,Bundle)
+ */
+ public static int retrievePassThroughMessageID( Intent queryConditionIntent ) {
+
+ int toReturn = -1;
+
+ Bundle passThroughData = Event.retrievePassThroughData( queryConditionIntent );
+
+ if ( passThroughData != null ) {
+ Integer id = (Integer) getBundleValueSafe(
+ passThroughData,
+ PASS_THROUGH_BUNDLE_MESSAGE_ID_KEY,
+ Integer.class,
+ "retrievePassThroughMessageID"
+ );
+
+ if ( id != null )
+ toReturn = id;
+ }
+
+ return toReturn;
+ }
+
+ // internal use
+ private static Bundle retrieveOrCreatePassThroughBundle( Intent requestQueryIntent ) {
+
+ Bundle passThroughBundle;
+
+ if ( requestQueryIntent.hasExtra( EXTRA_REQUEST_QUERY_PASS_THROUGH_DATA ) )
+ passThroughBundle = requestQueryIntent.getBundleExtra( EXTRA_REQUEST_QUERY_PASS_THROUGH_DATA );
+ else {
+ passThroughBundle = new Bundle();
+ requestQueryIntent.putExtra( EXTRA_REQUEST_QUERY_PASS_THROUGH_DATA, passThroughBundle );
+ }
+
+ return passThroughBundle;
+ }
+ }
+ // ---------------------------------- HOST ----------------------------------------- //
+
+ public static class Host {
+
+ /**
+ * Tell the plugin what capabilities the host support. This should be called when sending
+ * intents to any EditActivity, FireReceiver or QueryReceiver.
+ *
+ * @param toPlugin the intent we're sending
+ * @return capabilities one or more of the EXTRA_HOST_CAPABILITY_XXX flags
+ */
+ public static Intent addCapabilities( Intent toPlugin, int capabilities ) {
+ return toPlugin.putExtra( EXTRA_HOST_CAPABILITIES, capabilities );
+ }
+
+ /**
+ * Add an intent to the fire intent before it goes to the plugin FireReceiver, which the plugin
+ * can use to signal when it is finished. Only use if @code{pluginWantsSychronousExecution} is true.
+ *
+ * @param fireIntent fire intent going to the plugin
+ * @param completionIntent intent which will signal the host that the plugin is finished.
+ * Implementation is host-dependent.
+ */
+ public static void addCompletionIntent(Intent fireIntent, Intent completionIntent, ComponentName callService, boolean foreground) {
+ if (callService != null) {
+ completionIntent.putExtra(Setting.EXTRA_CALL_SERVICE_PACKAGE, callService.getPackageName());
+ completionIntent.putExtra(Setting.EXTRA_CALL_SERVICE, callService.getClassName());
+ completionIntent.putExtra(Setting.EXTRA_CALL_SERVICE_FOREGROUND, foreground);
+ }
+ fireIntent.putExtra(
+ Setting.EXTRA_PLUGIN_COMPLETION_INTENT,
+ completionIntent.toUri(Intent.URI_INTENT_SCHEME)
+ );
+ }
+
+ /**
+ * When a setting plugin is finished, it sends the host the intent which was passed to it
+ * via @code{addCompletionIntent}.
+ *
+ * @param completionIntent intent returned from the plugin when it finished.
+ * @return resultCode measure of plugin success, defaults to UNKNOWN
+ */
+ public static int getSettingResultCode( Intent completionIntent ) {
+
+ Integer val = (Integer) getExtraValueSafe( completionIntent, Setting.EXTRA_RESULT_CODE, Integer.class, "getSettingResultCode" );
+
+ return ( val == null ) ? Setting.RESULT_CODE_UNKNOWN : val;
+ }
+
+ /**
+ * Extract a bundle of variables from an intent received from the FireReceiver. This
+ * should be called if the host previously indicated to the plugin
+ * that it supports setting variable return.
+ *
+ * @param resultExtras getResultExtras() from BroadcastReceiver:onReceive()
+ * @return variables a bundle of variable name/value pairs
+ * @see #addCapabilities(Intent, int)
+ */
+
+ public static Bundle getVariablesBundle( Bundle resultExtras ) {
+ return (Bundle) getBundleValueSafe(
+ resultExtras, EXTRA_VARIABLES_BUNDLE, Bundle.class, "getVariablesBundle"
+ );
+ }
+
+ /**
+ * Inform a setting plugin of the timeout value the host is using.
+ *
+ * @param toPlugin the intent we're sending
+ * @param timeoutMS the hosts timeout setting for the action. Note that this may differ from
+ * that which the plugin requests.
+ * @see #REQUESTED_TIMEOUT_MS_NONE, REQUESTED_TIMEOUT_MS_MAX, REQUESTED_TIMEOUT_MS_NEVER
+ */
+ public static void addHintTimeoutMS( Intent toPlugin, int timeoutMS ) {
+ getHintsBundle( toPlugin, "addHintTimeoutMS" ).putInt( BUNDLE_KEY_HINT_TIMEOUT_MS, timeoutMS );
+ }
+
+ private static Bundle getHintsBundle( Intent intent, String funcName ) {
+
+ Bundle hintsBundle = (Bundle) getExtraValueSafe( intent, EXTRA_HINTS_BUNDLE, Bundle.class, funcName );
+
+ if ( hintsBundle == null ) {
+ hintsBundle = new Bundle();
+ intent.putExtra( EXTRA_HINTS_BUNDLE, hintsBundle );
+ }
+
+ return hintsBundle;
+ }
+
+ public static boolean haveRequestedTimeout( Bundle extrasFromPluginEditActivity ) {
+ return extrasFromPluginEditActivity.containsKey( Setting.EXTRA_REQUESTED_TIMEOUT );
+ }
+
+ public static int getRequestedTimeoutMS( Bundle extrasFromPluginEditActivity ) {
+ return
+ (Integer) getBundleValueSafe(
+ extrasFromPluginEditActivity, Setting.EXTRA_REQUESTED_TIMEOUT, Integer.class, "getRequestedTimeout"
+ )
+ ;
+ }
+
+ public static String [] getSettingVariableReplaceKeys( Bundle fromPluginEditActivity ) {
+ return getStringArrayFromBundleString(
+ fromPluginEditActivity, Setting.BUNDLE_KEY_VARIABLE_REPLACE_STRINGS,
+ "getSettingVariableReplaceKeys"
+ );
+ }
+
+ public static String [] getKeysWithEncoding( Bundle fromPluginEditActivity, Encoding encoding ) {
+
+ String [] toReturn = null;
+
+ if ( Encoding.JSON.equals( encoding ) )
+ toReturn = getStringArrayFromBundleString(
+ fromPluginEditActivity, TaskerPlugin.BUNDLE_KEY_ENCODING_JSON_KEYS,
+ "getKeyEncoding:JSON"
+ );
+ else
+ Log.w( TAG, "Host.getKeyEncoding: unknown encoding " + encoding );
+
+ return toReturn;
+ }
+
+ public static boolean haveRelevantVariables( Bundle b ) {
+ return b.containsKey( BUNDLE_KEY_RELEVANT_VARIABLES );
+ }
+
+ public static void cleanRelevantVariables( Bundle b ) {
+ b.remove( BUNDLE_KEY_RELEVANT_VARIABLES );
+ }
+
+ public static void cleanHints( Bundle extras ) {
+ extras.remove( TaskerPlugin.EXTRA_HINTS_BUNDLE );
+ }
+
+ public static void cleanRequestedTimeout( Bundle extras ) {
+ extras.remove( Setting.EXTRA_REQUESTED_TIMEOUT );
+ }
+
+ public static void cleanSettingReplaceVariables( Bundle b ) {
+ b.remove( Setting.BUNDLE_KEY_VARIABLE_REPLACE_STRINGS );
+ }
+ }
+
+ // ---------------------------------- HELPER FUNCTIONS -------------------------------- //
+
+ private static Object getBundleValueSafe( Bundle b, String key, Class> expectedClass, String funcName ) {
+ Object value = null;
+
+ if ( b != null ) {
+ if ( b.containsKey( key ) ) {
+ Object obj = b.get( key );
+ if ( obj == null )
+ Log.w( TAG, funcName + ": " + key + ": null value" );
+ else if ( obj.getClass() != expectedClass )
+ Log.w( TAG, funcName + ": " + key + ": expected " + expectedClass.getClass().getName() + ", got " + obj.getClass().getName() );
+ else
+ value = obj;
+ }
+ }
+ return value;
+ }
+
+ private static Object getExtraValueSafe( Intent i, String key, Class> expectedClass, String funcName ) {
+ return ( i.hasExtra( key ) ) ?
+ getBundleValueSafe( i.getExtras(), key, expectedClass, funcName ) :
+ null;
+ }
+
+ private static boolean hostSupports( Bundle extrasFromHost, int capabilityFlag ) {
+ Integer flags = (Integer) getBundleValueSafe( extrasFromHost, EXTRA_HOST_CAPABILITIES, Integer.class, "hostSupports" );
+ return
+ ( flags != null ) &&
+ ( ( flags & capabilityFlag ) > 0 )
+ ;
+ }
+
+ public static int getPackageVersionCode( PackageManager pm, String packageName ) {
+
+ int code = -1;
+
+ if ( pm != null ) {
+ try {
+ PackageInfo pi = pm.getPackageInfo( packageName, 0 );
+ if ( pi != null )
+ code = pi.versionCode;
+ }
+ catch ( Exception e ) {
+ Log.e( TAG, "getPackageVersionCode: exception getting package info" );
+ }
+ }
+
+ return code;
+ }
+
+ private static boolean variableNameIsLocal( String varName ) {
+
+ int digitCount = 0;
+ int length = varName.length();
+
+ for ( int x = 0; x < length; x++ ) {
+ char ch = varName.charAt( x );
+
+ if ( Character.isUpperCase( ch ) )
+ return false;
+ else if ( Character.isDigit( ch ) )
+ digitCount++;
+ }
+
+ if ( digitCount == ( varName.length() - 1 ) )
+ return false;
+
+ return true;
+ }
+
+ private static String [] getStringArrayFromBundleString( Bundle bundle, String key, String funcName ) {
+
+ String spec = (String) getBundleValueSafe( bundle, key, String.class, funcName );
+
+ String [] toReturn = null;
+
+ if ( spec != null )
+ toReturn = spec.split( " " );
+
+ return toReturn;
+ }
+
+ private static void addStringArrayToBundleAsString( String [] toAdd, Bundle bundle, String key, String callerName ) {
+
+ StringBuilder builder = new StringBuilder();
+
+ if ( toAdd != null ) {
+
+ for ( String keyName : toAdd ) {
+
+ if ( keyName.contains( " " ) )
+ Log.w( TAG, callerName + ": ignoring bad keyName containing space: " + keyName );
+ else {
+ if ( builder.length() > 0 )
+ builder.append( ' ' );
+
+ builder.append( keyName );
+ }
+
+ if ( builder.length() > 0 )
+ bundle.putString( key, builder.toString() );
+ }
+ }
+ }
+
+ // state tracking for random number sequence
+ private static int [] lastRandomsSeen = null;
+ private static int randomInsertPointer = 0;
+ private static SecureRandom sr = null;
+
+ /**
+ * Generate a sequence of secure random positive integers which is guaranteed not to repeat
+ * in the last 100 calls to this function.
+ *
+ * @return a random positive integer
+ */
+ public static int getPositiveNonRepeatingRandomInteger() {
+
+ // initialize on first call
+ if ( sr == null ) {
+ sr = new SecureRandom();
+ lastRandomsSeen = new int[RANDOM_HISTORY_SIZE];
+
+ for ( int x = 0; x < lastRandomsSeen.length; x++ )
+ lastRandomsSeen[x] = -1;
+ }
+
+ int toReturn;
+ do {
+ // pick a number
+ toReturn = sr.nextInt( Integer.MAX_VALUE );
+
+ // check we havn't see it recently
+ for ( int seen : lastRandomsSeen ) {
+ if ( seen == toReturn ) {
+ toReturn = -1;
+ break;
+ }
+ }
+ }
+ while ( toReturn == -1 );
+
+ // update history
+ lastRandomsSeen[randomInsertPointer] = toReturn;
+ randomInsertPointer = ( randomInsertPointer + 1 ) % lastRandomsSeen.length;
+
+ return toReturn;
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/act_tasker_setting_action.xml b/app/src/main/res/layout/act_tasker_setting_action.xml
new file mode 100644
index 0000000..70ddd97
--- /dev/null
+++ b/app/src/main/res/layout/act_tasker_setting_action.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/act_tasker_setting_condition.xml b/app/src/main/res/layout/act_tasker_setting_condition.xml
new file mode 100644
index 0000000..8c0466a
--- /dev/null
+++ b/app/src/main/res/layout/act_tasker_setting_condition.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 f722017..989ed9f 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -200,5 +200,11 @@
テザリングOFF時にサービス終了
ONにすると、テザリングを使う Taeget Type なのにテザリングが有効でない場合はFa Downloaderのサービスを停止します。
+ アクション
+ 保存
+ FADのアクション
+ FADの状態
+ パラメータはありません.
+ サービス起動中?
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index df49c58..8a9973e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -232,4 +232,11 @@ FADownloaderはテザリング内部のLANにUDPパケットをばらまいた
Stop when tethering off
if enabled, if target type requires tethering but it is off, this app stop the downloader service.
+ Action
+ Save
+ Action to FA Downloader
+ Condition of FA Downloader
+ There is no parameter.
+ Is alive Service?
+