@@ -47,15 +47,24 @@ class SwitchifyAccessibilityWindow private constructor() : LifecycleOwner, Saved
4747 private var isVisible = false
4848 private var windowGeneration = 0
4949 private var acceptingOverlayOperations = false
50+ private val windowStateCleaner = WindowStateCleaner ()
5051
5152 companion object {
5253 private const val TAG = " SwitchifyAccessibilityWindow"
53- private const val SHUTDOWN_CLEANUP_TIMEOUT_MS = 500L
54+ private const val SHUTDOWN_CLEANUP_TIMEOUT_MS = 2_000L
5455 val instance: SwitchifyAccessibilityWindow by lazy {
5556 SwitchifyAccessibilityWindow ()
5657 }
5758 }
5859
60+ private data class WindowState (
61+ val generation : Int ,
62+ val windowManager : WindowManager ? ,
63+ val baseLayout : RelativeLayout ? ,
64+ val surfaceHandles : MutableMap <ViewGroup , OverlayHandle >,
65+ val wasVisible : Boolean
66+ )
67+
5968 private val defaultDisplayTarget = OverlayTargets .defaultDisplay().copy(forceSurface = true )
6069
6170 override val lifecycle: Lifecycle
@@ -72,7 +81,9 @@ class SwitchifyAccessibilityWindow private constructor() : LifecycleOwner, Saved
7281 try {
7382 acceptingOverlayOperations = false
7483 windowGeneration + = 1
75- cleanupOnMainBlocking()
84+ if (! cleanupOnMainBlocking()) {
85+ Log .w(TAG , " Timed out while cleaning up previous window state before setup" )
86+ }
7687
7788 this .context = context
7889 windowManager = context.getSystemService(Context .WINDOW_SERVICE ) as WindowManager
@@ -215,22 +226,25 @@ class SwitchifyAccessibilityWindow private constructor() : LifecycleOwner, Saved
215226 */
216227 fun cleanup () {
217228 if (Looper .myLooper() == Looper .getMainLooper()) {
218- if (acceptingOverlayOperations) cleanupNow ()
229+ cleanupCurrentStateNow ()
219230 return
220231 }
221- postIfCurrentGeneration { cleanupNow () }
232+ mainHandler.post { cleanupCurrentStateNow () }
222233 }
223234
224235 /* *
225236 * Cleans up the window and its resources when the service is destroyed.
226237 */
227238 fun onServiceDestroy () {
228239 acceptingOverlayOperations = false
240+ windowGeneration + = 1
229241 ServiceMessageHUD .instance.dispose()
230242 MenuHighlightHud .instance.dispose()
231243 ServiceStartupSplash .instance.dispose()
232244 MediaPipeBackend .close()
233- cleanupForServiceShutdown()
245+ if (! cleanupOnMainBlocking()) {
246+ Log .w(TAG , " Timed out waiting for service window cleanup" )
247+ }
234248 getContext()?.let { ctx ->
235249 screenWatcher?.unregister(ctx)
236250 }
@@ -239,6 +253,8 @@ class SwitchifyAccessibilityWindow private constructor() : LifecycleOwner, Saved
239253 windowManager = null
240254 surfaceControlBackend = null
241255 baseLayout = null
256+ surfaceOverlayHandles.clear()
257+ isVisible = false
242258 }
243259
244260 /* *
@@ -527,62 +543,86 @@ class SwitchifyAccessibilityWindow private constructor() : LifecycleOwner, Saved
527543 }
528544 }
529545
530- private fun cleanupForServiceShutdown () {
531- acceptingOverlayOperations = false
532- cleanupOnMainBlocking()
533- }
534-
535- private fun cleanupOnMainBlocking () {
546+ private fun cleanupOnMainBlocking (): Boolean {
536547 if (Looper .myLooper() == Looper .getMainLooper()) {
537- cleanupNow ()
538- return
548+ cleanupCurrentStateNow ()
549+ return true
539550 }
551+ val state = captureCurrentWindowState()
540552 val latch = CountDownLatch (1 )
541553 mainHandler.postAtFrontOfQueue {
542554 try {
543- cleanupNow( )
555+ cleanupSnapshotNow(state )
544556 } finally {
545557 latch.countDown()
546558 }
547559 }
548- if (! latch.await(SHUTDOWN_CLEANUP_TIMEOUT_MS , TimeUnit .MILLISECONDS )) {
549- Log .w(TAG , " Timed out waiting for window cleanup" )
560+ return latch.await(SHUTDOWN_CLEANUP_TIMEOUT_MS , TimeUnit .MILLISECONDS ).also { completed ->
561+ if (! completed) {
562+ Log .w(TAG , " Timed out waiting for window cleanup" )
563+ }
550564 }
551565 }
552566
553- private fun cleanupNow () {
554- try {
555- releaseSurfaceOverlays()
556- for (i in 0 until (baseLayout?.childCount ? : 0 )) {
557- val child = baseLayout?.getChildAt(i)
567+ private fun cleanupCurrentStateNow () {
568+ cleanupSnapshotNow(captureCurrentWindowState())
569+ }
570+
571+ private fun captureCurrentWindowState (): WindowState {
572+ val state = WindowState (
573+ generation = windowGeneration,
574+ windowManager = windowManager,
575+ baseLayout = baseLayout,
576+ surfaceHandles = surfaceOverlayHandles.toMutableMap(),
577+ wasVisible = isVisible
578+ )
579+ surfaceOverlayHandles.clear()
580+ isVisible = false
581+ return state
582+ }
583+
584+ private fun cleanupSnapshotNow (state : WindowState ) {
585+ windowStateCleaner.cleanup(
586+ WindowCleanupState (
587+ surfaceHandles = state.surfaceHandles.mapKeys { it.key as Any }
588+ .mapValues { OverlayHandleCleanupHandle (it.value) }
589+ .toMutableMap(),
590+ root = state.baseLayout?.let { layout ->
591+ AndroidWindowCleanupRoot (state.windowManager, layout)
592+ },
593+ wasVisible = state.wasVisible
594+ )
595+ )
596+ }
597+
598+ private class OverlayHandleCleanupHandle (
599+ private val handle : OverlayHandle
600+ ) : WindowCleanupHandle {
601+ override fun release () {
602+ handle.release()
603+ }
604+ }
605+
606+ private class AndroidWindowCleanupRoot (
607+ private val windowManager : WindowManager ? ,
608+ private val layout : RelativeLayout
609+ ) : WindowCleanupRoot {
610+ override val isAttachedToWindow: Boolean
611+ get() = layout.parent != null
612+
613+ override fun removeDescendantViews () {
614+ for (i in 0 until layout.childCount) {
615+ val child = layout.getChildAt(i)
558616 if (child is ViewGroup ) {
559617 child.removeAllViews()
560618 }
561619 }
562- baseLayout?.removeAllViews()
563- baseLayout?.let { layout ->
564- if (isVisible || layout.parent != null ) {
565- windowManager?.removeViewImmediate(layout)
566- }
567- }
568- isVisible = false
569- } catch (e: IllegalArgumentException ) {
570- isVisible = false
571- Log .w(TAG , " Window was already removed during cleanup" , e)
572- } catch (e: Exception ) {
573- Log .e(TAG , " Error in cleanup: ${e.message} " , e)
620+ layout.removeAllViews()
574621 }
575- }
576622
577- private fun releaseSurfaceOverlays () {
578- surfaceOverlayHandles.values.forEach { handle ->
579- try {
580- handle.release()
581- } catch (e: Exception ) {
582- Log .e(TAG , " Error releasing surface overlay" , e)
583- }
623+ override fun removeImmediately () {
624+ windowManager?.removeViewImmediate(layout)
584625 }
585- surfaceOverlayHandles.clear()
586626 }
587627
588628 private fun layoutParamsForPlacement (placement : OverlayPlacement ): RelativeLayout .LayoutParams {
0 commit comments