Skip to content
Closed
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 @@ -17,6 +17,7 @@ import android.widget.Button
import android.widget.FrameLayout
import android.widget.TextView
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.activity.enableEdgeToEdge
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
Expand All @@ -43,6 +44,7 @@ import com.andrerinas.headunitrevived.view.OverlayTouchView
import com.andrerinas.headunitrevived.utils.HeadUnitScreenConfig
import com.andrerinas.headunitrevived.utils.SystemUI
import android.content.IntentFilter
import com.andrerinas.headunitrevived.view.ProjectionViewScaler

class AapProjectionActivity : SurfaceActivity(), IProjectionView.Callbacks, VideoDimensionsListener {

Expand All @@ -67,7 +69,10 @@ class AapProjectionActivity : SurfaceActivity(), IProjectionView.Callbacks, Vide
}
private val reconnectingWatchdog = object : Runnable {
override fun run() {
if (!commManager.isConnected) return
// Only run watchdog if we are actually supposed to be connected
if (commManager.connectionState.value !is CommManager.ConnectionState.HandshakeComplete) {
return
}
val lastFrame = videoDecoder.lastFrameRenderedMs
if (lastFrame == 0L) {
// First frame hasn't arrived yet — handled by the starting overlay
Expand Down Expand Up @@ -99,8 +104,8 @@ class AapProjectionActivity : SurfaceActivity(), IProjectionView.Callbacks, Vide
} else {
AppLog.e("Watchdog: TextureView NOT available. Vis=${tv.visibility}, W=${tv.width}, H=${tv.height}")
}
} else if (projectionView is com.andrerinas.headunitrevived.view.GlProjectionView) {
val gles = projectionView as com.andrerinas.headunitrevived.view.GlProjectionView
} else if (projectionView is GlProjectionView) {
val gles = projectionView as GlProjectionView
if (gles.isSurfaceValid()) {
AppLog.w("Watchdog: GlProjectionView IS valid. Forcing onSurfaceChanged.")
onSurfaceChanged(gles.getSurface()!!, gles.width, gles.height)
Expand Down Expand Up @@ -168,6 +173,20 @@ class AapProjectionActivity : SurfaceActivity(), IProjectionView.Callbacks, Vide

setContentView(R.layout.activity_headunit)

onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
val currentTime = System.currentTimeMillis()
if (currentTime - lastBackPressTime < 2000) {
AppLog.i("AapProjectionActivity: Double back press detected. Disconnecting...")
commManager.disconnect()
finish()
} else {
lastBackPressTime = currentTime
Toast.makeText(this@AapProjectionActivity, getString(R.string.press_back_again_to_exit), Toast.LENGTH_SHORT).show()
}
}
})

if (settings.showFpsCounter) {
val container = findViewById<FrameLayout>(R.id.container)
val fpsText = TextView(this).apply {
Expand Down Expand Up @@ -248,19 +267,19 @@ class AapProjectionActivity : SurfaceActivity(), IProjectionView.Callbacks, Vide
FrameLayout.LayoutParams.MATCH_PARENT
)
projectionView = glView
container.setBackgroundColor(android.graphics.Color.BLACK)
container.setBackgroundColor(Color.BLACK)
} else {
AppLog.i("Using SurfaceView")
projectionView = ProjectionView(this)
(projectionView as android.view.View).layoutParams = FrameLayout.LayoutParams(
(projectionView as View).layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
}
// Use the same screen conf for both views for negotiation
HeadUnitScreenConfig.init(this, displayMetrics, settings)

val view = projectionView as android.view.View
val view = projectionView as View
container.addView(view)

projectionView.addCallback(this)
Expand Down Expand Up @@ -377,7 +396,11 @@ class AapProjectionActivity : SurfaceActivity(), IProjectionView.Callbacks, Vide
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN)
}

SystemUI.apply(window, container, settings.fullscreenMode)
SystemUI.apply(window, container, settings.fullscreenMode) {
if (::projectionView.isInitialized) {
ProjectionViewScaler.updateScale(projectionView as View, videoDecoder.videoWidth, videoDecoder.videoHeight)
}
}

