@@ -17,7 +17,9 @@ import androidx.savedstate.SavedStateRegistry
1717import androidx.savedstate.SavedStateRegistryOwner
1818import androidx.savedstate.setViewTreeSavedStateRegistryOwner
1919import com.enaboapps.switchify.service.core.SwitchifyLifecycleOwner
20+ import com.enaboapps.switchify.backend.preferences.PreferenceManager
2021import com.enaboapps.switchify.service.llm.MediaPipeBackend
22+ import com.enaboapps.switchify.service.menu.MenuViewHandler
2123import com.enaboapps.switchify.service.utils.ScreenWatcher
2224import com.enaboapps.switchify.service.window.overlay.OverlayPlacement
2325import com.enaboapps.switchify.service.window.overlay.OverlayDisplayMetrics
@@ -46,7 +48,9 @@ class SwitchifyAccessibilityWindow private constructor() : LifecycleOwner, Saved
4648 private var screenWatcher: ScreenWatcher ? = null
4749 private var isVisible = false
4850 private var windowGeneration = 0
49- private var rootOverlayId: Long? = null
51+ private var rootOverlayId: String? = null
52+ private var serviceEpoch = 0L
53+ private var overlaySequence = 0L
5054 private var acceptingOverlayOperations = false
5155 private val windowStateCleaner = WindowStateCleaner (
5256 onCleanupStarted = { state, rootAttached ->
@@ -92,7 +96,7 @@ class SwitchifyAccessibilityWindow private constructor() : LifecycleOwner, Saved
9296 val baseLayout : RelativeLayout ? ,
9397 val surfaceHandles : MutableMap <ViewGroup , OverlayHandle >,
9498 val wasVisible : Boolean ,
95- val rootOverlayId : Long ?
99+ val rootOverlayId : String ?
96100 )
97101
98102 private val defaultDisplayTarget = OverlayTargets .defaultDisplay().copy(forceSurface = true )
@@ -110,6 +114,8 @@ class SwitchifyAccessibilityWindow private constructor() : LifecycleOwner, Saved
110114 fun setup (context : Context ) {
111115 try {
112116 acceptingOverlayOperations = false
117+ serviceEpoch = PreferenceManager (context.applicationContext).nextOverlayServiceEpoch()
118+ overlaySequence = 0L
113119 windowGeneration + = 1
114120 if (! cleanupOnMainBlocking()) {
115121 Log .w(TAG , " Timed out while cleaning up previous window state before setup" )
@@ -121,7 +127,9 @@ class SwitchifyAccessibilityWindow private constructor() : LifecycleOwner, Saved
121127 SurfaceControlOverlayBackend (
122128 service = service,
123129 hostTokenProvider = { baseLayout?.windowToken },
124- generationProvider = { windowGeneration }
130+ identityProvider = { target, view ->
131+ nextOverlayIdentity(stableOverlayIdFor(target, view))
132+ }
125133 )
126134 }
127135 createBaseLayout()
@@ -222,15 +230,16 @@ class SwitchifyAccessibilityWindow private constructor() : LifecycleOwner, Saved
222230 )
223231 params.layoutInDisplayCutoutMode =
224232 WindowManager .LayoutParams .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
225- val title = " Switchify:root:generation=$windowGeneration "
233+ val identity = nextOverlayIdentity(" root:default-display" )
234+ val title = " Switchify:${identity.diagnosticId} "
226235 params.setTitle(title)
227236
228237 baseLayout?.let { layout ->
229238 // Add view with minimal overhead - lifecycle owners will be set up after
230239 windowManager?.addView(layout, params)
231240 isVisible = true
232241 rootOverlayId = SwitchifyOverlayDebugRegistry .recordRootShown(
233- generation = windowGeneration ,
242+ identity = identity ,
234243 title = title,
235244 viewId = layout.id,
236245 viewClass = layout.javaClass.name,
@@ -385,11 +394,16 @@ class SwitchifyAccessibilityWindow private constructor() : LifecycleOwner, Saved
385394 postIfCurrentGeneration {
386395 try {
387396 if (shouldUseSurfaceBackend(target)) {
388- val handle = surfaceControlBackend?.attach(target, view, placement)
389- if (handle != null ) {
390- handle.setVisible(isVisible)
391- surfaceOverlayHandles[view] = handle
392- return @postIfCurrentGeneration
397+ if (baseLayout?.windowToken == null ) {
398+ Log .w(TAG , " Surface overlay unavailable for $target because root token is missing" )
399+ if (target is OverlayTarget .Window ) return @postIfCurrentGeneration
400+ } else {
401+ val handle = surfaceControlBackend?.attach(target, view, placement)
402+ if (handle != null ) {
403+ handle.setVisible(isVisible)
404+ surfaceOverlayHandles[view] = handle
405+ return @postIfCurrentGeneration
406+ }
393407 }
394408 if (target is OverlayTarget .Window ) {
395409 Log .w(TAG , " Window overlay unavailable for $target ; skipping fallback" )
@@ -594,6 +608,27 @@ class SwitchifyAccessibilityWindow private constructor() : LifecycleOwner, Saved
594608 }
595609 }
596610
611+ private fun nextOverlayIdentity (stableId : String ): OverlayDebugIdentity {
612+ overlaySequence + = 1L
613+ return OverlayDebugIdentity (
614+ stableId = stableId,
615+ serviceEpoch = serviceEpoch,
616+ generation = windowGeneration,
617+ sequence = overlaySequence
618+ )
619+ }
620+
621+ private fun stableOverlayIdFor (target : OverlayTarget , view : ViewGroup ): String {
622+ val role = when (view.id) {
623+ MenuViewHandler .VIEW_ID -> " menu"
624+ else -> view.javaClass.simpleName.ifEmpty { " view" }
625+ }
626+ return when (target) {
627+ is OverlayTarget .Display -> " surface:display:${target.displayId} :$role "
628+ is OverlayTarget .Window -> " surface:window:${target.displayId} :${target.accessibilityWindowId} :${target.windowType} :$role "
629+ }
630+ }
631+
597632 private fun cleanupOnMainBlocking (): Boolean {
598633 if (Looper .myLooper() == Looper .getMainLooper()) {
599634 cleanupCurrentStateNow()
@@ -660,7 +695,7 @@ class SwitchifyAccessibilityWindow private constructor() : LifecycleOwner, Saved
660695 private class AndroidWindowCleanupRoot (
661696 private val windowManager : WindowManager ? ,
662697 private val layout : RelativeLayout ,
663- override val debugOverlayId : Long ?
698+ override val debugOverlayId : String ?
664699 ) : WindowCleanupRoot {
665700 override val isAttachedToWindow: Boolean
666701 get() = layout.parent != null
0 commit comments