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

Add several display sensors around various screen states #4634

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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 @@ -274,6 +274,14 @@ open class HomeAssistantApplication : Application() {
)
}

// Register for changes to the configuration
ContextCompat.registerReceiver(
this,
sensorReceiver,
IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED),
ContextCompat.RECEIVER_NOT_EXPORTED
)

// Update widgets when the screen turns on, updates are skipped if widgets were not added
val buttonWidget = ButtonWidget()
val entityWidget = EntityWidget()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,11 @@ class SensorReceiver : SensorReceiverBase() {
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE to listOf(DevicePolicyManager.isWorkProfile.id),
Intent.ACTION_MANAGED_PROFILE_AVAILABLE to listOf(DevicePolicyManager.isWorkProfile.id),
WifiManager.WIFI_STATE_CHANGED_ACTION to listOf(NetworkSensorManager.wifiState.id),
NfcAdapter.ACTION_ADAPTER_STATE_CHANGED to listOf(NfcSensorManager.nfcStateSensor.id)
NfcAdapter.ACTION_ADAPTER_STATE_CHANGED to listOf(NfcSensorManager.nfcStateSensor.id),
Intent.ACTION_CONFIGURATION_CHANGED to listOf(
DisplaySensorManager.screenRotation.id,
DisplaySensorManager.screenOrientation.id
)
)