// Workaround for API < 19 (Jelly Bean) where Sticky Immersive Mode doesn't exist.
// If bars appear (e.g. on touch), hide them again after a delay.
Expand All @@ -386,7 +409,11 @@ class AapProjectionActivity : SurfaceActivity(), IProjectionView.Callbacks, Vide
if ((visibility and View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
// Bars are visible. Hide them again.
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
SystemUI.apply(window, container, settings.fullscreenMode)
SystemUI.apply(window, container, settings.fullscreenMode) {
if (::projectionView.isInitialized) {
ProjectionViewScaler.updateScale(projectionView as View, videoDecoder.videoWidth, videoDecoder.videoHeight)
}
}
}, 2000)
}
}
Expand All @@ -395,18 +422,6 @@ class AapProjectionActivity : SurfaceActivity(), IProjectionView.Callbacks, Vide

private var lastBackPressTime = 0L

override fun onBackPressed() {
val currentTime = System.currentTimeMillis()
if (currentTime - lastBackPressTime < 2000) {
AppLog.i("AapProjectionActivity: Double back press detected. Disconnecting...")
commManager.disconnect()
super.onBackPressed()
} else {
lastBackPressTime = currentTime
Toast.makeText(this, getString(R.string.press_back_again_to_exit), Toast.LENGTH_SHORT).show()
}
}

private val commManager get() = App.provide(this).commManager

