Skip to content

feat(replay): make screenshot optimizations opt-in - #761

Open
dustinbyrne wants to merge 1 commit into
perf/replay-pixel-copy-bufferfrom
feat/opt-in-screenshot-optimizations
Open

feat(replay): make screenshot optimizations opt-in#761
dustinbyrne wants to merge 1 commit into
perf/replay-pixel-copy-bufferfrom
feat/opt-in-screenshot-optimizations

Conversation

@dustinbyrne

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

Stacked on #756. This follow-up must land before the screenshot optimization is released so existing users retain the current capture defaults.

Adds experimental sessionReplayConfig.optimizeScreenshots, defaulting to false:

  • Default: a new full-resolution ARGB_8888 bitmap per screenshot, unchanged mask coordinates, and later capture attempts remain possible while a timed-out copy is pending.
  • Opt-in: perf(replay): reduce PixelCopy screenshot overhead #756's reusable half-width/height RGB_565 bitmap and existing ARGB_8888 fallback. This reduces image detail and removes alpha; transparent window regions appear black. A pending timed-out copy holds the pooled bitmap until its callback arrives.

The option is sampled once per capture, including masking and bitmap release. Both modes safely reclaim late bitmaps and discard captures with non-positive source dimensions. The existing screenshot default and all constructor signatures are unchanged.

The follow-up also removes redundant casts and updates the changeset to describe the opt-in behavior and minor API addition.

💚 How did you test it?

  • make test: 355 Android tests and 9 Compose tests passed; 3 existing skips.
  • make testJava: 942 core tests passed.
  • make checkFormat, make api, ./gradlew apiCheck, and git diff --check passed.
  • Native Robolectric graphics with WebP encode/decode verifies transparency and mask placement in both modes, including an option change during capture. PixelCopy itself is shadowed; no physical-device testing is claimed.
  • Regression tests cover fresh allocation by default, opt-in reuse/fallback, timeout recovery, both option-switch directions while a copy is pending, late callbacks after uninstall, and invalid-dimension recovery.
  • A Java consumer compiled against the old config constructors runs unchanged with the new classes. A new Java consumer verifies the option defaults off and does not enable screenshot capture.

📝 Checklist

  • I reviewed the submitted code and completed a simplify pass.
  • I added tests to verify the changes.
  • I updated the configuration documentation and changeset.
  • Existing constructor signatures and screenshot enablement defaults are preserved.

🤖 Agent context

Autonomy: Human-driven (agent-assisted).

Implemented and self-reviewed with Pi, Git/GitHub CLI, Gradle, Robolectric, and Java consumer checks. Kept one shared capture/completion pipeline with capture-local mode selection rather than duplicating the screenshot implementation. Human review is required; native/OEM PixelCopy behavior remains outside the local test coverage.

