Skip to content

Commit

Permalink
Add screensaver (daydream)
Browse files Browse the repository at this point in the history
Add a simple daydream service that displays a clock and the text from a custom Item. The implementation is kept simple as there might be a screen saver in Android 15 that utilizes the device controls.

https://www.androidauthority.com/android-15-home-controls-screensaver-3433352/

Closes openhab#215

Signed-off-by: mueller-ma <[email protected]>
  • Loading branch information
mueller-ma committed Jun 2, 2024
1 parent 4deee3d commit 6c505fd
Show file tree
Hide file tree
Showing 11 changed files with 292 additions and 0 deletions.
3 changes: 3 additions & 0 deletions mobile/src/beta/res/xml/day_dream.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<dream xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="org.openhab.habdroid.beta/org.openhab.habdroid.ui.preference.DayDreamPreferencesActivity" />
18 changes: 18 additions & 0 deletions mobile/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>

<activity
android:name=".ui.preference.DayDreamPreferencesActivity"
android:exported="true" />
<activity
android:name=".ui.AboutActivity"
android:label="@string/about_title"
Expand Down Expand Up @@ -322,6 +326,20 @@
</intent-filter>
</service>

<service
android:name=".ui.DayDream"
android:exported="true"
android:label="@string/app_name"
android:permission="android.permission.BIND_DREAM_SERVICE">
<intent-filter>
<action android:name="android.service.dreams.DreamService" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.service.dream"
android:resource="@xml/day_dream" />
</service>

<!-- Tiles -->
<service android:name=".background.tiles.TileService1" android:label="@string/tile_label_1" android:icon="@drawable/ic_openhab_appicon_24dp" android:permission="android.permission.BIND_QUICK_SETTINGS_TILE" android:enabled="true" android:exported="true">
<intent-filter><action android:name="android.service.quicksettings.action.QS_TILE"/></intent-filter>
Expand Down
154 changes: 154 additions & 0 deletions mobile/src/main/java/org/openhab/habdroid/ui/DayDream.kt
Original file line number Diff line number Diff line change
@@ -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/<item>/statechanged
// - openhab/items/<group item>/<item>/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
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -70,6 +71,9 @@ class PreferencesActivity : AbstractBaseActivity() {
) ?: AppWidgetManager.INVALID_APPWIDGET_ID
WidgetSettingsFragment.newInstance(id)
}
intent.action == ACTION_DAY_DREAM -> {
DayDreamFragment()
}
else -> {
MainSettingsFragment()
}
Expand Down Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
3 changes: 3 additions & 0 deletions mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
32 changes: 32 additions & 0 deletions mobile/src/main/res/layout/daydream.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
android:id="@+id/wrapper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:ignore="UselessParent">

<TextClock
android:id="@+id/clock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Material3.DisplayLarge" />

<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingTop="64dp"
android:textAppearance="@style/TextAppearance.Material3.DisplayMedium"
tools:text="foo: bar\nfoo:baz" />
</LinearLayout>


</FrameLayout>
4 changes: 4 additions & 0 deletions mobile/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@
<string name="device_control">Android device control</string>
<string name="device_control_subtitle">Description in tiles</string>
<string name="device_control_hint">Depending on your Android version these tiles are located at long-press menu of the power button or next to the quick tiles</string>
<string name="screensaver">Screensaver</string>
<string name="screensaver_item">Displayed Item</string>
<string name="screensaver_bright_screen">Bright screen</string>
<string name="screensaver_error_loading_item">Error loading Item \"%s\"</string>

<!-- App messages strings -->
<string name="title_voice_widget">Voice commands</string>
Expand Down
16 changes: 16 additions & 0 deletions mobile/src/main/res/xml/preferences_day_dream.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<org.openhab.habdroid.ui.preference.widgets.CustomInputTypePreference
android:key="daydream_item"
android:title="@string/screensaver_item"
app:whitespaceBehavior="trim"
app:useSimpleSummaryProvider="true"
android:inputType="text" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="daydream_bright_screen"
android:title="@string/screensaver_bright_screen"
android:widgetLayout="@layout/preference_material_switch"
app:singleLineTitle="false" />
</androidx.preference.PreferenceScreen>
3 changes: 3 additions & 0 deletions mobile/src/stable/res/xml/day_dream.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<dream xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="org.openhab.habdroid.ui.preference.DayDreamPreferencesActivity" />

0 comments on commit 6c505fd

Please sign in to comment.