Skip to content

feat: Save report — stats and log in one file, from the app and the debug page (#207) - #208

Merged
compot-dev merged 3 commits into
v2from
feat/save-report-207
Oct 15, 2026
Merged

compot-dev merged 3 commits into
v2from
feat/save-report-207

Conversation

@compot-dev

Copy link
Copy Markdown
Collaborator

Fixes #207

What

One report file with a header (app, version + code, git hash, device, Android, time, uptime), == stats.json == (the same object as /stats.json, pretty-printed) and == log == (the 500-line ring). Built in one place, DebugServer.reportText(); the sources are redacted already and the whole text goes through redact once more.

  1. In the app: long press on Debug in the menu. No new cell, the 8/4/4 grid is unchanged. Touch: long press. Remote: hold OK on Debug (View's own long press for DPAD_CENTER/ENTER), or Menu while Debug has the focus. It writes Download/OpenIPC/report-YYYYMMDD-HHMMSS.txt on a background thread: MediaStore.Downloads on Android 10+ with no permission; Android 6–9 ask for WRITE_EXTERNAL_STORAGE like screenshots and fall back to the app's own external folder if it is refused. Then a toast Report saved: Download/OpenIPC/report-….txt and the Share sheet (ACTION_SEND text/plain; the content URI with FLAG_GRANT_READ_URI_PERMISSION on 10+; on older versions there is no FileProvider, so EXTRA_TEXT cut at 50 000 characters with the file's path). If no app can take it (TV boxes), there is no share sheet; <queries> for SEND text/plain makes that check work on Android 11+.
  2. Debug page: a Save report button next to JSON and Log → /report.txt, Content-Type: text/plain; charset=utf-8, Content-Disposition: attachment; filename="report-YYYYMMDD-HHMMSS.txt", LAN-only like the rest.

The Debug-on toast now says Debug text on · hold Debug to save a report.

Docs: debug.md (endpoints table, Save report, bug reports), usage.md (Debug long press), toys.md (save on the toy's Wi-Fi, send later), security.md.

Tests: report composition (header, sections, redaction of a URL with a password, password=, Authorization), report name and uptime, /report.txt response headers through DebugServer.serve() (the routing now sits in a package-private method; handle() keeps the LAN check and request parsing).

Version 2.11 (36); other open PRs also use 2.11, re-bump at merge.

Device test

  • Phone: long press Debug → file in /sdcard/Download/OpenIPC/, share sheet, no passwords in the file
  • curl -sD- http://<phone>:8085/report.txt headers
  • TV box: hold OK on Debug → file saved, no crash without share targets

…ebug page (#207)

- DebugServer.reportText(): header (app, version, git hash, device, Android,
  time, uptime), /stats.json pretty-printed, the log ring; redacted again as a
  whole. The same text for both ways.
- /report.txt: text/plain, Content-Disposition attachment with
  report-YYYYMMDD-HHMMSS.txt; LAN-only like the other paths. Save report
  button in the page header next to JSON and Log.
- App: long press on Debug (touch, long OK on a remote, or Menu on Debug)
  saves Download/OpenIPC/report-....txt off the UI thread (MediaStore on
  Android 10+, storage permission on 6-9 with the app's own folder as a
  fallback), shows a toast and opens the Share sheet if an app can take it.
- Docs: debug.md (endpoint, Save report, bug reports), usage.md, toys.md,
  security.md. Tests: report composition and redaction, /report.txt headers.
- 2.11 (36).

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

Copy link
Copy Markdown

PR Summary by Qodo

Save a combined debug report from the app or debug page

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Combine device details, stats, and recent logs into one redacted report for bug reports.
• Save and share it from Debug, or download it from the LAN-only debug page.
• Document both workflows, test report content and HTTP headers, and bump the version to 2.11.
Diagram

graph TD
  Menu["Debug menu"] --> Decoder["Decoder activity"] --> Builder["Report builder"] --> Downloads["Saved report"]
  Page["Debug page"] --> Server["Debug server"] --> Builder
  Stats["State snapshot"] --> Builder
  Log["Log ring"] --> Builder
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use FileProvider for pre-Android 10 sharing
  • ➕ Shares the complete saved file instead of truncating report text at 50,000 characters.
  • ➖ Requires provider configuration and URI-access handling for older devices.

Recommendation: Keep the single report builder: it prevents the app and HTTP download from diverging. The current text-sharing fallback avoids extra provider configuration; consider FileProvider if sharing complete reports on older Android versions is important.

Files changed (10) +396 / -32

Enhancement (3) +280 / -24
DebugServer.javaBuild and serve a redacted combined report +97/-22

Build and serve a redacted combined report

• Adds a shared report builder with device, version, time, uptime, pretty-printed stats, and recent logs, followed by whole-report redaction. Exposes it through /report.txt with attachment headers and adds a Save report link to the debug page; extracts request routing for tests.

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

Decoder.javaSave reports off-thread and offer sharing +163/-2

Save reports off-thread and offer sharing

• Saves reports to Downloads through MediaStore on Android 10+, or to shared storage with an app-folder fallback on older versions. Handles storage permission, progress and failure feedback, URI-based sharing where available, and a bounded text-sharing fallback.

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

MenuManager.javaAdd report gestures to the existing Debug item +20/-0

Add report gestures to the existing Debug item

• Connects long press, remote long OK, and Menu-on-Debug to a new save callback without changing the menu grid.

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

Tests (1) +73 / -0
DebugServerTest.javaTest report content, redaction, and downloads +73/-0

Test report content, redaction, and downloads

• Covers report sections and secret redaction, filenames and uptime formatting, /report.txt response headers and length, and the debug-page link.

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

Documentation (4) +33 / -6
debug.mdDocument report contents and both access paths +28/-2

Document report contents and both access paths

• Adds the endpoint and a Save report guide covering file contents, storage behavior, sharing, and bug-report submission.

docs/debug.md

security.mdDocument report redaction and storage permission +2/-1

Document report redaction and storage permission

• Clarifies that reports receive whole-text redaction and that older Android versions use storage permission for reports.

docs/security.md

toys.mdExplain offline report collection on toy Wi-Fi +2/-2

Explain offline report collection on toy Wi-Fi

• Recommends saving a report while connected to a toy and sending it after reconnecting to the internet.

docs/toys.md

usage.mdDescribe the Debug report gesture +1/-1

Describe the Debug report gesture

• Adds touch and remote instructions for saving and sharing a report from the existing Debug menu item.

docs/usage.md

Other (2) +10 / -2
build.gradleBump the app to version 2.11 (36) +2/-2

Bump the app to version 2.11 (36)

• Raises the version name and code for the report release.

android/app/build.gradle

AndroidManifest.xmlDeclare visibility of text-sharing targets +8/-0

Declare visibility of text-sharing targets

• Adds an ACTION_SEND text/plain query so the app can detect available share targets on Android 11 and newer.

android/app/src/main/AndroidManifest.xml

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

qodo-free-for-open-source-projects Bot commented Oct 15, 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. Reports include verbose system logs ✓ Resolved
Description
reportText appends the entire log.text() ring without filtering records by source or severity.
When logcat contains system-library entries below Info, /report.txt and the app-saved report carry
those entries to LAN clients or sharing recipients instead of limiting them to Info and above.
Code

android/app/src/main/java/com/openipc/decoder/DebugServer.java[R262-264]

+        sb.append("\n== log ==\n");
+        sb.append(log != null ? log : "");
+        return redact(sb.toString());
Evidence
The report path newly appends the complete log ring, while LogBuffer.startReader() launches
unrestricted logcat and adds every returned line without checking whether it is app-owned or below
Info. This violates the requirement to preserve app lines but restrict system-library lines to Info
or higher.

Report redaction and log-ring contents
android/app/src/main/java/com/openipc/decoder/DebugServer.java[262-264]
android/app/src/main/java/com/openipc/decoder/LogBuffer.java[44-48]

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

## Issue description
The new report includes the complete log ring, but the log reader does not distinguish the app's lines from system-library lines or exclude system entries below Info. Reports can therefore disclose verbose system logs through both the LAN download and the app sharing flow.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/DebugServer.java[262-264]
- android/app/src/main/java/com/openipc/decoder/LogBuffer.java[44-48]
## Recommended Fix
Filter logcat entries so all app-owned lines remain available while system-library entries below Info are excluded before they enter the report log ring. Apply the same filtering consistently to `/log`, the app-saved report, and `/report.txt`.

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


2. Sharing a report can replace a camera URL ✓ Resolved
Description
shareReport() offers an unrestricted text-sharing chooser that includes the decoder itself, whose
existing incoming-text handler treats EXTRA_TEXT as a camera URL. If a user selects the decoder on
Android 9 or older, the report text is saved in the active camera slot and the app switches to
single-view playback.
Code

android/app/src/main/java/com/openipc/decoder/Decoder.java[2004]

+            startActivity(Intent.createChooser(send, "Share report"));
Evidence
The manifest makes Decoder a text-sharing target. The new older-Android share path puts the report
in EXTRA_TEXT, while the existing receiver persists any EXTRA_TEXT as the active camera's URL.

android/app/src/main/AndroidManifest.xml[47-55]
android/app/src/main/java/com/openipc/decoder/Decoder.java[1985-2004]
android/app/src/main/java/com/openipc/decoder/Decoder.java[622-640]

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

## Issue description
The report chooser can select the decoder itself. On older Android versions, the decoder interprets the shared report text as a camera URL and saves it over the active slot.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/Decoder.java[1985-2004]
- android/app/src/main/java/com/openipc/decoder/Decoder.java[622-640]
## Recommended Fix
Exclude the decoder activity from both the share-target availability check and the chooser, while preserving its ability to receive camera links from other apps.

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



Remediation recommended

3. A second report can erase the first ✓ Resolved
Description
reportName() uses timestamps precise only to the second, and writeFile() opens an existing file
with FileOutputStream, which truncates it. On Android 9 or older, two completed saves within the
same second therefore write to the same path and lose the earlier report.
Code

android/app/src/main/java/com/openipc/decoder/Decoder.java[R1972-1974]

+        File f = new File(dir, name);
+        try (FileOutputStream os = new FileOutputStream(f)) {
+            os.write(data);
Evidence
The filename has second-level precision, saving becomes available again when the preceding save
completes, and the older-Android path writes directly to that filename without an existence check.

android/app/src/main/java/com/openipc/decoder/DebugServer.java[237-240]
android/app/src/main/java/com/openipc/decoder/Decoder.java[1891-1906]
android/app/src/main/java/com/openipc/decoder/Decoder.java[1919-1923]
android/app/src/main/java/com/openipc/decoder/Decoder.java[1970-1976]

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

## Issue description
Two report saves in one second overwrite the same file on older Android versions.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/Decoder.java[1970-1976]
- android/app/src/main/java/com/openipc/decoder/DebugServer.java[237-240]
## Recommended Fix
Choose an unused filename before writing, such as by adding a numeric suffix, and use a non-overwriting file creation path so an existing report cannot be truncated.

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


4. Failed publication can strand a report ✓ Resolved
Description
writeReportToDownloads() clears IS_PENDING outside its cleanup handler and returns the URI
without checking whether resolver.update() changed a row. If publication throws, the inserted item
is not deleted; if the update affects no rows, the app announces a saved report and shares an item
that remains pending.
Code

android/app/src/main/java/com/openipc/decoder/Decoder.java[R1945-1948]

+        values.clear();
+        values.put(MediaStore.Downloads.IS_PENDING, 0);
+        resolver.update(uri, values, null, null);
+        return uri;
Evidence
Cleanup covers stream failures only. The subsequent update is unchecked, while the caller treats
every returned URI as a successfully saved, shareable report.

android/app/src/main/java/com/openipc/decoder/Decoder.java[1935-1948]
android/app/src/main/java/com/openipc/decoder/Decoder.java[1907-1923]

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

## Issue description
A failed MediaStore publication can leave a pending report behind or be reported as a successful save.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/Decoder.java[1935-1948]
## Recommended Fix
Include the pending-state update in the cleanup path, verify that it updates the inserted row, and delete the URI before reporting any publication failure.

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


5. Holding OK to save a report reopens the menu ✓ Resolved
Description
The long-press listener on Debug calls saveReport(), which closes the menu popup while the
OK/Enter key is still held down. View's onKeyDown handles confirm keys only on the first press
(repeatCount == 0), so once the popup is gone the repeat key-downs go to the activity window, and
Decoder.onKeyDown opens the menu on every DPAD_CENTER/ENTER key-down without checking
getRepeatCount(). As a result, a remote user who holds OK to save a report can see the menu open
again right after the save, and keep closing and reopening while the key is held, instead of just
getting the toast and the share sheet.
Code

android/app/src/main/java/com/openipc/decoder/MenuManager.java[R266-269]

+        statsBtn.setOnLongClickListener(v -> {
+            saveReport();
+            return true;
+        });
Evidence
saveReport() (MenuManager 306-310) closes mMenuPopup while OK is still held. Decoder.onKeyDown
(2404-2414) opens the menu on any DPAD_CENTER/ENTER key-down when no popup is showing and never
looks at the repeat count, so the auto-repeat events that follow the long press open the menu again.

android/app/src/main/java/com/openipc/decoder/MenuManager.java[263-274]
android/app/src/main/java/com/openipc/decoder/MenuManager.java[306-310]
android/app/src/main/java/com/openipc/decoder/Decoder.java[2404-2414]

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

## Issue description
Holding OK on Debug fires the long-press listener, which closes the menu popup while the key is still down. The repeat key-down events then go to the Decoder activity, whose onKeyDown opens the menu on every DPAD_CENTER/ENTER key-down, so the menu opens again after the report is saved.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/Decoder.java[2404-2414]
- android/app/src/main/java/com/openipc/decoder/MenuManager.java[263-274]
## Recommended Fix
In Decoder.onKeyDown, for KEYCODE_DPAD_CENTER/KEYCODE_ENTER, return true without toggling the menu when `event.getRepeatCount() > 0`. Toggle the menu only on the first key-down.

ⓘ 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 hide the parts of a finding you never read, like the evidence or the agent prompt

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread android/app/src/main/java/com/openipc/decoder/DebugServer.java
Comment thread android/app/src/main/java/com/openipc/decoder/Decoder.java Outdated
Comment thread android/app/src/main/java/com/openipc/decoder/Decoder.java
Comment thread android/app/src/main/java/com/openipc/decoder/Decoder.java Outdated
Comment thread android/app/src/main/java/com/openipc/decoder/MenuManager.java
…K, MediaStore publish (Qodo on #208)

- The chooser excludes the decoder (EXTRA_EXCLUDE_COMPONENTS, own package
  skipped in the target check), and shared text starting with the report
  title is not taken as a camera link.
- Android 9 and older: a free name (report-...-2.txt) instead of truncating
  an earlier file.
- MediaStore: the IS_PENDING update is checked and inside the cleanup path.
- A held OK toggles the menu once: its repeats no longer reopen the menu
  after a long OK on Debug closed it.
…aries, so chatty graphics logs no longer push them out (#187)
@compot-dev

Copy link
Copy Markdown
Collaborator Author

Qodo's remaining requirement gap (system lines below Info in the report): fixed in b8e944c. That is the same LogBuffer change as on #188 (cherry-picked): logcat now runs with OpenIPCDecoder:V *:I, so the ring, /log, the saved report and /report.txt keep all of the app's own lines and only Info and up from system libraries. Checked on a TECNO KM4: 1 mali_gralloc line out of 238 instead of 498 out of 500.

@compot-dev
compot-dev merged commit 5f1ae28 into v2 Oct 15, 2026
3 checks passed
@compot-dev
compot-dev deleted the feat/save-report-207 branch October 15, 2026 21:34
compot-dev pushed a commit that referenced this pull request Oct 15, 2026
compot-dev pushed a commit that referenced this pull request Oct 15, 2026
…K, MediaStore publish (Qodo on #208)

- The chooser excludes the decoder (EXTRA_EXCLUDE_COMPONENTS, own package
  skipped in the target check), and shared text starting with the report
  title is not taken as a camera link.
- Android 9 and older: a free name (report-...-2.txt) instead of truncating
  an earlier file.
- MediaStore: the IS_PENDING update is checked and inside the cleanup path.
- A held OK toggles the menu once: its repeats no longer reopen the menu
  after a long OK on Debug closed it.
compot-dev added a commit that referenced this pull request Oct 15, 2026
feat: Save report — stats and log in one file, from the app and the debug page (#207)
compot-dev pushed a commit that referenced this pull request Oct 15, 2026
compot-dev pushed a commit that referenced this pull request Oct 15, 2026
…K, MediaStore publish (Qodo on #208)

- The chooser excludes the decoder (EXTRA_EXCLUDE_COMPONENTS, own package
  skipped in the target check), and shared text starting with the report
  title is not taken as a camera link.
- Android 9 and older: a free name (report-...-2.txt) instead of truncating
  an earlier file.
- MediaStore: the IS_PENDING update is checked and inside the cleanup path.
- A held OK toggles the menu once: its repeats no longer reopen the menu
  after a long OK on Debug closed it.
compot-dev added a commit that referenced this pull request Oct 15, 2026
feat: Save report — stats and log in one file, from the app and the debug page (#207)
compot-dev pushed a commit that referenced this pull request Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting device test PR ready, merge only after testing on a real device

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Save report: stats.json and the log as one file, from the app and from the debug page

2 participants