Skip to content

Commit e8af266

Browse files
romtsnclaude
andcommitted
fix(replay): Detect window size changes on activities with configChanges
Activities that declare android:configChanges="orientation|screenSize|..." (e.g. Unity, fullscreen video players) keep the same root view across rotations, so onRootViewsChanged never fires and determineWindowSize was never re-invoked. The recording bitmap stayed at the pre-rotation size, the rotated window content rendered into wrong-dim bitmaps, and SurfaceView captures composited at stale coordinates. Attach an OnLayoutChangeListener to each tracked root so a same-root resize triggers determineWindowSize. The existing size-comparison guard (both width and height must differ) keeps IME/adjustResize relayouts from causing spurious reconfigurations. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d667d6a commit e8af266

1 file changed

Lines changed: 47 additions & 1 deletion

File tree

sentry-android-replay/src/main/java/io/sentry/android/replay/WindowRecorder.kt

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import io.sentry.android.replay.util.hasSize
1717
import io.sentry.android.replay.util.removeOnPreDrawListenerSafe
1818
import io.sentry.util.AutoClosableReentrantLock
1919
import java.lang.ref.WeakReference
20+
import java.util.WeakHashMap
2021
import java.util.concurrent.ScheduledExecutorService
2122
import java.util.concurrent.atomic.AtomicBoolean
2223

@@ -33,6 +34,7 @@ internal class WindowRecorder(
3334
private val isRecording = AtomicBoolean(false)
3435
private val rootViews = ArrayList<WeakReference<View>>()
3536
private var lastKnownWindowSize: Point = Point()
37+
private val rootLayoutListeners = WeakHashMap<View, View.OnLayoutChangeListener>()
3638
private val rootViewsLock = AutoClosableReentrantLock()
3739
private val capturerLock = AutoClosableReentrantLock()
3840
private val backgroundProcessingHandlerLock = AutoClosableReentrantLock()
@@ -124,21 +126,59 @@ internal class WindowRecorder(
124126
rootViews.add(WeakReference(root))
125127
capturer?.recorder?.bind(root)
126128
determineWindowSize(root)
129+
attachLayoutListener(root)
127130
} else {
131+
detachLayoutListener(root)
128132
capturer?.recorder?.unbind(root)
129133
rootViews.removeAll { it.get() == root }
130134

131135
val newRoot = rootViews.lastOrNull()?.get()
132136
if (newRoot != null && root != newRoot) {
133137
capturer?.recorder?.bind(newRoot)
134138
determineWindowSize(newRoot)
139+
attachLayoutListener(newRoot)
135140
} else {
136141
Unit // synchronized block wants us to return something lol
137142
}
138143
}
139144
}
140145
}
141146

147+
/**
148+
* Activities that handle their own configuration changes (e.g. Unity, video players via
149+
* `android:configChanges="orientation|screenSize|..."`) keep the same root view across rotations,
150+
* so [onRootViewsChanged] never fires and [determineWindowSize] would never re-detect the new
151+
* dimensions. Watch the root for layout-time size changes to catch these cases.
152+
*/
153+
private fun attachLayoutListener(root: View) {
154+
if (rootLayoutListeners.containsKey(root)) return
155+
val listener =
156+
View.OnLayoutChangeListener {
157+
v,
158+
left,
159+
top,
160+
right,
161+
bottom,
162+
oldLeft,
163+
oldTop,
164+
oldRight,
165+
oldBottom ->
166+
val width = right - left
167+
val height = bottom - top
168+
val oldWidth = oldRight - oldLeft
169+
val oldHeight = oldBottom - oldTop
170+
if (width != oldWidth || height != oldHeight) {
171+
determineWindowSize(v)
172+
}
173+
}
174+
rootLayoutListeners[root] = listener
175+
root.addOnLayoutChangeListener(listener)
176+
}
177+
178+
private fun detachLayoutListener(root: View) {
179+
rootLayoutListeners.remove(root)?.let { root.removeOnLayoutChangeListener(it) }
180+
}
181+
142182
fun determineWindowSize(root: View) {
143183
if (root.hasSize()) {
144184
if (root.width != lastKnownWindowSize.x && root.height != lastKnownWindowSize.y) {
@@ -222,7 +262,13 @@ internal class WindowRecorder(
222262
override fun reset() {
223263
lastKnownWindowSize.set(0, 0)
224264
rootViewsLock.acquire().use {
225-
rootViews.forEach { capturer?.recorder?.unbind(it.get()) }
265+
rootViews.forEach {
266+
val root = it.get()
267+
if (root != null) {
268+
detachLayoutListener(root)
269+
capturer?.recorder?.unbind(root)
270+
}
271+
}
226272
rootViews.clear()
227273
}
228274
}

0 commit comments

Comments
 (0)