Skip to content

Commit

Permalink
Add setting for external display override
Browse files Browse the repository at this point in the history
- Closes #34
- Requires further testing
- Should work on supported Retroid devices
  • Loading branch information
miguelguzmanr committed May 22, 2024
1 parent 66a9fc7 commit c051961
Show file tree
Hide file tree
Showing 5 changed files with 316 additions and 11 deletions.
21 changes: 21 additions & 0 deletions app/src/main/java/de/langerhans/odintools/main/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import de.langerhans.odintools.ui.composables.SwitchPreference
import de.langerhans.odintools.ui.composables.SwitchableTriggerPreference
import de.langerhans.odintools.ui.composables.TriggerPreference
import de.langerhans.odintools.ui.composables.VibrationPreferenceDialog
import de.langerhans.odintools.ui.composables.VideoOutputOverridePreferenceDialog
import de.langerhans.odintools.ui.theme.OdinToolsTheme

@AndroidEntryPoint
Expand Down Expand Up @@ -117,6 +118,17 @@ fun SettingsScreen(viewModel: MainViewModel = hiltViewModel(), navigateToOverrid
)
}

if (uiState.showVideoOutputOverrideDialog) {
VideoOutputOverridePreferenceDialog(
initialControllerStyle = uiState.videoOutputControllerStyle,
initialL2R2Style = uiState.videoOutputL2R2Style,
onCancel = { viewModel.videoOutputControllerProfileDialogDismissed() },
onSave = { newControllerStyle, newL2R2Style ->
viewModel.saveVideoOutputControllerProfile(newControllerStyle, newL2R2Style)
},
)
}

