Skip to content

fix: screenshot permission (#28), back handling (#30), cleanup (#35) - #50

Merged
compot-dev merged 2 commits into
v2from
fix/platform-cleanup
Oct 12, 2026
Merged

compot-dev merged 2 commits into
v2from
fix/platform-cleanup

Conversation

@compot-dev

Copy link
Copy Markdown
Collaborator

Fixes #28, fixes #30, fixes #35.

⚠️ Do not merge before testing on a device (label awaiting device test).

Issue Problem Fix
#28 On Android 6–9 screenshots were not saved: WRITE_EXTERNAL_STORAGE was never requested at runtime The permission is requested on the first long press, and the screenshot is taken once it is granted. A refusal shows a message
#30 Android 16 (targetSdk 36): back gestures no longer arrive as KEYCODE_BACK; lint reported GestureBackNavigation The activity no longer intercepts Back. The menu (a focusable PopupWindow) and the dialogs handle it themselves, including predictive back gestures via their own OnBackInvokedDispatcher. enableOnBackInvokedCallback="true" gives Android 13–15 the same behaviour
#35 Small items See below

#35 checklist

Lint

The baseline is refreshed and contains no errors, only warnings: GestureBackNavigation and SuspiciousIndentation are gone.

Tests

FramePoolTest is new: capacity rounding, reuse, size-class separation, bounded large class. All JVM tests pass; assembleDebug and lintDebug pass.

Device checklist

@compot-dev compot-dev added the awaiting device test PR ready, merge only after testing on a real device label Oct 12, 2026
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix screenshot permissions and Back handling; improve frame reuse

🐞 Bug fix ✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Request screenshot storage permission on Android 6–9 and save PNGs off the UI thread.
• Let popups and dialogs handle Back, including predictive back gestures.
• Improve key-frame buffer reuse and 4K NAL capacity; refresh tests and lint baseline.
Diagram

graph TD
  P["Storage Permission"] --> D["Decoder Activity"] --> M["MediaStore"]
  D --> U["Popup and Dialogs"]
  D --> N["NAL Assembler"] --> F["Frame Pool"]
  N --> C["MediaCodec Manager"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Pool by exact rounded capacity
  • ➕ Avoids polling and discarding an undersized frame within a broad size class.
  • ➖ Requires more queues and bookkeeping for capacities that may see little reuse.

Recommendation: Keep the two-class pool as a proportionate fix for small packets competing with key-frame buffers. The rounded-capacity alternative is worth revisiting only if profiling shows substantial allocation or discard within either class; complete the requested device tests before merging.

Files changed (7) +140 / -44

Enhancement (1) +32 / -10
FramePool.javaSeparate and bound frame-buffer size classes +32/-10

Separate and bound frame-buffer size classes

• Splits recycled frames into small and bounded large queues. Rounds new capacities for reuse so small requests do not consume key-frame buffers.

android/app/src/main/java/com/openipc/decoder/FramePool.java

Bug fix (3) +46 / -8
AndroidManifest.xmlEnable platform Back callbacks +5/-2

Enable platform Back callbacks

• Enables OnBackInvokedCallback behavior for the application and adds a tools API annotation for the manifest attribute.

android/app/src/main/AndroidManifest.xml

Decoder.javaGate screenshots on permission and stop intercepting Back +36/-5

Gate screenshots on permission and stop intercepting Back

• Requests storage permission before screenshots on Android 6–9, resumes after a grant, and encodes and saves PNGs on a worker thread. Removes KEYCODE_BACK interception while retaining Escape handling, and uses the shared 2 MiB NAL limit.

android/app/src/main/java/com/openipc/decoder/Decoder.java

MediaCodecManager.javaMatch codec input capacity to 2 MiB NAL limit +5/-1

Match codec input capacity to 2 MiB NAL limit

• Defines a shared maximum NAL size and raises the MediaCodec input-size hint to accommodate larger key frames.

android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java

Tests (1) +59 / -0
FramePoolTest.javaTest frame capacity, reuse, and pool bounds +59/-0

Test frame capacity, reuse, and pool bounds

• Adds JVM tests for capacity rounding, small-frame reuse, separation from large frames, and bounded large-frame retention.

android/app/src/test/java/com/openipc/decoder/FramePoolTest.java

Other (2) +3 / -26
lint-baseline.xmlRefresh resolved lint findings +2/-24

Refresh resolved lint findings

• Removes the Back-navigation and unused-attribute baseline entries and adjusts manifest line references. The remaining entries are warnings.

android/app/lint-baseline.xml

proguard-rules.proRemove obsolete Frame keep rule +1/-2

Remove obsolete Frame keep rule

• Deletes the keep rule for the no-longer-existing Decoder$Frame class.

android/app/proguard-rules.pro

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Pooled buffers disappear on misses ✓ Resolved
Description
FramePool.obtain() drops the polled frame whenever its capacity is below size and immediately
allocates a replacement. When requests of different rounded capacities share a queue, a smaller
entry can be removed before a suitable later entry, causing avoidable allocations and reducing
reuse.
Code

android/app/src/main/java/com/openipc/decoder/FramePool.java[R34-36]

+        Frame frame = (isSmall ? small : large).poll();
     if (frame != null && frame.data().length >= size) {
         frame.setLength(0);
Evidence
The selected queue is polled once, and an undersized frame is neither returned nor searched around
before a new allocation is made. capacityFor() creates multiple capacities within both size
classes, so mismatched entries can coexist and be discarded by this path, violating the requirement
to reuse suitable buffers without discarding mismatched ones.

Separate and bound FramePool buffer reuse
android/app/src/main/java/com/openipc/decoder/FramePool.java[33-40]
android/app/src/main/java/com/openipc/decoder/FramePool.java[54-60]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`FramePool.obtain()` removes one frame from the selected queue and permanently discards it when its capacity is too small for the request. Different rounded capacities coexist in each queue, so this loses reusable buffers and causes unnecessary allocations.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/FramePool.java[33-40]
## Recommended Fix
When the polled frame is undersized, return it to the appropriate queue or search the queue for a suitable frame without discarding mismatched entries. Preserve the existing small/large separation and capacity bounds, and add a regression test covering a smaller buffer encountered before a suitable larger buffer.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can commit Qodo's fix in one click with committable suggestions (GitHub & GitLab)

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread android/app/src/main/java/com/openipc/decoder/FramePool.java Outdated
- Screenshots on Android 6-9 request WRITE_EXTERNAL_STORAGE first and
  continue once it is granted; the PNG is encoded and saved on a
  background thread instead of the UI thread (fixes #28).
- Back is no longer intercepted in onKeyDown(): the menu PopupWindow and
  the dialogs handle it themselves, including predictive back gestures;
  enableOnBackInvokedCallback opts Android 13-15 into the same behaviour
  (fixes #30).
- Cleanup (fixes #35):
  - removed the ProGuard rule for the no longer existing Decoder$Frame;
  - FramePool keeps small and large frames in separate, bounded classes
    with rounded capacities, so small requests no longer take key-frame
    buffers and key frames reuse them;
  - the single-view NAL buffer and KEY_MAX_INPUT_SIZE are both 2 MiB, so
    4K key frames fit;
  - (the 5-thread pool and the suspicious indentation went away with
    RtspSession, #45.)
- Lint baseline refreshed: no errors left in it.
obtain() looks at each pooled frame of the size class at most once and
puts undersized ones back, so a smaller buffer in front of a suitable
one no longer causes an allocation and a lost buffer.
@compot-dev
compot-dev force-pushed the fix/platform-cleanup branch from cc60036 to fb6057d Compare October 12, 2026 15:00
@compot-dev

Copy link
Copy Markdown
Collaborator Author

Device-tested (#53): the screenshot permission is requested and screenshots are saved; Back exits correctly. FramePool keeps undersized frames (unit tests). Rebased onto the current v2, CI is green.

@compot-dev compot-dev removed the awaiting device test PR ready, merge only after testing on a real device label Oct 12, 2026
@compot-dev
compot-dev merged commit 55f07ff into v2 Oct 12, 2026
4 checks passed
@compot-dev
compot-dev deleted the fix/platform-cleanup branch October 13, 2026 17:16
compot-dev added a commit that referenced this pull request Oct 15, 2026
fix: screenshot permission (#28), back handling (#30), cleanup (#35)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant