Skip to content

ADFA-3050 | Fix crash when resolving file name from revoked URI#1006

Merged
jatezzz merged 4 commits intostagefrom
fix/ADFA-3050-uri-get-filename-crash
Mar 2, 2026
Merged

ADFA-3050 | Fix crash when resolving file name from revoked URI#1006
jatezzz merged 4 commits intostagefrom
fix/ADFA-3050-uri-get-filename-crash

Conversation

@jatezzz
Copy link
Collaborator

@jatezzz jatezzz commented Feb 23, 2026

Description

Fixed a fatal crash that occurred when the app attempted to retrieve a file name using a content:// URI after its read permissions had expired or the file had been deleted/moved by the user. The contentResolver.query is now wrapped in a try-catch block to safely handle SecurityException and other unexpected exceptions. Additionally, the fallback mechanism was improved to return "Unknown File" for unreadable content URIs and to properly decode URL-encoded paths for standard URIs.

Details

Added warning logs to capture SecurityException and unexpected errors without interrupting the user flow. The UI will now gracefully display "Unknown File" or the decoded path name instead of causing an application crash.

Screen.Recording.2026-02-23.at.3.17.38.PM.mov
Screenshot 2026-02-23 at 3 32 37 PM

Ticket

ADFA-3050

Observation

This fix acts as a defensive measure to handle cases where the OS cleans up memory or the user modifies the local LLM model file externally while the app is in the background.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough
  • Fixed: Crash when resolving file names from revoked or inaccessible URIs. The app no longer crashes when attempting to retrieve a file name from a content:// URI after read permissions have expired or the file was deleted/moved; contentResolver.query() is now wrapped in safe error handling with fallbacks.

  • Changed: Content URIs that cannot be read due to SecurityException or other failures now return "Unknown File" and log a warning instead of crashing.

  • Changed: Non-content URIs now decode the last path segment (using Uri.decode) with fallbacks to the original segment or "Unknown File" for improved readability and resilience.

  • Changed: Added warning-level logging for SecurityException and unexpected exceptions during URI file name resolution to aid troubleshooting without interrupting user flow.

  • Technical details:

    • File modified: common/src/main/java/com/itsaky/androidide/utils/UriExtensions.kt
    • Added logging (android.util.Log) and improved decode/fallback logic for Uri.getFileName(context: Context): String
    • No public API/signature changes.
  • ⚠️ Risk / Best-practice considerations:

    • Broad exception handling: catching SecurityException and a general Exception avoids crashes but may mask other issues; monitor warning logs and investigate recurring exceptions.
    • Silent fallback UX: returning "Unknown File" may hide details from users—consider adding richer UI messaging or telemetry if users encounter frequent unknown-file fallbacks.

Walkthrough

Adds safer filename resolution for URIs: content-provider queries are wrapped in try/catch (SecurityException and Exception) with warnings; returns "Unknown File" when DISPLAY_NAME is missing; non-content URI last path segments are URL-decoded with a fallback.

Changes

Cohort / File(s) Summary
URI Extension Enhancements
common/src/main/java/com/itsaky/androidide/utils/UriExtensions.kt
Wrap content-URI queries in try/catch for SecurityException and general Exception with warning Log calls; return explicit "Unknown File" when DISPLAY_NAME is unavailable; URL-decode non-content URI last path segment with fallback; add android.util.Log import and decoding fallback logic.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested reviewers

  • itsaky-adfa
  • jomen-adfa

Poem

🐇 I hop through URIs, sniffing each name,
I catch the slip, I whisper "Unknown File" tame.
I decode the curl of a last-path thread,
A tiny patch, so filenames go ahead.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically describes the main change: fixing a crash when resolving file names from revoked URIs, which directly matches the changeset's primary objective.
Description check ✅ Passed The PR description is comprehensive and directly related to the changeset, explaining the crash fix, exception handling improvements, and fallback mechanism enhancements with supporting evidence.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/ADFA-3050-uri-get-filename-crash

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
common/src/main/java/com/itsaky/androidide/utils/UriExtensions.kt (1)

12-12: Use a specific projection to avoid fetching unnecessary columns.

Passing null as the projection fetches every column from the content provider. Since only DISPLAY_NAME is read, this wastes memory and cursor-row bandwidth for providers that vend many columns.

♻️ Proposed refactor
-            context.contentResolver.query(this, null, null, null, null)?.use { cursor ->
+            context.contentResolver.query(this, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursor ->
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@common/src/main/java/com/itsaky/androidide/utils/UriExtensions.kt` at line
12, The query call in UriExtensions.kt uses a null projection and thus fetches
all columns; change the projection to only request the display name (e.g., use
arrayOf(OpenableColumns.DISPLAY_NAME) or
arrayOf(MediaStore.MediaColumns.DISPLAY_NAME) as appropriate) when calling
context.contentResolver.query(this, ...), keep the rest of the call and cursor
handling the same, and update any column index lookup to reference
OpenableColumns.DISPLAY_NAME to avoid loading unnecessary columns.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@common/src/main/java/com/itsaky/androidide/utils/UriExtensions.kt`:
- Around line 14-17: The current code assumes cursor.getString(nameIndex) is
non-null; instead guard against SQL NULLs by checking cursor.isNull(nameIndex)
(or handling a null result) before returning the string for
OpenableColumns.DISPLAY_NAME. Update the logic around
getColumnIndex/OpenableColumns.DISPLAY_NAME to only return
cursor.getString(nameIndex) when the column is not null (or safely map a null to
a fallback/empty value), ensuring callers won't receive a typed-null and cause
an NPE.
- Around line 20-23: The warning logs in the catch blocks of the UriExtensions
code currently log the full URI (Log.w("UriExtensions", "… $this", e)), which
can leak sensitive path segments; update both catch blocks (the
SecurityException and generic Exception handlers in the function that reads a
Uri) to redact path data by logging only the URI's scheme and authority (e.g.,
use uri.scheme and uri.authority or uri.host/port where appropriate) combined
with a short descriptive message and the exception object so diagnostics remain
useful but full paths are not emitted.

---

Nitpick comments:
In `@common/src/main/java/com/itsaky/androidide/utils/UriExtensions.kt`:
- Line 12: The query call in UriExtensions.kt uses a null projection and thus
fetches all columns; change the projection to only request the display name
(e.g., use arrayOf(OpenableColumns.DISPLAY_NAME) or
arrayOf(MediaStore.MediaColumns.DISPLAY_NAME) as appropriate) when calling
context.contentResolver.query(this, ...), keep the rest of the call and cursor
handling the same, and update any column index lookup to reference
OpenableColumns.DISPLAY_NAME to avoid loading unnecessary columns.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 081a638 and b98c653.

📒 Files selected for processing (1)
  • common/src/main/java/com/itsaky/androidide/utils/UriExtensions.kt

@jatezzz jatezzz force-pushed the fix/ADFA-3050-uri-get-filename-crash branch from b98c653 to e7d4792 Compare February 26, 2026 14:36
@jatezzz jatezzz requested a review from a team February 26, 2026 14:36
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
common/src/main/java/com/itsaky/androidide/utils/UriExtensions.kt (1)

13-13: Narrow the query projection to only DISPLAY_NAME.

Line 13 requests all columns with projection = null, but the method only uses OpenableColumns.DISPLAY_NAME (line 15). Specify only the needed column to reduce provider work and memory overhead, which aligns with Android's documented performance best practices for ContentResolver queries.

♻️ Proposed refactor
-            context.contentResolver.query(this, null, null, null, null)?.use { cursor ->
+            context.contentResolver.query(
+                this,
+                arrayOf(OpenableColumns.DISPLAY_NAME),
+                null,
+                null,
+                null
+            )?.use { cursor ->
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@common/src/main/java/com/itsaky/androidide/utils/UriExtensions.kt` at line
13, The ContentResolver query currently passes projection = null and fetches
only OpenableColumns.DISPLAY_NAME; change the query in the UriExtensions code
(the call starting with context.contentResolver.query(this, ...)) to specify a
projection array containing only OpenableColumns.DISPLAY_NAME (e.g.,
arrayOf(OpenableColumns.DISPLAY_NAME)) so the provider returns only that column
and reduces memory/work; keep the existing cursor usage (cursor.moveToFirst()
and cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@common/src/main/java/com/itsaky/androidide/utils/UriExtensions.kt`:
- Around line 30-33: The code decodes a URI path segment using URLDecoder.decode
which treats '+' as space; change it to use Uri.decode to only decode
percent-escapes and preserve '+': replace the URLDecoder.decode(fallbackName,
"UTF-8") call with android.net.Uri.decode(fallbackName) in the same block that
returns the decoded fallbackName (symbols: fallbackName, unknownFile in
UriExtensions.kt) so filenames like "C++Notes.txt" remain intact.

---

Nitpick comments:
In `@common/src/main/java/com/itsaky/androidide/utils/UriExtensions.kt`:
- Line 13: The ContentResolver query currently passes projection = null and
fetches only OpenableColumns.DISPLAY_NAME; change the query in the UriExtensions
code (the call starting with context.contentResolver.query(this, ...)) to
specify a projection array containing only OpenableColumns.DISPLAY_NAME (e.g.,
arrayOf(OpenableColumns.DISPLAY_NAME)) so the provider returns only that column
and reduces memory/work; keep the existing cursor usage (cursor.moveToFirst()
and cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)) unchanged.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b98c653 and e7d4792.

📒 Files selected for processing (1)
  • common/src/main/java/com/itsaky/androidide/utils/UriExtensions.kt

Catches SecurityException during contentResolver query and safely falls back to a default or decoded file name.
…e the logs to avoid exposing sensitive file paths.
@jatezzz jatezzz force-pushed the fix/ADFA-3050-uri-get-filename-crash branch from e7d4792 to e55a2fe Compare February 27, 2026 14:14
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
common/src/main/java/com/itsaky/androidide/utils/UriExtensions.kt (1)

22-23: Prefer narrow exception handling for this crash fix.

Line 22 catches all exceptions and converts unexpected failures into a silent fallback. That can mask new bugs; keep this handler specific to the documented crash type(s) and let unknown exceptions fail fast.

Suggested change
-        } catch (e: Exception) {
-            Log.w("UriExtensions", "Unexpected error while reading URI: ${scheme}://${authority}", e)
-        }
+        }

Based on learnings: prefer narrow exception handling that catches only the specific exception documented in crash reports, and evaluate other exception types in separate PRs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@common/src/main/java/com/itsaky/androidide/utils/UriExtensions.kt` around
lines 22 - 23, The current catch-all in the URI read code in UriExtensions.kt
(the block that logs "Unexpected error while reading URI") must be narrowed:
replace catch (e: Exception) with explicit catches for the specific crash types
reported (e.g., catch SecurityException and any documented IO-related exceptions
such as FileNotFoundException/IOException), log those cases the same way, and
rethrow or let other exceptions propagate so they fail fast; update the
try/catch around the URI-read helper in UriExtensions (the method that
constructs the "scheme://authority" log) to handle only those specific exception
types and not swallow unknown exceptions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@common/src/main/java/com/itsaky/androidide/utils/UriExtensions.kt`:
- Around line 22-23: The current catch-all in the URI read code in
UriExtensions.kt (the block that logs "Unexpected error while reading URI") must
be narrowed: replace catch (e: Exception) with explicit catches for the specific
crash types reported (e.g., catch SecurityException and any documented
IO-related exceptions such as FileNotFoundException/IOException), log those
cases the same way, and rethrow or let other exceptions propagate so they fail
fast; update the try/catch around the URI-read helper in UriExtensions (the
method that constructs the "scheme://authority" log) to handle only those
specific exception types and not swallow unknown exceptions.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e7d4792 and e55a2fe.

📒 Files selected for processing (1)
  • common/src/main/java/com/itsaky/androidide/utils/UriExtensions.kt

@jatezzz jatezzz merged commit 87e7f81 into stage Mar 2, 2026
2 checks passed
@jatezzz jatezzz deleted the fix/ADFA-3050-uri-get-filename-crash branch March 2, 2026 19:43
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.

2 participants