if (uiState.showVibrationDialog) {
VibrationPreferenceDialog(
initialValue = uiState.currentVibration,
Expand Down Expand Up @@ -167,6 +179,15 @@ fun SettingsScreen(viewModel: MainViewModel = hiltViewModel(), navigateToOverrid
) {
viewModel.overrideDelayEnabled(it)
}
SwitchableTriggerPreference(
icon = R.drawable.ic_gamepad_docked,
title = R.string.videoOutputOverride,
description = R.string.videoOutputOverrideDescription,
state = uiState.videoOutputOverrideEnabled,
onClick = { viewModel.videoOutputControllerProfileClicked() },
) {
viewModel.updateExternalControllerProfilePreference(it)
}
SettingsHeader(R.string.quickSettings)
TriggerPreference(
icon = R.drawable.ic_face_buttons,
Expand Down
37 changes: 37 additions & 0 deletions app/src/main/java/de/langerhans/odintools/main/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import de.langerhans.odintools.R
import de.langerhans.odintools.data.SharedPrefsRepo
import de.langerhans.odintools.models.ControllerStyle
import de.langerhans.odintools.models.ControllerStyle.Disconnect
import de.langerhans.odintools.models.ControllerStyle.Odin
import de.langerhans.odintools.models.ControllerStyle.Xbox
import de.langerhans.odintools.models.L2R2Style
import de.langerhans.odintools.models.L2R2Style.Analog
import de.langerhans.odintools.models.L2R2Style.Both
import de.langerhans.odintools.models.L2R2Style.Digital
Expand Down Expand Up @@ -218,6 +220,41 @@ class MainViewModel @Inject constructor(
}
}

fun updateExternalControllerProfilePreference(newValue: Boolean) {
prefs.videoOutputOverrideEnabled = newValue
_uiState.update {
it.copy(videoOutputOverrideEnabled = newValue)
}
}

fun videoOutputControllerProfileClicked() {
_uiState.update {
it.copy(
showVideoOutputOverrideDialog = true,
videoOutputControllerStyle = ControllerStyle.getById(prefs.videoOutputControllerStyle),
videoOutputL2R2Style = L2R2Style.getById(prefs.videoOutputL2R2Style),
)
}
}

fun videoOutputControllerProfileDialogDismissed() {
_uiState.update {
it.copy(showVideoOutputOverrideDialog = false)
}
}

fun saveVideoOutputControllerProfile(newControllerStyle: ControllerStyle, newL2R2Style: L2R2Style) {
prefs.videoOutputControllerStyle = newControllerStyle.id
prefs.videoOutputL2R2Style = newL2R2Style.id
_uiState.update {
it.copy(
showVideoOutputOverrideDialog = false,
videoOutputControllerStyle = newControllerStyle,
videoOutputL2R2Style = newL2R2Style,
)
}
}

fun appOverridesEnabled(newValue: Boolean) {
prefs.appOverridesEnabled = newValue
_uiState.update {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import de.langerhans.odintools.models.L2R2Style
import de.langerhans.odintools.models.PerfMode
import de.langerhans.odintools.tools.BatteryLevelReceiver
import de.langerhans.odintools.tools.ShellExecutor
import de.langerhans.odintools.tools.VideoOutputReceiver
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
Expand All @@ -38,6 +39,7 @@ class ForegroundAppWatcherService @Inject constructor() : AccessibilityService()
lateinit var prefs: SharedPrefsRepo

private var batteryLevelReceiver: BatteryLevelReceiver = BatteryLevelReceiver()
private var videoOutputReceiver: VideoOutputReceiver = VideoOutputReceiver()

private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.IO + job)
Expand All @@ -63,6 +65,7 @@ class ForegroundAppWatcherService @Inject constructor() : AccessibilityService()
}

private var chargeLimitEnabled: Boolean = false
private var videoOutputOverrideEnabled: Boolean = false

override fun onAccessibilityEvent(event: AccessibilityEvent) {
if (shouldIgnore(event)) return
Expand Down Expand Up @@ -102,18 +105,21 @@ class ForegroundAppWatcherService @Inject constructor() : AccessibilityService()
savedFanMode = FanMode.getMode(executor)
}

ControllerStyle.getById(override.controllerStyle).takeIf {
it != Unknown
}?.enable(executor) ?: run {
// Reset to default if we switch between override and NoChange app
savedControllerStyle?.enable(executor)
}
// Avoid conflicts with Video Output Override
if (!videoOutputOverrideEnabled) {
ControllerStyle.getById(override.controllerStyle).takeIf {
it != Unknown
}?.enable(executor) ?: run {
// Reset to default if we switch between override and NoChange app
savedControllerStyle?.enable(executor)
}

L2R2Style.getById(override.l2R2Style).takeIf {
it != L2R2Style.Unknown
}?.enable(executor) ?: run {
// Reset to default if we switch between override and NoChange app
savedL2R2Style?.enable(executor)
L2R2Style.getById(override.l2R2Style).takeIf {
it != L2R2Style.Unknown
}?.enable(executor) ?: run {
// Reset to default if we switch between override and NoChange app
savedL2R2Style?.enable(executor)
}
}

PerfMode.getById(override.perfMode).takeIf {
Expand Down Expand Up @@ -165,6 +171,20 @@ class ForegroundAppWatcherService @Inject constructor() : AccessibilityService()
chargeLimitEnabled = newValue
}

private fun applyVideoOutputOverride(newValue: Boolean) {
if (newValue && !videoOutputOverrideEnabled) {
val intentFilter = IntentFilter().apply {
VideoOutputReceiver.ALLOWED_INTENTS.forEach { action ->
addAction(action)
}
}
registerReceiver(videoOutputReceiver, intentFilter)
} else if (!newValue && videoOutputOverrideEnabled) {
unregisterReceiver(videoOutputReceiver)
}
videoOutputOverrideEnabled = newValue
}

override fun onInterrupt() {
// Nothing here
}
Expand Down Expand Up @@ -192,6 +212,11 @@ class ForegroundAppWatcherService @Inject constructor() : AccessibilityService()
applyChargeLimit(it)
}

applyVideoOutputOverride(prefs.videoOutputOverrideEnabled)
prefs.observeVideoOutputOverrideEnabledState {
applyVideoOutputOverride(it)
}

scope.launch {
appOverrideDao.getAll()
.flowOn(Dispatchers.IO)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package de.langerhans.odintools.tools

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.Looper
import dagger.hilt.android.AndroidEntryPoint
import de.langerhans.odintools.data.SharedPrefsRepo
import de.langerhans.odintools.models.ControllerStyle
import de.langerhans.odintools.models.L2R2Style
import de.langerhans.odintools.service.ForegroundAppWatcherService.Companion.OVERRIDE_DELAY
import javax.inject.Inject

@AndroidEntryPoint
class VideoOutputReceiver : BroadcastReceiver() {

private var overrideEnabled = false
private var savedControllerStyle: ControllerStyle? = null
private var savedL2R2Style: L2R2Style? = null

@Inject
lateinit var executor: ShellExecutor

@Inject
lateinit var prefs: SharedPrefsRepo

private fun handleEvent(connected: Boolean?) {
if (connected == true && !overrideEnabled) {
// Save default styles
savedControllerStyle = ControllerStyle.getStyle(executor)
savedL2R2Style = L2R2Style.getStyle(executor)

// Apply style profile
ControllerStyle.getById(prefs.videoOutputControllerStyle).takeIf {
it != ControllerStyle.Unknown
}?.enable(executor)
L2R2Style.getById(prefs.videoOutputL2R2Style).takeIf {
it != L2R2Style.Unknown
}?.enable(executor)

overrideEnabled = true
} else if (connected == false && overrideEnabled) {
// Reset to defaults
savedControllerStyle?.enable(executor)
savedL2R2Style?.enable(executor)

overrideEnabled = false
}
}

override fun onReceive(context: Context, intent: Intent) {
if (intent.action !in ALLOWED_INTENTS) {
return
}

val connected = intent.extras?.getBoolean("is_connected")

if (prefs.overrideDelay) {
Handler(Looper.getMainLooper()).postDelayed({ handleEvent(connected) }, OVERRIDE_DELAY)
} else {
handleEvent(connected)
}
}

companion object {
private const val ACTION_DP_STATUS_CHANGED = "com.retrostation.action.DP_STATUS_CHANGED"
private const val ACTION_HDMI_STATUS_CHANGED = "com.retrostation.action.HDMI_STATUS_CHANGED"
private const val ACTION_HDMI_OR_DP_STATUS_CHANGED = "com.retrostation.action.HDMI_OR_DP_STATUS_CHANGED"
val ALLOWED_INTENTS = listOf(
ACTION_DP_STATUS_CHANGED,
ACTION_HDMI_STATUS_CHANGED,
ACTION_HDMI_OR_DP_STATUS_CHANGED,
)
}
}
Loading

0 comments on commit c051961

Please sign in to comment.