Skip to content

ADFA-3162: Add FileOpenExtension plugin delegation, archive viewer, and Install action#1068

Merged
Daniel-ADFA merged 4 commits intostagefrom
ADFA-3162
Mar 12, 2026
Merged

ADFA-3162: Add FileOpenExtension plugin delegation, archive viewer, and Install action#1068
Daniel-ADFA merged 4 commits intostagefrom
ADFA-3162

Conversation

@Daniel-ADFA
Copy link
Contributor

  • Plugins can now register to handle specific file types and open them directly from the file tree via the FileOpenExtension plugin api
  • APK files delegate directly to APK Analyzer plugin when installed (single-click analysis)
  • APK/CGP/ZIP files open as read-only archive listings when no plugin handles them
  • Add Install tab action for APK (device install) and CGP (plugin install with feedback)
  • Plugin file menu items appear in both tab popup and file tree context menu
Screen.Recording.2026-03-12.at.11.01.56.mov

…nd Install action

  - Plugins can now intercept file opens via FileOpenExtension (canHandleFileOpen/handleFileOpen)
  - APK files delegate directly to APK Analyzer plugin when installed (single-click analysis)
  - APK/CGP/ZIP files open as read-only archive listings when no plugin handles them
  - Add Install tab action for APK (device install) and CGP (plugin install with feedback)
  - Plugin file menu items appear in both tab popup and file tree context menu
@Daniel-ADFA Daniel-ADFA requested a review from a team March 12, 2026 10:22
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4dbda3fe-4b5c-4b7b-a67a-7bb0691b3989

📥 Commits

Reviewing files that changed from the base of the PR and between 6823691 and 805fe5f.

📒 Files selected for processing (1)
  • app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt

📝 Walkthrough

Walkthrough

Adds a FileOpenExtension API and integrates plugin-driven file-open delegation, file-tab menu items, and lifecycle notifications. APK viewer implements the extension with deferred analysis; editor UI surfaces plugin menu items; archives (apk/cgp/zip) are shown as read-only listings; an Install action and plugin metadata handling were added/updated.

Changes

Cohort / File(s) Summary
Plugin API & Manager
plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/extensions/FileOpenExtension.kt, plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt
Adds FileOpenExtension and FileTabMenuItem; PluginManager can delegate file opens, notify open/close events, and aggregate/sort file-tab menu items from enabled extensions.
APK Viewer Plugin
apk-viewer-plugin/src/main/AndroidManifest.xml, apk-viewer-plugin/src/main/kotlin/com/example/sampleplugin/ApkViewer.kt, apk-viewer-plugin/src/main/kotlin/com/example/sampleplugin/fragments/ApkAnalyzerFragment.kt
Bumps plugin.version to 1.0.2; ApkViewer now implements FileOpenExtension (canHandle/handle/onFileOpened/onFileClosed/getFileTabMenuItems, pendingAnalysisFile, TAB_ID usage); fragment adds analyzeFile, deferred processing, unified async analysis, and refactors ZIP handling to an entry map.
Editor Integration
app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt, app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt
Editor delegates file-open to PluginManager (delegateFileOpen) and notifies plugins on open/close; BaseEditor collects plugin-provided menu items for popup display (null-safety added).
File Actions & UI Menu
app/src/main/java/com/itsaky/androidide/actions/file/InstallFileAction.kt, app/src/main/java/com/itsaky/androidide/utils/ActionMenuUtils.kt, app/src/main/java/com/itsaky/androidide/utils/EditorActivityActions.kt
Adds InstallFileAction and registers it; ActionMenuUtils.showPopupWindow gains pluginMenuItems param and renders plugin FileTabMenuItems (divider and click handling) in file-tab popups.
File Tree & Context Menu
app/src/main/java/com/itsaky/androidide/handlers/FileTreeActionHandler.kt
Removes APK auto-install special-case; introduces archive detection (apk,cgp,zip) to skip size-blocking for archives; injects plugin file-tab menu items into file options and executes them when selected.
Editor View (Archive Handling)
app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt
Adds ARCHIVE_EXTENSIONS; disables saving for archives; reads ZIP contents to generate/display a structured archive listing and locks editing for archives.
Resources & Plugin Loader
resources/src/main/res/values/strings.xml, plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/loaders/PluginLoader.kt
Adds action_install string; plugin loader now reads plugin.version meta-data (defaults to "1.0.0") when extracting plugin metadata.
Misc UI wiring
app/src/main/java/com/itsaky/androidide/...
Small control-flow and logging adjustments across editor/tab lifecycle files to integrate plugin notifications and tab selection handling.