@dustinbyrne
dustinbyrne requested a review from a team as a code owner September 4, 2026 21:25
@dustinbyrne dustinbyrne self-assigned this Sep 4, 2026
bitmap = bitmapLease.bitmap
} else {
bitmapLease = null
bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low: Unbounded bitmap retention after PixelCopy timeouts

When optimizations are disabled, every capture allocates a new full-resolution bitmap. A timed-out request retains that bitmap until its callback arrives, but unlike the lease-backed path, it does not prevent subsequent captures from allocating more; repeated UI redraws during stalled callbacks can therefore exhaust the host app's memory. Keep full-resolution ARGB_8888 capture while applying equivalent single-request backpressure or another explicit bound to outstanding bitmaps.

@veria-ai

veria-ai Bot commented Sep 4, 2026

Copy link
Copy Markdown

PR overview

This PR makes screenshot optimizations opt-in for Android session replay, using full-resolution bitmap capture when optimizations are disabled.

One issue remains: stalled PixelCopy callbacks can allow full-resolution bitmaps to accumulate during repeated UI redraws, potentially exhausting the host app’s memory. The impact depends on timeout conditions and sustained redraw activity, and no reported issues have yet been addressed.

Open issues (1)

Fixed/addressed: 0 · PR risk: 4/10

@greptile-apps

greptile-apps Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
### Issue 1
posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt:1715
**Pending bitmaps can leak**

If a default-mode PixelCopy times out, its fresh full-resolution bitmap is left for the callback to recycle. If `uninstall()` quits the callback thread before that callback is delivered, the bitmap is not recycled and is not tracked by `pixelCopyBitmapBuffer.close()`. Multiple delayed captures can therefore retain large ARGB_8888 bitmaps without deterministic cleanup. Please retain ownership of pending default-mode bitmaps so uninstall can release them explicitly.

### Issue 2
posthog-android/src/test/java/com/posthog/android/replay/PostHogReplayIntegrationTest.kt:1785
**Cases are not parameterised**

These new tests loop over multiple cases inside ordinary tests, which violates the repository directive to prefer parameterised tests. A failure in one iteration prevents later cases from running and reports all inputs under one result. Please parameterise the optimization modes and dimension pairs before merging. The same pattern appears in the late-callback, transparency, mask-scaling, and invalid-dimension tests.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "feat(replay): make screenshot optimizati..." | Re-trigger Greptile

bitmap = bitmapLease.bitmap
} else {
bitmapLease = null
bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Pending bitmaps can leak

If a default-mode PixelCopy times out, its fresh full-resolution bitmap is left for the callback to recycle. If uninstall() quits the callback thread before that callback is delivered, the bitmap is not recycled and is not tracked by pixelCopyBitmapBuffer.close(). Multiple delayed captures can therefore retain large ARGB_8888 bitmaps without deterministic cleanup. Please retain ownership of pending default-mode bitmaps so uninstall can release them explicitly.

Knowledge Base Used: Android session replay

Prompt To Fix With AI
This is a comment left during a code review.
Path: posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt
Line: 1715

Comment:
**Pending bitmaps can leak**

If a default-mode PixelCopy times out, its fresh full-resolution bitmap is left for the callback to recycle. If `uninstall()` quits the callback thread before that callback is delivered, the bitmap is not recycled and is not tracked by `pixelCopyBitmapBuffer.close()`. Multiple delayed captures can therefore retain large ARGB_8888 bitmaps without deterministic cleanup. Please retain ownership of pending default-mode bitmaps so uninstall can release them explicitly.

**Knowledge Base Used:** [Android session replay](https://app.greptile.com/posthog-org-19734/-/custom-context/knowledge-base/posthog/posthog-android/-/docs/android-session-replay.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@Test
@Config(sdk = [26], shadows = [RecordingShadowPixelCopy::class])
fun `screenshot optimization can change while a previous capture is pending`() {
for (initiallyOptimized in listOf(false, true)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Cases are not parameterised

These new tests loop over multiple cases inside ordinary tests, which violates the repository directive to prefer parameterised tests. A failure in one iteration prevents later cases from running and reports all inputs under one result. Please parameterise the optimization modes and dimension pairs before merging. The same pattern appears in the late-callback, transparency, mask-scaling, and invalid-dimension tests.

Context Used: Do not attempt to comment on incorrect alphabetica... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: posthog-android/src/test/java/com/posthog/android/replay/PostHogReplayIntegrationTest.kt
Line: 1785

Comment:
**Cases are not parameterised**

These new tests loop over multiple cases inside ordinary tests, which violates the repository directive to prefer parameterised tests. A failure in one iteration prevents later cases from running and reports all inputs under one result. Please parameterise the optimization modes and dimension pairs before merging. The same pattern appears in the late-callback, transparency, mask-scaling, and invalid-dimension tests.

**Context Used:** Do not attempt to comment on incorrect alphabetica... ([source](https://app.greptile.com/review/custom-context?memory=instruction-0))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant