diff --git a/mobile/src/beta/res/xml/day_dream.xml b/mobile/src/beta/res/xml/day_dream.xml
new file mode 100644
index 0000000000..a116f55d37
--- /dev/null
+++ b/mobile/src/beta/res/xml/day_dream.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml
index 7582d6e690..f0d30ebe3c 100644
--- a/mobile/src/main/AndroidManifest.xml
+++ b/mobile/src/main/AndroidManifest.xml
@@ -91,6 +91,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/DayDream.kt b/mobile/src/main/java/org/openhab/habdroid/ui/DayDream.kt
new file mode 100644
index 0000000000..5d297d7522
--- /dev/null
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/DayDream.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.openhab.habdroid.ui
+
+import android.animation.ObjectAnimator
+import android.service.dreams.DreamService
+import android.util.Log
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import android.widget.TextView
+import kotlin.coroutines.CoroutineContext
+import kotlin.random.Random
+import kotlin.time.Duration.Companion.minutes
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
+import org.json.JSONException
+import org.json.JSONObject
+import org.openhab.habdroid.R
+import org.openhab.habdroid.core.connection.ConnectionFactory
+import org.openhab.habdroid.util.HttpClient
+import org.openhab.habdroid.util.ItemClient
+import org.openhab.habdroid.util.PrefKeys
+import org.openhab.habdroid.util.getPrefs
+import org.openhab.habdroid.util.getStringOrNull
+
+class DayDream : DreamService(), CoroutineScope {
+ private val job = Job()
+ override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job
+ private lateinit var textView: TextView
+ private lateinit var wrapper: LinearLayout
+ private lateinit var container: FrameLayout
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ isInteractive = false
+ isFullscreen = true
+ isScreenBright = applicationContext.getPrefs().getBoolean(PrefKeys.DAY_DREAM_BRIGHT_SCREEN, true)
+ setContentView(R.layout.daydream)
+ }
+
+ override fun onDreamingStarted() {
+ super.onDreamingStarted()
+ val item = applicationContext.getPrefs().getStringOrNull(PrefKeys.DAY_DREAM_ITEM)
+
+ textView = findViewById(R.id.text)
+ wrapper = findViewById(R.id.wrapper)
+ container = findViewById(R.id.container)
+
+ launch {
+ item?.let { listenForTextItem(it) }
+ }
+ launch {
+ moveText()
+ }
+ }
+
+ private suspend fun listenForTextItem(item: String) {
+ ConnectionFactory.waitForInitialization()
+ val connection = ConnectionFactory.primaryUsableConnection?.connection ?: return
+
+ val eventSubscription = connection.httpClient.makeSse(
+ // Support for both the "openhab" and the older "smarthome" root topic by using a wildcard
+ connection.httpClient.buildUrl("rest/events?topics=*/items/$item/command")
+ )
+
+ textView.text = try {
+ ItemClient.loadItem(connection, item)?.state?.asString.orEmpty()
+ } catch (e: HttpClient.HttpException) {
+ getString(R.string.screensaver_error_loading_item, item)
+ }
+
+ try {
+ while (isActive) {
+ try {
+ val event = JSONObject(eventSubscription.getNextEvent())
+ if (event.optString("type") == "ALIVE") {
+ Log.d(TAG, "Got ALIVE event")
+ continue
+ }
+ val topic = event.getString("topic")
+ val topicPath = topic.split('/')
+ // Possible formats:
+ // - openhab/items/- /statechanged
+ // - openhab/items//
- /statechanged
+ // When an update for a group is sent, there's also one for the individual item.
+ // Therefore always take the element on index two.
+ if (topicPath.size !in 4..5) {
+ throw JSONException("Unexpected topic path $topic")
+ }
+ val state = JSONObject(event.getString("payload")).getString("value")
+ Log.d(TAG, "Got state by event: $state")
+ textView.text = state
+ } catch (e: JSONException) {
+ Log.e(TAG, "Failed parsing JSON of state change event", e)
+ }
+ }
+ } finally {
+ eventSubscription.cancel()
+ }
+ }
+
+ private suspend fun moveText() {
+ wrapper.fadeOut()
+ wrapper.moveViewToRandomPosition(container)
+ wrapper.fadeIn()
+ delay(1.minutes)
+ moveText()
+ }
+
+ private fun View.moveViewToRandomPosition(container: FrameLayout) {
+ val randomX = Random.nextInt(0, container.width - width)
+ val randomY = Random.nextInt(0, container.height - height)
+
+ x = randomX.toFloat()
+ y = randomY.toFloat()
+ }
+
+ private fun View.fadeOut() {
+ val animator = ObjectAnimator.ofFloat(this, View.ALPHA, 1f, 0f)
+ animator.duration = 2000
+ animator.start()
+ }
+
+ private fun View.fadeIn() {
+ val animator = ObjectAnimator.ofFloat(this, View.ALPHA, 0f, 1f)
+ animator.duration = 2000
+ animator.start()
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ job.cancel()
+ }
+
+ companion object {
+ private val TAG = DayDream::class.java.simpleName
+ }
+}
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/DayDreamPreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/DayDreamPreferencesActivity.kt
new file mode 100644
index 0000000000..1fa1b03bc6
--- /dev/null
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/DayDreamPreferencesActivity.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.openhab.habdroid.ui.preference
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+
+class DayDreamPreferencesActivity : AppCompatActivity() {
+ public override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val intent = Intent(this, PreferencesActivity::class.java)
+ intent.action = PreferencesActivity.ACTION_DAY_DREAM
+ startActivity(intent)
+ }
+}
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/PreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/PreferencesActivity.kt
index b1b5aff9c6..aa8ca17bc3 100644
--- a/mobile/src/main/java/org/openhab/habdroid/ui/preference/PreferencesActivity.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/PreferencesActivity.kt
@@ -31,6 +31,7 @@ import org.openhab.habdroid.R
import org.openhab.habdroid.background.tiles.AbstractTileService
import org.openhab.habdroid.ui.AbstractBaseActivity
import org.openhab.habdroid.ui.preference.fragments.AbstractSettingsFragment
+import org.openhab.habdroid.ui.preference.fragments.DayDreamFragment
import org.openhab.habdroid.ui.preference.fragments.MainSettingsFragment
import org.openhab.habdroid.ui.preference.fragments.TileOverviewFragment
import org.openhab.habdroid.ui.preference.fragments.TileSettingsFragment
@@ -70,6 +71,9 @@ class PreferencesActivity : AbstractBaseActivity() {
) ?: AppWidgetManager.INVALID_APPWIDGET_ID
WidgetSettingsFragment.newInstance(id)
}
+ intent.action == ACTION_DAY_DREAM -> {
+ DayDreamFragment()
+ }
else -> {
MainSettingsFragment()
}
@@ -175,6 +179,7 @@ class PreferencesActivity : AbstractBaseActivity() {
}
companion object {
+ const val ACTION_DAY_DREAM = "day_dream"
const val RESULT_EXTRA_THEME_CHANGED = "theme_changed"
const val RESULT_EXTRA_SITEMAP_CLEARED = "sitemap_cleared"
const val RESULT_EXTRA_SITEMAP_DRAWER_CHANGED = "sitemap_drawer_changed"
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/DayDreamFragment.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/DayDreamFragment.kt
new file mode 100644
index 0000000000..a0278304ce
--- /dev/null
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/DayDreamFragment.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.openhab.habdroid.ui.preference.fragments
+
+import android.os.Bundle
+import androidx.annotation.StringRes
+import org.openhab.habdroid.R
+
+class DayDreamFragment : AbstractSettingsFragment() {
+ override val titleResId: Int @StringRes get() = R.string.screensaver
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ addPreferencesFromResource(R.xml.preferences_day_dream)
+ }
+}
diff --git a/mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt b/mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt
index 6a4aa08d29..168a319030 100644
--- a/mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt
@@ -90,6 +90,9 @@ object PrefKeys {
const val DRAWER_ENTRY_NFC = "show_nfc"
const val DRAWER_ENTRY_FRONTAIL = "show_frontail"
+ const val DAY_DREAM_ITEM = "daydream_item"
+ const val DAY_DREAM_BRIGHT_SCREEN = "daydream_bright_screen"
+
/**
* Application state flags
*/
diff --git a/mobile/src/main/res/layout/daydream.xml b/mobile/src/main/res/layout/daydream.xml
new file mode 100644
index 0000000000..fb1df6886b
--- /dev/null
+++ b/mobile/src/main/res/layout/daydream.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml
index 4ca9b7b4bb..ed37634802 100644
--- a/mobile/src/main/res/values/strings.xml
+++ b/mobile/src/main/res/values/strings.xml
@@ -157,6 +157,10 @@
Android device control
Description in tiles
Depending on your Android version these tiles are located at long-press menu of the power button or next to the quick tiles
+ Screensaver
+ Displayed Item
+ Bright screen
+ Error loading Item \"%s\"
Voice commands
diff --git a/mobile/src/main/res/xml/preferences_day_dream.xml b/mobile/src/main/res/xml/preferences_day_dream.xml
new file mode 100644
index 0000000000..6bbd9bd6aa
--- /dev/null
+++ b/mobile/src/main/res/xml/preferences_day_dream.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/mobile/src/stable/res/xml/day_dream.xml b/mobile/src/stable/res/xml/day_dream.xml
new file mode 100644
index 0000000000..ba37f5eb69
--- /dev/null
+++ b/mobile/src/stable/res/xml/day_dream.xml
@@ -0,0 +1,3 @@
+
+