Sequence Diagram

sequenceDiagram
    participant User
    participant Editor as EditorHandlerActivity
    participant PM as PluginManager
    participant Plugin as FileOpenExtension\n(e.g., ApkViewer)
    participant Fragment as ApkAnalyzerFragment

    User->>Editor: Open file (file.apk)
    Editor->>PM: delegateFileOpen(file)
    PM->>Plugin: canHandleFileOpen(file)?
    Plugin-->>PM: true
    PM->>Plugin: handleFileOpen(file)
    Plugin->>Fragment: analyzeFile(file) / queue pendingAnalysisFile
    PM-->>Editor: handled (skip standard open)

    User->>Editor: Open file tab menu
    Editor->>PM: getFileTabMenuItems(file)
    PM->>Plugin: getFileTabMenuItems(file)
    Plugin-->>PM: [FileTabMenuItem]
    PM-->>Editor: aggregated menu items
    Editor->>Editor: showPopupWindow(..., pluginMenuItems)

    User->>Editor: Close file
    Editor->>PM: notifyFileClosed(file)
    PM->>Plugin: onFileClosed(file)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • itsaky-adfa
  • jatezzz

Poem

🐰 I hopped through bytes and zipped-up trails,
Plugins now answer opens and menu tales.
APKs queued to scan, deferred till tab's bright,
Install shows up, listings read-only at night.
🥕✨ Hop, celebrate—carrots and code delight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% 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 title directly summarizes the main changes: adding FileOpenExtension plugin delegation, archive viewer support, and Install action—all core features described in the changeset.
Description check ✅ Passed The description is directly related to the changeset, detailing the FileOpenExtension API, plugin delegation for APK files, archive viewer functionality, Install action, and menu item integration.

✏️ 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 ADFA-3162
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

You can make CodeRabbit's review stricter and more nitpicky using the `assertive` profile, if that's what you prefer.

Change the reviews.profile setting to assertive to make CodeRabbit's nitpick more issues in your PRs.

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: 6

🧹 Nitpick comments (4)
app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt (2)

334-335: Consider adding a log statement for debugging purposes.

The silent return false is functionally correct since archive files are made non-editable in initializeArchiveContent(). However, adding a debug log here would help trace save operations during troubleshooting:

