Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support DateTime items as Alarm clock receiver #1707

Merged
merged 7 commits into from
Dec 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ class BackgroundTasksManager : BroadcastReceiver() {
info.itemName,
info.label,
info.value,
info.mappedValue,
BackoffPolicy.EXPONENTIAL,
info.showToast,
info.taskerIntent,
Expand Down Expand Up @@ -110,8 +109,7 @@ class BackgroundTasksManager : BroadcastReceiver() {
WORKER_TAG_PREFIX_TASKER + itemName,
itemName,
label,
state,
mappedState,
ItemUpdateWorker.ValueWithInfo(state, mappedState),
BackoffPolicy.EXPONENTIAL,
false,
intent.getStringExtra(TaskerPlugin.Setting.EXTRA_PLUGIN_COMPLETION_INTENT),
Expand All @@ -129,8 +127,7 @@ class BackgroundTasksManager : BroadcastReceiver() {
val tag: String,
val itemName: String,
val label: String?,
val value: String,
val mappedValue: String?,
val value: ItemUpdateWorker.ValueWithInfo,
val showToast: Boolean,
val taskerIntent: String?,
val asCommand: Boolean
Expand Down Expand Up @@ -179,7 +176,7 @@ class BackgroundTasksManager : BroadcastReceiver() {
"com.android.providers.calendar",
"com.android.calendar"
)
private val VALUE_GETTER_MAP = HashMap<String, (Context) -> String?>()
private val VALUE_GETTER_MAP = HashMap<String, (Context) -> ItemUpdateWorker.ValueWithInfo?>()

// need to keep a ref for this to avoid it being GC'ed
// (SharedPreferences only keeps a WeakReference)
Expand All @@ -202,8 +199,7 @@ class BackgroundTasksManager : BroadcastReceiver() {
WORKER_TAG_PREFIX_NFC + tag.item,
tag.item,
tag.label,
tag.state,
tag.mappedState,
ItemUpdateWorker.ValueWithInfo(tag.state, tag.mappedState),
BackoffPolicy.LINEAR,
showToast = true,
asCommand = true
Expand All @@ -218,8 +214,7 @@ class BackgroundTasksManager : BroadcastReceiver() {
WORKER_TAG_PREFIX_WIDGET + data.item,
data.item,
data.label,
data.state,
data.mappedState,
ItemUpdateWorker.ValueWithInfo(data.state, data.mappedState),
BackoffPolicy.LINEAR,
showToast = true,
asCommand = true
Expand All @@ -243,16 +238,15 @@ class BackgroundTasksManager : BroadcastReceiver() {
return
}

val getter = VALUE_GETTER_MAP[key] ?: return

val value = VALUE_GETTER_MAP[key]?.invoke(context) ?: return
val prefix = prefs.getString(Constants.PREFERENCE_SEND_DEVICE_INFO_PREFIX)

enqueueItemUpload(
context,
key,
prefix + setting.second,
null,
getter(context) ?: return,
null,
value,
BackoffPolicy.EXPONENTIAL,
showToast = false,
asCommand = true
Expand All @@ -264,8 +258,7 @@ class BackgroundTasksManager : BroadcastReceiver() {
tag: String,
itemName: String,
label: String?,
value: String,
mappedValue: String?,
value: ItemUpdateWorker.ValueWithInfo,
backoffPolicy: BackoffPolicy,
showToast: Boolean,
taskerIntent: String? = null,
Expand All @@ -274,13 +267,13 @@ class BackgroundTasksManager : BroadcastReceiver() {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val inputData = ItemUpdateWorker.buildData(itemName, label, value, showToast, taskerIntent, asCommand)
val workRequest = OneTimeWorkRequest.Builder(ItemUpdateWorker::class.java)
.setConstraints(constraints)
.setBackoffCriteria(backoffPolicy, WorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS)
.addTag(tag)
.addTag(WORKER_TAG_ITEM_UPLOADS)
.setInputData(
ItemUpdateWorker.buildData(itemName, label, value, mappedValue, showToast, taskerIntent, asCommand))
.setInputData(inputData)
.build()

val workManager = WorkManager.getInstance(context)
Expand Down Expand Up @@ -312,17 +305,18 @@ class BackgroundTasksManager : BroadcastReceiver() {
putBoolean(Constants.PREFERENCE_ALARM_CLOCK_LAST_VALUE_WAS_ZERO, time == "0" || time == null)
}

time
time?.let { ItemUpdateWorker.ValueWithInfo(it, type = ItemUpdateWorker.ValueType.Timestamp) }
}
}
VALUE_GETTER_MAP[Constants.PREFERENCE_PHONE_STATE] = { context ->
val manager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
when (manager.callState) {
val state = when (manager.callState) {
TelephonyManager.CALL_STATE_IDLE -> "IDLE"
TelephonyManager.CALL_STATE_RINGING -> "RINGING"
TelephonyManager.CALL_STATE_OFFHOOK -> "OFFHOOK"
else -> "UNDEF"
}
ItemUpdateWorker.ValueWithInfo(state)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
package org.openhab.habdroid.background

import android.content.Context
import android.os.Parcelable
import android.util.Log
import androidx.core.os.bundleOf
import androidx.work.Data
import androidx.work.Worker
import androidx.work.WorkerParameters
import kotlinx.android.parcel.Parcelize
import kotlinx.coroutines.runBlocking
import org.json.JSONException
import org.json.JSONObject
Expand All @@ -30,12 +32,16 @@ import org.openhab.habdroid.model.toItem
import org.openhab.habdroid.ui.TaskerItemPickerActivity
import org.openhab.habdroid.util.HttpClient
import org.openhab.habdroid.util.TaskerPlugin
import org.openhab.habdroid.util.orDefaultIfEmpty
import org.openhab.habdroid.util.showErrorToast
import org.openhab.habdroid.util.showToast
import org.xml.sax.InputSource
import org.xml.sax.SAXException
import java.io.IOException
import java.io.StringReader
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.parsers.ParserConfigurationException

Expand Down Expand Up @@ -71,13 +77,6 @@ class ItemUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont
}

val itemName = inputData.getString(INPUT_DATA_ITEM_NAME)!!
var value = inputData.getString(INPUT_DATA_VALUE)!!

var label = inputData.getString(INPUT_DATA_LABEL)
if (label.isNullOrEmpty()) label = itemName

var mappedValue = inputData.getString(INPUT_DATA_MAPPED_VALUE)
if (mappedValue.isNullOrEmpty()) mappedValue = value

return runBlocking {
try {
Expand All @@ -91,34 +90,64 @@ class ItemUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont
)
return@runBlocking Result.failure(buildOutputData(true, 500))
}
if (value == "TOGGLE") {
value = determineOppositeState(item)
mappedValue = value

val value = inputData.getValueWithInfo(INPUT_DATA_VALUE)!!
mueller-ma marked this conversation as resolved.
Show resolved Hide resolved
val valueToBeSent = mapValueAccordingToItemTypeAndValue(value, item)
Log.d(TAG, "Trying to update Item '$itemName' to value $valueToBeSent, was ${value.value}")
val actualMappedValue = if (value.value != valueToBeSent) {
valueToBeSent
} else {
value.mappedValue.orDefaultIfEmpty(value.value)
}

val result = if (inputData.getBoolean(INPUT_DATA_AS_COMMAND, false)) {
connection.httpClient
.post("rest/items/$itemName", value, "text/plain;charset=UTF-8")
.post("rest/items/$itemName", valueToBeSent, "text/plain;charset=UTF-8")
.asStatus()
} else {
connection.httpClient
.put("rest/items/$itemName/state", value, "text/plain;charset=UTF-8")
.put("rest/items/$itemName/state", valueToBeSent, "text/plain;charset=UTF-8")
.asStatus()
}
Log.d(TAG, "Item '$itemName' successfully updated to value $value")
Log.d(TAG, "Item '$itemName' successfully updated to value $valueToBeSent")
if (showToast) {
val label = inputData.getString(INPUT_DATA_LABEL).orDefaultIfEmpty(itemName)
applicationContext.showToast(
getItemUpdateSuccessMessage(applicationContext, label, value, mappedValue!!))
getItemUpdateSuccessMessage(applicationContext, label, value.value, actualMappedValue))
}
sendTaskerSignalIfNeeded(taskerIntent, true, result.statusCode, null)
Result.success(buildOutputData(true, result.statusCode))
} catch (e: HttpClient.HttpException) {
Log.e(TAG, "Error updating item '$itemName' to value $value. Got HTTP error ${e.statusCode}", e)
Log.e(TAG, "Error updating item '$itemName'. Got HTTP error ${e.statusCode}", e)
sendTaskerSignalIfNeeded(taskerIntent, true, e.statusCode, e.localizedMessage)
Result.failure(buildOutputData(true, e.statusCode))
}
}
}

private fun mapValueAccordingToItemTypeAndValue(value: ValueWithInfo, item: Item) = when {
value.value == "TOGGLE" && item.canBeToggled() -> determineOppositeState(item)
value.type == ValueType.Timestamp && item.isOfTypeOrGroupType(Item.Type.DateTime) -> convertToTimestamp(value)
else -> value.value
}

private fun determineOppositeState(item: Item): String {
return if (item.isOfTypeOrGroupType(Item.Type.Rollershutter) || item.isOfTypeOrGroupType(Item.Type.Dimmer)) {
// If shutter is (partially) closed, open it, else close it
if (item.state?.asNumber?.value == 0F) "100" else "0"
} else if (item.state?.asBoolean == true) {
"OFF"
} else {
"ON"
}
}

private fun convertToTimestamp(value: ValueWithInfo): String {
val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss+0000", Locale.US)
formatter.timeZone = TimeZone.getTimeZone("UTC")
return formatter.format(value.value.toLong())
}

private fun sendTaskerSignalIfNeeded(
taskerIntent: String?,
hadConnection: Boolean,
Expand Down Expand Up @@ -178,25 +207,13 @@ class ItemUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont
}
}

private fun determineOppositeState(item: Item): String {
return if (item.isOfTypeOrGroupType(Item.Type.Rollershutter) || item.isOfTypeOrGroupType(Item.Type.Dimmer)) {
// If shutter is (partially) closed, open it, else close it
if (item.state?.asNumber?.value == 0F) "100" else "0"
} else if (item.state?.asBoolean == true) {
"OFF"
} else {
"ON"
}
}

private fun buildOutputData(hasConnection: Boolean, httpStatus: Int): Data {
return Data.Builder()
.putBoolean(OUTPUT_DATA_HAS_CONNECTION, hasConnection)
.putInt(OUTPUT_DATA_HTTP_STATUS, httpStatus)
.putString(OUTPUT_DATA_ITEM_NAME, inputData.getString(INPUT_DATA_ITEM_NAME))
.putString(OUTPUT_DATA_LABEL, inputData.getString(INPUT_DATA_LABEL))
.putString(OUTPUT_DATA_VALUE, inputData.getString(INPUT_DATA_VALUE))
.putString(OUTPUT_DATA_MAPPED_VALUE, inputData.getString(INPUT_DATA_MAPPED_VALUE))
.putValueWithInfo(OUTPUT_DATA_VALUE, inputData.getValueWithInfo(INPUT_DATA_VALUE))
.putBoolean(OUTPUT_DATA_SHOW_TOAST, inputData.getBoolean(INPUT_DATA_SHOW_TOAST, false))
.putString(OUTPUT_DATA_TASKER_INTENT, inputData.getString(INPUT_DATA_TASKER_INTENT))
.putString(OUTPUT_DATA_AS_COMMAND, inputData.getString(INPUT_DATA_AS_COMMAND))
Expand Down Expand Up @@ -238,7 +255,6 @@ class ItemUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont
private const val INPUT_DATA_ITEM_NAME = "item"
private const val INPUT_DATA_LABEL = "label"
private const val INPUT_DATA_VALUE = "value"
private const val INPUT_DATA_MAPPED_VALUE = "mappedValue"
private const val INPUT_DATA_SHOW_TOAST = "showToast"
private const val INPUT_DATA_TASKER_INTENT = "taskerIntent"
private const val INPUT_DATA_AS_COMMAND = "command"
Expand All @@ -248,7 +264,6 @@ class ItemUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont
const val OUTPUT_DATA_ITEM_NAME = "item"
const val OUTPUT_DATA_LABEL = "label"
const val OUTPUT_DATA_VALUE = "value"
const val OUTPUT_DATA_MAPPED_VALUE = "mappedValue"
const val OUTPUT_DATA_SHOW_TOAST = "showToast"
const val OUTPUT_DATA_TASKER_INTENT = "taskerIntent"
const val OUTPUT_DATA_AS_COMMAND = "command"
Expand All @@ -257,21 +272,43 @@ class ItemUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont
fun buildData(
itemName: String,
label: String?,
value: String,
mappedValue: String?,
value: ValueWithInfo,
showToast: Boolean,
taskerIntent: String?,
asCommand: Boolean
): Data {
return Data.Builder()
.putString(INPUT_DATA_ITEM_NAME, itemName)
.putString(INPUT_DATA_LABEL, label)
.putString(INPUT_DATA_VALUE, value)
.putString(INPUT_DATA_MAPPED_VALUE, mappedValue)
.putValueWithInfo(INPUT_DATA_VALUE, value)
.putBoolean(INPUT_DATA_SHOW_TOAST, showToast)
.putString(INPUT_DATA_TASKER_INTENT, taskerIntent)
.putBoolean(INPUT_DATA_AS_COMMAND, asCommand)
.build()
}
}

enum class ValueType {
Raw,
Timestamp
}

@Parcelize
data class ValueWithInfo(
val value: String,
val mappedValue: String? = null,
val type: ValueType = ValueType.Raw
) : Parcelable
}

fun Data.Builder.putValueWithInfo(key: String, value: ItemUpdateWorker.ValueWithInfo?): Data.Builder {
if (value != null) {
putStringArray(key, arrayOf(value.value, value.mappedValue, value.type.name))
}
return this
}

fun Data.getValueWithInfo(key: String): ItemUpdateWorker.ValueWithInfo? {
val array = getStringArray(key) ?: return null
return ItemUpdateWorker.ValueWithInfo(array[0], array[1], ItemUpdateWorker.ValueType.valueOf(array[2]))
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,7 @@ internal class NotificationUpdateObserver(context: Context) : Observer<List<Work
val data = info.outputData
val itemName = data.getString(ItemUpdateWorker.OUTPUT_DATA_ITEM_NAME)
val label = data.getString(ItemUpdateWorker.OUTPUT_DATA_LABEL)
val value = data.getString(ItemUpdateWorker.OUTPUT_DATA_VALUE)
val mappedValue = data.getString(ItemUpdateWorker.OUTPUT_DATA_MAPPED_VALUE)
val value = data.getValueWithInfo(ItemUpdateWorker.OUTPUT_DATA_VALUE)
val showToast = data.getBoolean(ItemUpdateWorker.OUTPUT_DATA_SHOW_TOAST, false)
val taskerIntent = data.getString(ItemUpdateWorker.OUTPUT_DATA_TASKER_INTENT)
val asCommand = data.getBoolean(ItemUpdateWorker.OUTPUT_DATA_AS_COMMAND, false)
Expand All @@ -103,7 +102,6 @@ internal class NotificationUpdateObserver(context: Context) : Observer<List<Work
itemName,
label,
value,
mappedValue,
showToast,
taskerIntent,
asCommand
Expand Down
10 changes: 8 additions & 2 deletions mobile/src/main/java/org/openhab/habdroid/model/Item.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,12 @@
package org.openhab.habdroid.model

import android.os.Parcelable

import kotlinx.android.parcel.Parcelize
import org.json.JSONException
import org.json.JSONObject
import org.openhab.habdroid.util.forEach
import org.openhab.habdroid.util.optStringOrNull
import org.openhab.habdroid.util.map
import org.openhab.habdroid.util.optStringOrNull
import org.w3c.dom.Node

@Parcelize
Expand Down Expand Up @@ -57,6 +56,13 @@ data class Item internal constructor(
return this.type == type || groupType == type
}

fun canBeToggled(): Boolean {
return isOfTypeOrGroupType(Type.Color) ||
isOfTypeOrGroupType(Type.Dimmer) ||
isOfTypeOrGroupType(Type.Rollershutter) ||
isOfTypeOrGroupType(Type.Switch)
}

companion object {
@Throws(JSONException::class)
fun updateFromEvent(item: Item?, jsonObject: JSONObject?): Item? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ fun String?.toNormalizedUrl(): String {
}
}

fun String?.orDefaultIfEmpty(defaultValue: String) = if (isNullOrEmpty()) defaultValue else this!!

fun Uri?.openInBrowser(context: Context) {
if (this == null) {
return
Expand Down