override fun onSurfaceCreated(surface: android.view.Surface) {
Expand Down Expand Up @@ -453,7 +468,7 @@ class AapProjectionActivity : SurfaceActivity(), IProjectionView.Callbacks, Vide
AppLog.i("[AapProjectionActivity] Decoder already has dimensions: ${currentVideoWidth}x$currentVideoHeight. Applying to view.")
runOnUiThread {
projectionView.setVideoSize(currentVideoWidth, currentVideoHeight)
projectionView.setVideoScale(HeadUnitScreenConfig.getScaleX(), HeadUnitScreenConfig.getScaleY())
ProjectionViewScaler.updateScale(projectionView as View, currentVideoWidth, currentVideoHeight)
}
}
}
Expand All @@ -469,7 +484,7 @@ class AapProjectionActivity : SurfaceActivity(), IProjectionView.Callbacks, Vide
AppLog.i("[AapProjectionActivity] Received video dimensions: ${width}x$height")
runOnUiThread {
projectionView.setVideoSize(width, height)
projectionView.setVideoScale(HeadUnitScreenConfig.getScaleX(), HeadUnitScreenConfig.getScaleY())
ProjectionViewScaler.updateScale(projectionView as View, width, height)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,7 @@ class AapService : Service(), UsbReceiver.Listener {
if (selfMode) {
AppLog.i("AapService: Self Mode disconnected. Not restarting.")
selfMode = false
stopWirelessServer()
return
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class SettingsFragment : Fragment() {

// Flag to determine if the projection should stretch to fill the screen
private var pendingStretchToFill: Boolean? = null
private var pendingForcedScale: Boolean? = null

private var pendingKillOnDisconnect: Boolean? = null
private var pendingAutoEnableHotspot: Boolean? = null
Expand Down Expand Up @@ -109,6 +110,7 @@ class SettingsFragment : Fragment() {

// Initialize local state for stretch to fill
pendingStretchToFill = settings.stretchToFill
pendingForcedScale = settings.forcedScale

pendingKillOnDisconnect = settings.killOnDisconnect
pendingAutoEnableHotspot = settings.autoEnableHotspot
Expand Down Expand Up @@ -232,6 +234,7 @@ class SettingsFragment : Fragment() {

// Save the stretch to fill preference
pendingStretchToFill?.let { settings.stretchToFill = it }
pendingForcedScale?.let { settings.forcedScale = it }

pendingKillOnDisconnect?.let { settings.killOnDisconnect = it }
pendingAutoEnableHotspot?.let { settings.autoEnableHotspot = it }
Expand Down Expand Up @@ -294,6 +297,7 @@ class SettingsFragment : Fragment() {
pendingScreenOrientation != settings.screenOrientation ||
pendingAppLanguage != settings.appLanguage ||
pendingStretchToFill != settings.stretchToFill ||
pendingForcedScale != settings.forcedScale ||
pendingInsetLeft != settings.insetLeft ||
pendingInsetTop != settings.insetTop ||
pendingInsetRight != settings.insetRight ||
Expand Down Expand Up @@ -616,13 +620,15 @@ class SettingsFragment : Fragment() {
Settings.FullscreenMode.NONE -> getString(R.string.fullscreen_none)
Settings.FullscreenMode.IMMERSIVE -> getString(R.string.fullscreen_immersive)
Settings.FullscreenMode.STATUS_ONLY -> getString(R.string.fullscreen_status_only)
Settings.FullscreenMode.IMMERSIVE_WITH_NOTCH -> getString(R.string.fullscreen_immersive_avoid_notch)
else -> getString(R.string.auto)
},
onClick = {
val modes = arrayOf(
getString(R.string.fullscreen_none),
getString(R.string.fullscreen_immersive),
getString(R.string.fullscreen_status_only)
getString(R.string.fullscreen_status_only),
getString(R.string.fullscreen_immersive_avoid_notch)
)
MaterialAlertDialogBuilder(requireContext(), R.style.DarkAlertDialog)
.setTitle(R.string.start_in_fullscreen_mode)
Expand Down Expand Up @@ -703,6 +709,21 @@ class SettingsFragment : Fragment() {
}
))

if (pendingViewMode == Settings.ViewMode.SURFACE) {
items.add(SettingItem.ToggleSettingEntry(
stableId = "forcedScale",
nameResId = R.string.forced_scale,
descriptionResId = R.string.forced_scale_description,
isChecked = pendingForcedScale!!,
onCheckedChanged = { isChecked ->
pendingForcedScale = isChecked
requiresRestart = true
checkChanges()
updateSettingsList()
}
))
}

// --- Video Settings ---
items.add(SettingItem.CategoryHeader("video", R.string.category_video))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ object HeadUnitScreenConfig {
// Flag to determine if the projection should stretch and ignore aspect ratio
private var stretchToFill: Boolean = false

// Forced scale for older devices (Legacy fix)
var forcedScale: Boolean = false
private set

var negotiatedResolutionType: Control.Service.MediaSinkService.VideoConfiguration.VideoCodecResolutionType? = null
private lateinit var currentSettings: Settings // Store settings instance

Expand All @@ -41,6 +45,8 @@ object HeadUnitScreenConfig {
fun init(context: Context, displayMetrics: DisplayMetrics, settings: Settings) {
// Read user preference for stretching the screen from the app's Settings
stretchToFill = settings.stretchToFill
// Only allow forcedScale if viewMode is SURFACE (0)
forcedScale = settings.forcedScale && settings.viewMode == Settings.ViewMode.SURFACE

val screenWidth: Int
val screenHeight: Int
Expand Down Expand Up @@ -208,6 +214,10 @@ object HeadUnitScreenConfig {
}

fun getScaleX(): Float {
if (forcedScale) {
return 1.0f
}

if (getNegotiatedWidth() > screenWidthPx) {
return divideOrOne(getNegotiatedWidth().toFloat(), screenWidthPx.toFloat())
}
Expand All @@ -217,7 +227,11 @@ object HeadUnitScreenConfig {
return 1.0f
}
// Stretch option PR #259
fun getScaleY(): Float {
fun getScaleY(): Float {
if (forcedScale) {
return 1.0f
}

if (getNegotiatedHeight() > screenHeightPx) {
return if (stretchToFill) {
// Before PR #233 Fix scaler Y
Expand Down Expand Up @@ -259,4 +273,7 @@ object HeadUnitScreenConfig {
val fIntValue = (getNegotiatedHeight() - getHeightMargin()).toFloat() / screenHeightPx.toFloat()
return fIntValue
}

fun getUsableWidth(): Int = screenWidthPx
fun getUsableHeight(): Int = screenHeightPx
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ class Settings(context: Context) {
get() = prefs.getBoolean("stretch_to_fill", true)
set(value) { prefs.edit().putBoolean("stretch_to_fill", value).apply() }

// Forced scale for older devices (SurfaceView fix)
var forcedScale: Boolean
get() = prefs.getBoolean("forced_scale", false)
set(value) { prefs.edit().putBoolean("forced_scale", value).apply() }

var micSampleRate: Int
get() = prefs.getInt("mic-sample-rate", 16000)
set(sampleRate) {
Expand Down Expand Up @@ -623,7 +628,8 @@ class Settings(context: Context) {
enum class FullscreenMode(val value: Int) {
NONE(0),
IMMERSIVE(1),
STATUS_ONLY(2);
STATUS_ONLY(2),
IMMERSIVE_WITH_NOTCH(3);

companion object {
private val map = values().associateBy(FullscreenMode::value)
Expand Down
Loading