-		if (file.extension.lowercase() in ARCHIVE_EXTENSIONS) return false
+		if (file.extension.lowercase() in ARCHIVE_EXTENSIONS) {
+			log.debug("Skipping save for archive file: {}", file.name)
+			return false
+		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt` around lines
334 - 335, Add a short debug log before the early-return that rejects archive
files: in CodeEditorView where the check "if (file.extension.lowercase() in
ARCHIVE_EXTENSIONS) return false" occurs, log the file name and extension (using
the existing logger or android.util.Log/Timber) so save attempts on archive
files are visible during debugging; keep the return behavior unchanged and
reference initializeArchiveContent() for context.

509-513: Consider narrowing the exception catch.

While catching Exception provides a safe fallback for user display, it could mask unexpected errors during development. Consider catching the specific exceptions that ZipFile can throw:

-		} catch (e: Exception) {
+		} catch (e: java.util.zip.ZipException) {
+			builder.clear()
+			builder.appendLine("Failed to read archive: ${file.name}")
+			builder.appendLine(e.message ?: "Invalid or corrupt archive")
+		} catch (e: java.io.IOException) {
 			builder.clear()
 			builder.appendLine("Failed to read archive: ${file.name}")
 			builder.appendLine(e.message ?: "Unknown error")
 		}

Based on learnings: "prefer narrow exception handling that catches only the specific exception type... This aligns with fail-fast behavior during development."

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

In `@app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt` around lines
509 - 513, The current broad catch (e: Exception) in CodeEditorView should be
narrowed to only handle the expected archive-related errors: catch ZipException
and IOException (or the concrete exceptions thrown by ZipFile) and keep the
existing builder.clear()/appendLine(...) handling in those catches; remove the
general Exception catch so unexpected runtime errors can propagate (fail fast)
during development. Ensure you refer to the existing symbols (builder,
file.name) when implementing the two specific catch blocks and preserve the
user-facing messages there.
plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/extensions/FileOpenExtension.kt (1)

19-27: Consider documenting the icon and action fields.

The FileTabMenuItem data class is functional, but consumers would benefit from brief documentation clarifying that icon expects an Android drawable resource ID and that action exceptions should be handled by callers.

📝 Suggested documentation
+/**
+ * Represents a menu item contributed by a plugin for file tab context menus.
+ * `@param` id Unique identifier for this menu item
+ * `@param` title Display text for the menu item
+ * `@param` icon Optional drawable resource ID for the icon
+ * `@param` isEnabled Whether the item is enabled
+ * `@param` isVisible Whether the item is visible
+ * `@param` order Sort order (lower values appear first)
+ * `@param` action Callback invoked when the item is clicked; callers must handle exceptions
+ */
 data class FileTabMenuItem(
     val id: String,
     val title: String,
     val icon: Int? = null,
     val isEnabled: Boolean = true,
     val isVisible: Boolean = true,
     val order: Int = 0,
     val action: () -> Unit
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/extensions/FileOpenExtension.kt`
around lines 19 - 27, Add KDoc to the FileTabMenuItem data class clarifying the
semantics of the icon and action properties: document that icon is an Android
drawable resource ID (Int) and may be null when no icon is needed, and document
that action is a callback invoked on selection and that callers are responsible
for handling or catching any exceptions thrown by the action; place the comments
on the FileTabMenuItem class and/or directly above the icon and action
parameters to make the contract clear to consumers.
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt (1)

604-613: Consider fallback behavior when the selected handler fails.

If canHandleFileOpen returns true but handleFileOpen throws or returns false, the delegation returns false without trying other capable extensions. This is acceptable if the "first match wins" semantic is intentional, but consider whether a fallback to subsequent handlers would be more resilient.

♻️ Optional: Try next handler on failure
 fun delegateFileOpen(file: File): Boolean {
-    val handler = getEnabledFileOpenExtensions().firstOrNull { extension ->
-        executeWithErrorHandling("check canHandleFileOpen") {
-            extension.canHandleFileOpen(file)
-        }.getOrDefault(false)
-    } ?: return false
-    return executeWithErrorHandling("handle file open") {
-        handler.handleFileOpen(file)
-    }.getOrDefault(false)
+    for (extension in getEnabledFileOpenExtensions()) {
+        val canHandle = executeWithErrorHandling("check canHandleFileOpen") {
+            extension.canHandleFileOpen(file)
+        }.getOrDefault(false)
+        if (canHandle) {
+            val handled = executeWithErrorHandling("handle file open") {
+                extension.handleFileOpen(file)
+            }.getOrDefault(false)
+            if (handled) return true
+        }
+    }
+    return false
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt`
around lines 604 - 613, delegateFileOpen currently picks the first extension
where getEnabledFileOpenExtensions().firstOrNull { ... canHandleFileOpen(...) }
and if that handler's handleFileOpen(...) fails or returns false the method
returns false without trying other capable extensions; change it to iterate
through the enabled extensions, call executeWithErrorHandling("check
canHandleFileOpen") { extension.canHandleFileOpen(file) } and for each true
candidate call executeWithErrorHandling("handle file open") {
extension.handleFileOpen(file) }, returning true immediately on the first
successful handleFileOpen(true) and otherwise continuing to the next candidate
(and finally returning false if none succeed); preserve existing error
handling/logging via executeWithErrorHandling and ensure exceptions from one
handler do not prevent trying subsequent handlers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apk-viewer-plugin/src/main/AndroidManifest.xml`:
- Around line 17-19: The manifest still declares plugin.min_ide_version as 1.0.0
while the plugin now unconditionally implements FileOpenExtension; update the
manifest entry plugin.min_ide_version to "2.1.0" to match the IDE release that
introduced FileOpenExtension so older IDEs won't load the incompatible plugin;
locate the meta-data element named plugin.min_ide_version in the
AndroidManifest.xml (alongside plugin.version) and change its android:value to
"2.1.0".

In `@apk-viewer-plugin/src/main/kotlin/com/example/sampleplugin/ApkViewer.kt`:
- Around line 129-136: The pendingAnalysisFile can be left unprocessed if the
APK Analyzer tab is already selected because onEditorTabSelected only fires on
selection changes; update openApkAnalyzerTab (after calling
selectPluginTab(TAB_ID)) to query the current tab/fragment from the
IdeEditorTabService (or equivalent) and if the current tab id equals TAB_ID and
the fragment is an ApkAnalyzerFragment, call
fragment.analyzeFile(pendingAnalysisFile) and clear pendingAnalysisFile (same
behavior as in onEditorTabSelected), with null and type checks; this ensures
pendingAnalysisFile is handled whether the tab is newly selected or already
active.

In
`@apk-viewer-plugin/src/main/kotlin/com/example/sampleplugin/fragments/ApkAnalyzerFragment.kt`:
- Around line 149-158: The ZipFile opened in analyzeApkFromFile (variable
zipFile) can leak if an exception is thrown; replace the manual ZipFile creation
with Kotlin's use{} (or ensure a try/finally that closes zipFile) so the ZipFile
is closed automatically after processing; update the block that builds
entries/entryMap and any further access to the zip to occur inside the use{}
scope to guarantee proper resource cleanup.

In `@app/src/main/java/com/itsaky/androidide/actions/file/InstallFileAction.kt`:
- Around line 50-68: The doAction implementation in EditorHandlerActivity
obtains a file via editorViewModel.getCurrentFile() and switches on
file.extension.lowercase(), but lacks an else/default branch so files with no or
unhandled extensions result in no action while the method still returns true;
modify EditorHandlerActivity.doAction to treat unrecognized/empty extensions as
no-op failures by adding an else branch (or explicit handling for empty
file.extension) that returns false (or otherwise signals no action taken) so the
method only returns true when an action (e.g.,
apkInstallationViewModel.installApk or repo.installPluginFromFile) was actually
invoked.

In `@app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt`:
- Around line 479-498: The loop that builds the ZIP listing currently uses
entry.size, entry.compressedSize, and entry.crc directly which can be -1 for
unknown values; change the logic in the entries iteration (the block that
updates totalSize/totalCompressed and calls builder.appendLine with
String.format) to: treat any entry.size or entry.compressedSize == -1 as
"unknown" (display "-" in the formatted columns and do NOT add them to
totalSize/totalCompressed), and treat entry.crc == -1 as "-" (instead of
formatting to "ffffffff"); adjust the values passed to String.format accordingly
so totals remain non-negative and unknowns are shown as "-" while keeping the
existing method/time/name formatting.
- Around line 448-460: The archive-loading path in initializeArchiveContent does
not refresh the Activity options menu like initializeContent does; after calling
configureEditorIfNeeded() in initializeArchiveContent(), add a menu invalidation
call: invoke (context as? Activity?)?.invalidateOptionsMenu() so the UI reflects
the new read-only file state; update initializeArchiveContent to call this
(mirroring the postRead behavior referenced in initializeContent) to keep menu
items synchronized.

---

Nitpick comments:
In `@app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt`:
- Around line 334-335: Add a short debug log before the early-return that
rejects archive files: in CodeEditorView where the check "if
(file.extension.lowercase() in ARCHIVE_EXTENSIONS) return false" occurs, log the
file name and extension (using the existing logger or android.util.Log/Timber)
so save attempts on archive files are visible during debugging; keep the return
behavior unchanged and reference initializeArchiveContent() for context.
- Around line 509-513: The current broad catch (e: Exception) in CodeEditorView
should be narrowed to only handle the expected archive-related errors: catch
ZipException and IOException (or the concrete exceptions thrown by ZipFile) and
keep the existing builder.clear()/appendLine(...) handling in those catches;
remove the general Exception catch so unexpected runtime errors can propagate
(fail fast) during development. Ensure you refer to the existing symbols
(builder, file.name) when implementing the two specific catch blocks and
preserve the user-facing messages there.

In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/extensions/FileOpenExtension.kt`:
- Around line 19-27: Add KDoc to the FileTabMenuItem data class clarifying the
semantics of the icon and action properties: document that icon is an Android
drawable resource ID (Int) and may be null when no icon is needed, and document
that action is a callback invoked on selection and that callers are responsible
for handling or catching any exceptions thrown by the action; place the comments
on the FileTabMenuItem class and/or directly above the icon and action
parameters to make the contract clear to consumers.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt`:
- Around line 604-613: delegateFileOpen currently picks the first extension
where getEnabledFileOpenExtensions().firstOrNull { ... canHandleFileOpen(...) }
and if that handler's handleFileOpen(...) fails or returns false the method
returns false without trying other capable extensions; change it to iterate
through the enabled extensions, call executeWithErrorHandling("check
canHandleFileOpen") { extension.canHandleFileOpen(file) } and for each true
candidate call executeWithErrorHandling("handle file open") {
extension.handleFileOpen(file) }, returning true immediately on the first
successful handleFileOpen(true) and otherwise continuing to the next candidate
(and finally returning false if none succeed); preserve existing error
handling/logging via executeWithErrorHandling and ensure exceptions from one
handler do not prevent trying subsequent handlers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bf553b7e-e9bf-499c-b830-9f412e46eb43

📥 Commits

Reviewing files that changed from the base of the PR and between 26b4268 and 73b199d.

📒 Files selected for processing (13)
  • apk-viewer-plugin/src/main/AndroidManifest.xml
  • apk-viewer-plugin/src/main/kotlin/com/example/sampleplugin/ApkViewer.kt
  • apk-viewer-plugin/src/main/kotlin/com/example/sampleplugin/fragments/ApkAnalyzerFragment.kt
  • app/src/main/java/com/itsaky/androidide/actions/file/InstallFileAction.kt
  • app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt
  • app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt
  • app/src/main/java/com/itsaky/androidide/handlers/FileTreeActionHandler.kt
  • app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt
  • app/src/main/java/com/itsaky/androidide/utils/ActionMenuUtils.kt
  • app/src/main/java/com/itsaky/androidide/utils/EditorActivityActions.kt
  • plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/extensions/FileOpenExtension.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt
  • resources/src/main/res/values/strings.xml

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 (4)
app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt (3)

475-476: Use explicit Locale.ROOT for consistent formatting.

String.format without a locale uses the device's default locale, which can produce inconsistent output (e.g., decimal separators varying between , and . for the compression ratio). Since this is a technical listing, use Locale.ROOT for consistent formatting.

♻️ Proposed fix
+import java.util.Locale

Then in generateArchiveListing:

 				builder.appendLine(
-					String.format("%-10s %-10s %-6s %-8s %-20s %s",
+					String.format(Locale.ROOT, "%-10s %-10s %-6s %-8s %-20s %s",
 						"Length", "Compressed", "Method", "CRC-32", "Date & Time", "Name")
 				)
-					val crc = if (entry.crc >= 0) String.format("%08x", entry.crc) else "-"
+					val crc = if (entry.crc >= 0) String.format(Locale.ROOT, "%08x", entry.crc) else "-"
 					builder.appendLine(
-						String.format("%-10s %-10s %-6s %-8s %-20s %s",
+						String.format(Locale.ROOT, "%-10s %-10s %-6s %-8s %-20s %s",
 							sizeStr, compressedStr, method, crc, time, entry.name)
 					)
 				builder.appendLine(
-					String.format("%-10d %-10d %-6s %s",
+					String.format(Locale.ROOT, "%-10d %-10d %-6s %s",
 						totalSize, totalCompressed, ratio, "${entries.size} files")
 				)

Also applies to: 489-489, 500-501, 510-511

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

In `@app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt` around lines
475 - 476, The String.format calls in generateArchiveListing (including the
header at the shown line and the other occurrences around lines 489, 500-501,
510-511) should be made locale-independent by passing Locale.ROOT as the first
argument; update each String.format(...) call used for the archive listing
(e.g., the header format and the per-entry formatting in generateArchiveListing)
to call String.format(Locale.ROOT, formatString, ...), ensuring consistent
numeric/decimal formatting across locales.

514-518: Prefer narrow exception handling for ZipFile operations.

Catching the broad Exception type can mask unexpected errors. ZipFile operations typically throw IOException or ZipException (which extends IOException), and file access may throw SecurityException.

♻️ Proposed fix
-		} catch (e: Exception) {
+		} catch (e: IOException) {
 			builder.clear()
 			builder.appendLine("Failed to read archive: ${file.name}")
 			builder.appendLine(e.message ?: "Unknown error")
+		} catch (e: SecurityException) {
+			builder.clear()
+			builder.appendLine("Failed to read archive: ${file.name}")
+			builder.appendLine("Permission denied: ${e.message ?: "Unknown error"}")
 		}

Based on learnings: "In Kotlin files across the AndroidIDE project, prefer narrow exception handling that catches only the specific exception type... instead of a broad catch-all."

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

In `@app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt` around lines
514 - 518, The current catch-all "catch (e: Exception)" in CodeEditorView.kt
should be narrowed to handle the specific ZipFile-related errors: replace it
with separate catches for java.util.zip.ZipException (or
java.util.zip.ZipException/IOException combined), java.io.IOException, and
java.lang.SecurityException in the method/block that reads archives (the one
that uses builder and file.name), and in each handler keep the builder.clear()
and append descriptive messages including the exception details (e.g., e.message
or e.localizedMessage) so callers still see "Failed to read archive:
${file.name}" plus the specific error; remove the generic Exception catch to
avoid masking other issues.

334-335: Consider adding a log statement for clarity.

While this follows the existing pattern (unmodified files also return false), a log message would help distinguish "archive files cannot be saved" from "file wasn't modified" when debugging save operations.

📝 Proposed improvement
-		if (file.extension.lowercase() in ARCHIVE_EXTENSIONS) return false
+		if (file.extension.lowercase() in ARCHIVE_EXTENSIONS) {
+			log.debug("Skipping save operation for archive file {}", file.name)
+			return false
+		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt` around lines
334 - 335, In CodeEditorView, before the early return that checks "if
(file.extension.lowercase() in ARCHIVE_EXTENSIONS) return false", add a log
statement that clearly records that the file is an archive and therefore not
savable (include file.name and file.extension in the message); use the project's
logging mechanism (e.g., Android Log or the existing logger used elsewhere in
CodeEditorView) so this case is distinguishable from the "not modified" return
path.
apk-viewer-plugin/src/main/kotlin/com/example/sampleplugin/fragments/ApkAnalyzerFragment.kt (1)

152-153: Prefer explicit ZIP/IO catches here.

runCatching catches Throwable, so fatal VM errors also get converted into a normal "Failed to analyze APK" result. It would be safer to catch the expected parsing failures explicitly instead of wrapping the whole method in a catch-all.

Based on learnings: In Kotlin files across the AndroidIDE project, prefer narrow exception handling that catches only the specific exception type reported in crashes instead of a broad catch-all.

Also applies to: 335-337

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

In
`@apk-viewer-plugin/src/main/kotlin/com/example/sampleplugin/fragments/ApkAnalyzerFragment.kt`
around lines 152 - 153, The use of runCatching around the ZipFile(file).use {
... } in ApkAnalyzerFragment wraps all Throwables (including fatal VM errors);
replace the broad runCatching with a try { ZipFile(file).use { ... } } catch (e:
java.util.zip.ZipException) { /* handle/log specific zip parse error and return
failure result */ } catch (e: java.io.IOException) { /* handle/log IO error and
return failure result */ } so only expected parsing/IO failures are caught (do
not catch Throwable or Exception); update the same pattern where runCatching is
used again in the file (the other similar block) to mirror this explicit
ZipException/IOException handling and return the same failure result.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apk-viewer-plugin/src/main/kotlin/com/example/sampleplugin/fragments/ApkAnalyzerFragment.kt`:
- Around line 115-117: analyzeApkInBackground currently calls runAnalysis
without the same lifecycle guard used in analyzeFile, which can access
viewLifecycleOwner after onDestroyView and will also hide CancellationException
inside runCatching; fix by checking the fragment view lifecycle before starting
work (e.g., if (!isAdded || view == null) return) in analyzeApkInBackground (or
at the top of runAnalysis), and update the error handling around
withContext/runCatching so CancellationException is rethrown instead of being
converted to a failure message (catch Throwable but if it is a
CancellationException rethrow it, otherwise handle/log the error). Ensure
references: analyzeApkInBackground, analyzeFile, runAnalysis, runCatching,
withContext, viewLifecycleOwner.
- Around line 137-145: The analyzeApkFromUri function currently ignores a null
InputStream from requireContext().contentResolver.openInputStream(uri) and
proceeds to call analyzeApkFromFile on an empty tempFile; change
analyzeApkFromUri to explicitly handle a null return: after creating tempFile,
call openInputStream(uri) into a local val (e.g. val input =
requireContext().contentResolver.openInputStream(uri)), if input is null delete
tempFile and throw or return a clear error indicating the URI could not be
opened (or return a meaningful Result), otherwise use input.use { ... } to copy
to tempFile and then call analyzeApkFromFile(tempFile); ensure tempFile is still
deleted in the finally block.

---

Nitpick comments:
In
`@apk-viewer-plugin/src/main/kotlin/com/example/sampleplugin/fragments/ApkAnalyzerFragment.kt`:
- Around line 152-153: The use of runCatching around the ZipFile(file).use { ...
} in ApkAnalyzerFragment wraps all Throwables (including fatal VM errors);
replace the broad runCatching with a try { ZipFile(file).use { ... } } catch (e:
java.util.zip.ZipException) { /* handle/log specific zip parse error and return
failure result */ } catch (e: java.io.IOException) { /* handle/log IO error and
return failure result */ } so only expected parsing/IO failures are caught (do
not catch Throwable or Exception); update the same pattern where runCatching is
used again in the file (the other similar block) to mirror this explicit
ZipException/IOException handling and return the same failure result.

In `@app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt`:
- Around line 475-476: The String.format calls in generateArchiveListing
(including the header at the shown line and the other occurrences around lines
489, 500-501, 510-511) should be made locale-independent by passing Locale.ROOT
as the first argument; update each String.format(...) call used for the archive
listing (e.g., the header format and the per-entry formatting in
generateArchiveListing) to call String.format(Locale.ROOT, formatString, ...),
ensuring consistent numeric/decimal formatting across locales.
- Around line 514-518: The current catch-all "catch (e: Exception)" in
CodeEditorView.kt should be narrowed to handle the specific ZipFile-related
errors: replace it with separate catches for java.util.zip.ZipException (or
java.util.zip.ZipException/IOException combined), java.io.IOException, and
java.lang.SecurityException in the method/block that reads archives (the one
that uses builder and file.name), and in each handler keep the builder.clear()
and append descriptive messages including the exception details (e.g., e.message
or e.localizedMessage) so callers still see "Failed to read archive:
${file.name}" plus the specific error; remove the generic Exception catch to
avoid masking other issues.
- Around line 334-335: In CodeEditorView, before the early return that checks
"if (file.extension.lowercase() in ARCHIVE_EXTENSIONS) return false", add a log
statement that clearly records that the file is an archive and therefore not
savable (include file.name and file.extension in the message); use the project's
logging mechanism (e.g., Android Log or the existing logger used elsewhere in
CodeEditorView) so this case is distinguishable from the "not modified" return
path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e2f4437e-b4e7-4e6b-bc8e-71715d2796cb

📥 Commits

Reviewing files that changed from the base of the PR and between 73b199d and 0492385.

📒 Files selected for processing (2)
  • apk-viewer-plugin/src/main/kotlin/com/example/sampleplugin/fragments/ApkAnalyzerFragment.kt
  • app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt

@Daniel-ADFA Daniel-ADFA merged commit 0681d66 into stage Mar 12, 2026
2 checks passed
@Daniel-ADFA Daniel-ADFA deleted the ADFA-3162 branch March 12, 2026 19:30
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.

3 participants