override fun getSensorSettingsIntent(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
package io.homeassistant.companion.android.common.sensors

import android.content.Context
import android.content.pm.PackageManager.FEATURE_SENSOR_ACCELEROMETER
import android.content.res.Configuration
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager.SENSOR_DELAY_NORMAL
import android.hardware.display.DisplayManager
import android.provider.Settings
import android.util.Log
import android.view.Display
import android.view.Surface
import androidx.core.content.getSystemService
import io.homeassistant.companion.android.common.R as commonR
import io.homeassistant.companion.android.common.util.STATE_UNKNOWN

class DisplaySensorManager : SensorManager {
class DisplaySensorManager : SensorManager, SensorEventListener {
companion object {
private const val TAG = "ScreenBrightness"
private const val TAG = "DisplaySensors"
private var hasAccelerometer = false
private var isListenerRegistered = false
private var listenerLastRegistered = 0

val screenBrightness = SensorManager.BasicSensor(
"screen_brightness",
Expand All @@ -26,23 +40,70 @@ class DisplaySensorManager : SensorManager {
"mdi:cellphone-off",
docsLink = "https://companion.home-assistant.io/docs/core/sensors#screen-off-timeout-sensor"
)

val screenOrientation = SensorManager.BasicSensor(
"screen_orientation",
"sensor",
commonR.string.sensor_name_screen_orientation,
commonR.string.sensor_description_screen_orientation,
"mdi:screen-rotation",
docsLink = "https://companion.home-assistant.io/docs/core/sensors#screen-orientation-sensor",
updateType = SensorManager.BasicSensor.UpdateType.INTENT
)

val screenRotation = SensorManager.BasicSensor(
"screen_rotation",
"sensor",
commonR.string.sensor_name_screen_rotation,
commonR.string.sensor_description_screen_rotation,
"mdi:screen-rotation",
docsLink = "https://companion.home-assistant.io/docs/core/sensors#screen-rotation-sensor",
unitOfMeasurement = "°"
)

val isFaceDownOrUp = SensorManager.BasicSensor(
"is_face_down_or_up",
"sensor",
commonR.string.sensor_name_is_face_down_or_up,
commonR.string.sensor_description_is_face_down_or_up,
"mdi:hand-pointing-down",
docsLink = "https://companion.home-assistant.io/docs/core/sensors#is-face-down-or-up-sensor"
)
}
override val name: Int
get() = commonR.string.sensor_name_display_sensors

override suspend fun getAvailableSensors(context: Context): List<SensorManager.BasicSensor> {
return listOf(screenBrightness, screenOffTimeout)
hasAccelerometer = context.packageManager.hasSystemFeature(FEATURE_SENSOR_ACCELEROMETER)
return if (hasAccelerometer) {
listOf(screenBrightness, screenOffTimeout, screenOrientation, screenRotation, isFaceDownOrUp)
} else {
listOf(screenBrightness, screenOffTimeout, screenOrientation, screenRotation)
}
}

override fun requiredPermissions(sensorId: String): Array<String> {
return emptyArray()
}

override fun docsLink(): String {
return "https://companion.home-assistant.io/docs/core/sensors#display-sensors"
}

private lateinit var latestContext: Context
private lateinit var mySensorManager: android.hardware.SensorManager

override fun requestSensorUpdate(
context: Context
) {
latestContext = context
updateScreenBrightness(context)
updateScreenTimeout(context)
updateScreenOrientation(context)
updateScreenRotation(context)
if (hasAccelerometer) {
updateIsFaceDownOrUp(context)
}
}

private fun updateScreenBrightness(context: Context) {
Expand Down Expand Up @@ -98,4 +159,130 @@ class DisplaySensorManager : SensorManager {
mapOf()
)
}

private fun updateScreenOrientation(context: Context) {
if (!isEnabled(context, screenOrientation)) {
return
}

@Suppress("DEPRECATION")
jpelgrom marked this conversation as resolved.
Show resolved Hide resolved
val orientation = when (context.resources.configuration.orientation) {
Configuration.ORIENTATION_PORTRAIT -> "portrait"
Configuration.ORIENTATION_LANDSCAPE -> "landscape"
Configuration.ORIENTATION_SQUARE -> "square"
Configuration.ORIENTATION_UNDEFINED -> STATE_UNKNOWN
else -> STATE_UNKNOWN
}
dshokouhi marked this conversation as resolved.
Show resolved Hide resolved

val icon = when (orientation) {
"portrait" -> "mdi:phone-rotate-portrait"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the icon to rotate from landscape to portrait, not sure about the fit 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes the icons were not easy to pick, originally I had crop-portrait but this looked more fun lol

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When quickly looking at it the icons seem identical but I recognize how hard it is. I was thinking about tablet for landscape, cellphone for portrait for a while but the screen aspect ratios don't match unfortunately.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea this one wasnt easy based on the current available options lol

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this could be good to leave as is in case another contributor has a better idea? Picking icons and naming things are usually the most thought driven part of PRs like this :)

"landscape" -> "mdi:phone-rotate-landscape"
"square" -> "mdi:crop-square"
else -> screenOrientation.statelessIcon
}

onSensorUpdated(
context,
screenOrientation,
orientation,
icon,
mapOf(
"options" to listOf("portrait", "landscape", "square", STATE_UNKNOWN)
)
)
}

private fun updateScreenRotation(context: Context) {
if (!isEnabled(context, screenRotation)) {
return
}

val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager

val display = getRotationString(dm.getDisplay(Display.DEFAULT_DISPLAY).rotation)

val multiple = dm.displays.associate { it.name to getRotationString(it.rotation) }
val possibleStates = listOf("0", "90", "180", "270")
val attrs = if (dm.displays.size > 1) {
multiple.plus("options" to possibleStates)
} else {
mapOf("options" to possibleStates)
}
onSensorUpdated(
context,
screenRotation,
display,
screenRotation.statelessIcon,
attrs
)
}

private fun updateIsFaceDownOrUp(context: Context) {
if (!isEnabled(context, isFaceDownOrUp)) {
return
}
val now = System.currentTimeMillis()
if (listenerLastRegistered + SensorManager.SENSOR_LISTENER_TIMEOUT < now && isListenerRegistered) {
Log.d(TAG, "Re-registering listener as it appears to be stuck")
mySensorManager.unregisterListener(this)
isListenerRegistered = false
}
mySensorManager = latestContext.getSystemService()!!

val accelerometerSensor = mySensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
if (accelerometerSensor != null && !isListenerRegistered) {
mySensorManager.registerListener(
this,
accelerometerSensor,
SENSOR_DELAY_NORMAL
)
Log.d(TAG, "Accelerometer sensor listener registered")
isListenerRegistered = true
listenerLastRegistered = now.toInt()
}
}

override fun onSensorChanged(event: SensorEvent?) {
if (event?.sensor?.type == Sensor.TYPE_ACCELEROMETER) {
// Following the example from: https://developer.android.com/reference/android/hardware/SensorEvent#values
// When the device is lying flat with the screen down the value is inverted
val state = when {
event.values[2] < -9 -> "down"
event.values[2] > 9 -> "up"
else -> STATE_UNKNOWN
}
val icon = when (state) {
"down" -> "mdi:hand-pointing-down"
"up" -> "mdi:hand-pointing-up"
else -> "mdi:crosshairs-question"
}
onSensorUpdated(
latestContext,
isFaceDownOrUp,
state,
icon,
mapOf(
"x" to event.values[0],
"y" to event.values[1],
"z" to event.values[2]
)
)
}

mySensorManager.unregisterListener(this)
Log.d(TAG, "Accelerometer sensor listener unregistered")
isListenerRegistered = false
}

override fun onAccuracyChanged(p0: Sensor?, p1: Int) {
// No op
}

private fun getRotationString(rotate: Int): String = when (rotate) {
Surface.ROTATION_0 -> "0"
Surface.ROTATION_90 -> "90"
Surface.ROTATION_180 -> "180"
Surface.ROTATION_270 -> "270"
else -> STATE_UNKNOWN
}
}
6 changes: 6 additions & 0 deletions common/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1283,4 +1283,10 @@
<string name="basic_sensor_name_sim_1_data_network_type">Data network type (SIM 1)</string>
<string name="basic_sensor_name_sim_2_data_network_type">Data network type (SIM 2)</string>
<string name="sensor_description_data_network_type">The radio technology (network type) currently in use on the device for data transmission</string>
<string name="sensor_name_screen_orientation">Screen orientation</string>
<string name="sensor_description_screen_orientation">Overall orientation of the screen</string>
<string name="sensor_name_screen_rotation">Screen rotation</string>
<string name="sensor_description_screen_rotation">The rotation of the screen from its \"natural\" orientation. If the device has multiple displays the state will be for the default display. Attributes will exist for additional detected displays with the display name and its rotation angle.</string>
<string name="sensor_name_is_face_down_or_up">Is face down or up</string>
<string name="sensor_description_is_face_down_or_up">If the device screen is facing down at the ground, up at the air or unknown</string>
</resources>