Skip to content

ADFA-3260: Fix plugin resource and theme resolution for custom package IDs#1103

Open
Daniel-ADFA wants to merge 7 commits intostagefrom
ADFA-3260
Open

ADFA-3260: Fix plugin resource and theme resolution for custom package IDs#1103
Daniel-ADFA wants to merge 7 commits intostagefrom
ADFA-3260

Conversation

@Daniel-ADFA
Copy link
Contributor

This video demonstrates keystore generator using the Material3 custom theme

Screen.Recording.2026-03-21.at.19.39.09.mov

# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
…alParameters for package-id and allow-reserved-package-id
…alParameters for package-id and allow-reserved-package-id
… ID theme attributes

  Plugins with auto-assigned package IDs (0x02-0x7E) crashed on dark/light
  toggle because their bundled Material3 theme attributes weren't added to the
  plugin theme. Now applies the manifest-declared theme to bridge the gap.
@Daniel-ADFA Daniel-ADFA requested a review from jatezzz March 21, 2026 19:20
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 21, 2026

📝 Walkthrough

Release Notes - ADFA-3260: Plugin Resource and Theme Resolution for Custom Package IDs

Major Features & Improvements

  • Custom Package ID Support for Plugins: Plugin resource and theme resolution now properly handles plugins with custom package IDs, enabling better theme customization and resource isolation
  • Material3 Theme Migration: Keystore Generator plugin UI refactored to use Material3 components (TextInputLayout, MaterialButton, LinearProgressIndicator) replacing legacy Material and platform widgets
  • Enhanced Theme System:
    • Plugin themes now reference custom @style/PluginTheme instead of generic @style/Theme.AppCompat
    • Dynamic activity theme resolution integrated into plugin resource context
    • Theme invalidation and rebuilding when activity context changes
  • Native Code Support: Added NATIVE_CODE permission type for plugins requiring native library execution
    • Native libraries automatically extracted from plugin APKs during loading
    • Security validation ensures plugins declare NATIVE_CODE permission before library extraction
    • Per-plugin native library directory management with cleanup on plugin unload
  • Plugin Builder Improvements:
    • Automatic package ID computation from android.defaultConfig.applicationId
    • Unified task creation for debug/release variants (assemblePluginDebug/assemblePlugin)
    • Debug builds now generate -debug suffix in .cgp filenames

Styling & Resource Updates

  • Color Palette Expansion: Added Material3 color roles (plugin_secondary, plugin_on_secondary, plugin_surface, plugin_on_surface, plugin_on_surface_variant, plugin_outline, plugin_outline_variant)
  • Status Color Updates: Enhanced status indicators with dedicated success/error foreground and background colors
  • Comprehensive String Resources: Added 23+ new localization entries for validation, form state, error handling, and success messages

Plugin Version Updates

  • apk-viewer-plugin: 1.0.2 → 1.0.3
  • keystore-generator-plugin: 1.0.3 → 1.0.4
  • markdown-preview-plugin: 1.0.0 → 1.0.1

Technical Improvements

  • Resource Context Enhancements:
    • Plugin resource context now accepts optional pluginClassLoader parameter
    • Custom package ID detection via reflection-based R class inspection
    • Conditional resource resolution between plugin resources and activity resources
  • Theme Resolution:
    • Added getCurrentActivityTheme() and getCurrentActivityContext() API in PluginFragmentHelper
    • Weak references to activity context/theme for automatic cleanup
  • API Level Compatibility: ResourcesProvider and ResourcesLoader for API 30+; fallback to AssetManager.addAssetPath reflection for earlier versions
  • Plugin Manager Extension: New getCurrentActivity() method for plugin access to host activity

Risks & Best Practices Considerations

⚠️ Weak Reference Usage: PluginFragmentHelper stores activity context and theme in volatile WeakReference fields. Callers of getCurrentActivityTheme() and getCurrentActivityContext() must handle potential null returns, especially after activity recreation or destruction.

⚠️ Native Library Security: Native library extraction and permission enforcement is in place, but plugins with extracted native libraries require explicit NATIVE_CODE permission. Ensure security review of any plugin declaring this permission.

⚠️ API Level Variance: Resource loading uses different mechanisms on API 30+ vs. earlier versions (reflection-based fallback). Test across API levels to ensure theme/resource consistency.

⚠️ Theme Caching & Invalidation: Plugin theme is lazily resolved and cached. Ensure proper invalidation when switching between activities with different themes to avoid stale theme references.

  • Build Directory Migration: Shift from project.buildDir to project.layout.buildDirectory aligns with Gradle best practices but verify compatibility with build scripts/CI pipelines.

Walkthrough

The PR introduces Material Design 3 theming across multiple plugins, refactors the keystore generator fragment to use Material components and localized resources, implements native library extraction and loading in the plugin system with permission enforcement, and updates plugin infrastructure including new permission types and context management.

Changes

Cohort / File(s) Summary
Build Configuration Formatting
apk-viewer-plugin/build.gradle.kts, keystore-generator-plugin/build.gradle.kts, markdown-preview-plugin/build.gradle.kts
Added blank lines within android {} blocks for formatting consistency.
Plugin Version and Theme Updates
apk-viewer-plugin/src/main/AndroidManifest.xml, keystore-generator-plugin/src/main/AndroidManifest.xml, markdown-preview-plugin/src/main/AndroidManifest.xml
Updated theme references from @style/Theme.AppCompat to plugin-specific themes (@style/PluginTheme, @style/Theme.MarkdownPreviewer) and incremented plugin version metadata.
Keystore Generator UI Refactoring
keystore-generator-plugin/src/main/kotlin/.../KeystoreGeneratorFragment.kt, keystore-generator-plugin/src/main/res/layout/fragment_keystore_generator.xml
Migrated from platform widgets to Material 3 components (MaterialButton, LinearProgressIndicator, TextInputLayout), integrated localized resource strings for validation/error messages, and refactored coroutine scope management and error handling.
Material Design 3 Color Palette
keystore-generator-plugin/src/main/res/values/colors.xml, keystore-generator-plugin/src/main/res/values-night/colors.xml
Updated primary/secondary/surface color values and added Material 3 color roles (plugin_secondary, plugin_on_secondary, plugin_surface, plugin_on_surface, plugin_outline variants).
String Resources and Localization
keystore-generator-plugin/src/main/res/values/strings.xml
Added 22 new localized string resources for form validation, generation states, error/success messages, service availability, and permission/build status outcomes.
Plugin Theme Styling
keystore-generator-plugin/src/main/res/values/styles.xml
Extended PluginTheme with Material 3 color attributes (colorSecondary, colorSurface, colorOutline variants), replacing deprecated theme attribute patterns.
Native Code Permission
plugin-api/src/main/kotlin/.../IPlugin.kt, templates-impl/src/main/java/.../PluginTemplateData.kt
Added NATIVE_CODE permission entry to PluginPermission enum, enabling plugins to declare native library execution capability.
Plugin Builder Task Reorganization
plugin-api/plugin-builder/src/main/kotlin/.../PluginBuilder.kt
Consolidated variant-specific task creation into shared createAssembleTask(), introduced configurePackageId() to auto-compute and validate plugin package IDs from application config, updated build-directory references to use project.layout.buildDirectory.
Plugin Inflater and Context Helpers
plugin-api/src/main/kotlin/.../PluginFragment.kt
Simplified LayoutInflater resolution to always clone from default context, added public getter methods getCurrentActivityTheme() and getCurrentActivityContext() with weak references for plugin context access.
Plugin Resource Context Enhancement
plugin-manager/.../PluginResourceContext.kt
Extended constructor to accept pluginClassLoader, implemented custom package ID detection via reflection, added dynamic resource patching using ResourcesProvider.loadFromApk on API 30+, and refactored theme resolution to depend on current activity context.
Plugin Manager Native Library Support
plugin-manager/.../PluginManager.kt
Integrated native library extraction via pluginLoader.extractNativeLibs(), enforced NATIVE_CODE permission check before class loading (deletes extracted libs on permission denial), added explicit native-lib directory cleanup during plugin unload/cache cleanup, and exposed getCurrentActivity() method.
Plugin Loader Native Library Extraction
plugin-manager/.../PluginLoader.kt
Updated loadPluginClasses() to accept optional nativeLibPath parameter, implemented extractNativeLibs() to extract .so files from plugin APK for target ABI, added pluginClassLoader argument to PluginResourceContext construction for classloader-based resource resolution.

Sequence Diagram(s)

sequenceDiagram
    participant PluginManager
    participant PluginLoader as PluginLoader
    participant PluginManifest as Plugin Manifest
    participant FileSystem as File System
    participant DexClassLoader

    PluginManager->>PluginLoader: extractNativeLibs(pluginId)
    PluginLoader->>FileSystem: createDir(plugin_native_libs/{pluginId})
    PluginLoader->>PluginManifest: open plugin APK
    PluginLoader->>PluginManifest: scan for .so files (native ABI)
    PluginManifest-->>PluginLoader: .so file list
    loop for each native library
        PluginLoader->>FileSystem: extract .so to plugin dir
    end
    PluginLoader-->>PluginManager: nativeLibPath (or null)
    
    alt Native libs extracted
        PluginManager->>PluginManifest: check NATIVE_CODE permission
        alt Permission granted
            PluginManager->>PluginLoader: loadPluginClasses(nativeLibPath)
            PluginLoader->>DexClassLoader: new DexClassLoader(nativeLibPath)
            DexClassLoader-->>PluginLoader: classloader ready
            PluginLoader-->>PluginManager: success
        else Permission denied
            PluginManager->>FileSystem: deleteRecursive(plugin_native_libs/{pluginId})
            PluginManager-->>PluginManager: return SecurityException
        end
    else No native libs
        PluginManager->>PluginLoader: loadPluginClasses(null)
        PluginLoader-->>PluginManager: classloader ready
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related PRs

Suggested Reviewers

  • itsaky-adfa
  • jatezzz

Poem

🐰 Hop along as themes bloom in Material's glow,
Native libraries dance where permissions allow to flow,
Resources now speak in localized tongues so clear,
Plugin contexts shimmer with classloaders near—
A vibrant tapestry woven with care and delight! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.07% 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 describes the main change: fixing plugin resource and theme resolution for custom package IDs, which aligns with the significant refactoring in PluginResourceContext and related loader/manager changes.
Description check ✅ Passed The description mentions Material3 custom theme and includes a video demonstration related to keystore generator changes, which is relevant to the UI/theme updates in the changeset.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ADFA-3260

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

🧹 Nitpick comments (3)
keystore-generator-plugin/src/main/res/layout/fragment_keystore_generator.xml (1)

269-271: Optional: avoid fixed bottom spacer view.

Line 269 adds static whitespace that may be inconsistent across screen sizes/IME states. Prefer handling bottom space via parent/action-container padding or insets.

♻️ Optional cleanup
-    <View
-        android:layout_width="match_parent"
-        android:layout_height="16dp" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@keystore-generator-plugin/src/main/res/layout/fragment_keystore_generator.xml`
around lines 269 - 271, The layout currently contains a fixed-height spacer View
(the anonymous <View> at the bottom of fragment_keystore_generator.xml) which
can cause inconsistent gaps across screens and IME states; remove that static
View and instead provide bottom spacing via the parent/container (e.g., add
paddingBottom to the root or action container) and handle system/IME insets (use
WindowInsets or ViewCompat.setOnApplyWindowInsetsListener on the root or
container) so the UI adapts to keyboard and different screen sizes while
preserving the intended bottom gap.
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/loaders/PluginResourceContext.kt (1)

42-63: Consider narrowing the exception handling.

Per retrieved learnings, prefer narrow exception handling in Kotlin files across the AndroidIDE project. The current catch (e: Exception) at line 59 catches all exceptions, which could mask unexpected errors.

However, reflection and class loading operations can throw multiple exception types (ClassNotFoundException, IllegalAccessException, NoSuchFieldException, etc.), so a pragmatic approach would be to catch ReflectiveOperationException which covers most reflection-related failures:

Suggested refinement
-        } catch (e: Exception) {
+        } catch (e: ReflectiveOperationException) {
+            Log.w(TAG, "Failed to detect custom package ID for $pkg", e)
+        } catch (e: ClassNotFoundException) {
             Log.w(TAG, "Failed to detect custom package ID for $pkg", e)
         }

Based on learnings: "prefer narrow exception handling that catches only the specific exception type reported in crashes."

🤖 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/loaders/PluginResourceContext.kt`
around lines 42 - 63, The catch-all in detectCustomPackageId currently swallows
all exceptions; narrow it to reflection-related failures by replacing catch (e:
Exception) with catch (e: ReflectiveOperationException) (and optionally catch
IllegalArgumentException if you expect invalid arg access) so only
reflection/class-loading errors from pluginClassLoader.loadClass("$pkg.R") and
subsequent field access are handled; keep the Log.w(TAG, "Failed to detect
custom package ID for $pkg", e) behavior and leave other exceptions to
propagate.
plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/base/PluginFragment.kt (1)

96-97: Consider documenting thread-safety assumptions.

These assignments are not atomic with respect to each other. While @Volatile ensures visibility, concurrent calls to getPluginInflater could result in activityContextRef and activityThemeRef pointing to different activities momentarily.

This is likely acceptable if:

  1. getPluginInflater is only called from the main thread, or
  2. Both refs always come from the same activity in practice.

A brief comment clarifying the expected threading model would help future maintainers.

🤖 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/base/PluginFragment.kt`
around lines 96 - 97, The two WeakReference assignments (activityContextRef and
activityThemeRef) in getPluginInflater are not atomic and can briefly point to
different activities if called concurrently; add a concise comment near these
assignments documenting the thread-safety assumption (e.g., getPluginInflater is
expected to be called on the main/UI thread or that both refs always originate
from the same Activity), and to harden the contract optionally annotate
getPluginInflater with `@MainThread` (or synchronize the update) so future
maintainers know the intended threading model; reference activityContextRef,
activityThemeRef, getPluginInflater and defaultInflater in the comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@keystore-generator-plugin/src/main/kotlin/com/appdevforall/keygen/plugin/fragments/KeystoreGeneratorFragment.kt`:
- Around line 267-272: The catch block in KeystoreGeneratorFragment currently
appends e.message which can be null and show literal "null" to users; change the
error message construction used with showError(...) to only append the exception
message when it's non-null/blank (e.g., use a safe fallback like e.message ?: ""
or conditional concatenation) so hideProgress(), btnGenerate.isEnabled = true
and showError(...) are still called but the displayed text never contains
"null".

In `@keystore-generator-plugin/src/main/res/values/strings.xml`:
- Line 46: Update the user-facing error string named error_no_build_gradle to
mention Kotlin DSL files as well (e.g., "build.gradle or build.gradle.kts") so
it reflects what the code checks; open the strings.xml entry for the string
resource error_no_build_gradle and change its text to include "build.gradle.kts"
(or equivalent phrasing) to avoid misleading users.

In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/base/PluginFragment.kt`:
- Line 98: The expression pluginContext.theme in PluginFragment is unused and
should be either removed or made explicit; either delete the dead expression or
use the theme value (for example assign it to a val or call a method like
applyStyle on it) so the access has an effect. Locate the occurrence in
PluginFragment (the pluginContext.theme expression) and replace the no-op access
with a meaningful operation (remove it if unnecessary, or store it in a variable
or invoke the intended theme method such as applyStyle) so the code is not a
discarded expression.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt`:
- Around line 315-330: The extracted native libs directory (nativeLibPath set
from pluginLoader.extractNativeLibs(manifest.id)) is left on disk when the
NATIVE_CODE permission check fails; update the permission-failure branch in
PluginManager (around the nativeLibPath check) to delete or clean up the
extracted native libs before returning the Result.failure — for example, if
nativeLibPath != null call the existing cleanup routine (cleanupPluginCacheFiles
or a new pluginLoader.deleteExtractedNativeLibs(manifest.id)) or delete the
File(nativeLibPath) and still release sidebar slots via
SidebarSlotManager.releasePluginSlots(manifest.id) before returning the
SecurityException. Ensure the cleanup is performed only when extraction
succeeded (nativeLibPath != null) and do not rely solely on
unloadPlugin/cleanupPluginCacheFiles which run only for successfully loaded
plugins.

In
`@templates-impl/src/main/java/com/itsaky/androidide/templates/impl/pluginProject/pluginBuildGradle.kt`:
- Around line 56-58: The template currently hardcodes an APK package-id via the
aaptOptions block (additionalParameters("--package-id", "0x71",
"--allow-reserved-package-id")), which overrides
PluginBuilder.configurePackageId() auto-assignment and can cause resource ID
collisions; remove the entire aaptOptions block (the
additionalParameters("--package-id", "0x71", "--allow-reserved-package-id")
call) from pluginBuildGradle.kt so that PluginBuilder.configurePackageId() can
compute and assign unique package IDs per plugin.

---

Nitpick comments:
In
`@keystore-generator-plugin/src/main/res/layout/fragment_keystore_generator.xml`:
- Around line 269-271: The layout currently contains a fixed-height spacer View
(the anonymous <View> at the bottom of fragment_keystore_generator.xml) which
can cause inconsistent gaps across screens and IME states; remove that static
View and instead provide bottom spacing via the parent/container (e.g., add
paddingBottom to the root or action container) and handle system/IME insets (use
WindowInsets or ViewCompat.setOnApplyWindowInsetsListener on the root or
container) so the UI adapts to keyboard and different screen sizes while
preserving the intended bottom gap.

In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/base/PluginFragment.kt`:
- Around line 96-97: The two WeakReference assignments (activityContextRef and
activityThemeRef) in getPluginInflater are not atomic and can briefly point to
different activities if called concurrently; add a concise comment near these
assignments documenting the thread-safety assumption (e.g., getPluginInflater is
expected to be called on the main/UI thread or that both refs always originate
from the same Activity), and to harden the contract optionally annotate
getPluginInflater with `@MainThread` (or synchronize the update) so future
maintainers know the intended threading model; reference activityContextRef,
activityThemeRef, getPluginInflater and defaultInflater in the comment.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/loaders/PluginResourceContext.kt`:
- Around line 42-63: The catch-all in detectCustomPackageId currently swallows
all exceptions; narrow it to reflection-related failures by replacing catch (e:
Exception) with catch (e: ReflectiveOperationException) (and optionally catch
IllegalArgumentException if you expect invalid arg access) so only
reflection/class-loading errors from pluginClassLoader.loadClass("$pkg.R") and
subsequent field access are handled; keep the Log.w(TAG, "Failed to detect
custom package ID for $pkg", e) behavior and leave other exceptions to
propagate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 879422ae-7965-428e-af94-d0e371faa38d

📥 Commits

Reviewing files that changed from the base of the PR and between 19e414c and 28ed1d2.

📒 Files selected for processing (20)
  • apk-viewer-plugin/build.gradle.kts
  • apk-viewer-plugin/src/main/AndroidManifest.xml
  • keystore-generator-plugin/build.gradle.kts
  • keystore-generator-plugin/src/main/AndroidManifest.xml
  • keystore-generator-plugin/src/main/kotlin/com/appdevforall/keygen/plugin/fragments/KeystoreGeneratorFragment.kt
  • keystore-generator-plugin/src/main/res/layout/fragment_keystore_generator.xml
  • keystore-generator-plugin/src/main/res/values-night/colors.xml
  • keystore-generator-plugin/src/main/res/values/colors.xml
  • keystore-generator-plugin/src/main/res/values/strings.xml
  • keystore-generator-plugin/src/main/res/values/styles.xml
  • markdown-preview-plugin/build.gradle.kts
  • markdown-preview-plugin/src/main/AndroidManifest.xml
  • plugin-api/plugin-builder/src/main/kotlin/com/itsaky/androidide/plugins/build/PluginBuilder.kt
  • plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/IPlugin.kt
  • plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/base/PluginFragment.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/loaders/PluginLoader.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/loaders/PluginResourceContext.kt
  • templates-impl/src/main/java/com/itsaky/androidide/templates/impl/pluginProject/PluginTemplateData.kt
  • templates-impl/src/main/java/com/itsaky/androidide/templates/impl/pluginProject/pluginBuildGradle.kt

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/base/PluginFragment.kt`:
- Around line 21-31: The current global WeakReference fields activityThemeRef
and activityContextRef (and their accessors
getCurrentActivityTheme/getCurrentActivityContext) must be replaced with a
plugin-scoped snapshot map to avoid cross-plugin/activity leaks and
interleaving: introduce a Map keyed by pluginId (e.g., activitySnapshots:
MutableMap<String, Pair<WeakReference<Context>,
WeakReference<Resources.Theme>>>) and update getPluginInflater()/inflation path
to store and read the pair under the current pluginId instead of writing the two
globals; remove or stop using activityThemeRef/activityContextRef and change the
accessors to look up by pluginId. Also ensure you clear
activitySnapshots[pluginId] in unregisterPluginContext() and call
activitySnapshots.clear() in clearAllContexts() so snapshots are removed on
unload.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 02e7d470-d908-4e8d-8f93-f1f1a5b4d503

📥 Commits

Reviewing files that changed from the base of the PR and between 28ed1d2 and 3b6adf2.

📒 Files selected for processing (3)
  • keystore-generator-plugin/src/main/res/values/strings.xml
  • plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/base/PluginFragment.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt
✅ Files skipped from review due to trivial changes (1)
  • keystore-generator-plugin/src/main/res/values/strings.xml
🚧 Files skipped from review as they are similar to previous changes (1)
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt

Comment on lines +21 to +31
@Volatile
private var activityThemeRef: WeakReference<Resources.Theme>? = null

@Volatile
private var activityContextRef: WeakReference<Context>? = null

@JvmStatic
fun getCurrentActivityTheme(): Resources.Theme? = activityThemeRef?.get()

@JvmStatic
fun getCurrentActivityContext(): Context? = activityContextRef?.get()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't cache the host Context/Theme as a single global snapshot.

getPluginInflater() now overwrites one process-wide pair on every inflate, so later lookups can resolve resources against the wrong activity when multiple plugin fragments/activities are alive. Because Context and Theme are stored in separate mutable refs, callers can also observe a mismatched pair during interleaved updates. This also leaves stale host state hanging around after unload until GC happens. Prefer scoping the snapshot by pluginId (and clearing it on unregister/clear) or passing the host activity/theme through the consuming API instead of exposing global mutable state.

💡 One way to scope the snapshot correctly
+    private data class ActivitySnapshot(
+        val contextRef: WeakReference<Context>,
+        val themeRef: WeakReference<Resources.Theme>,
+    )
+
-    `@Volatile`
-    private var activityThemeRef: WeakReference<Resources.Theme>? = null
-
-    `@Volatile`
-    private var activityContextRef: WeakReference<Context>? = null
+    private val activitySnapshots = mutableMapOf<String, ActivitySnapshot>()
 
     `@JvmStatic`
-    fun getCurrentActivityTheme(): Resources.Theme? = activityThemeRef?.get()
+    fun getCurrentActivityTheme(pluginId: String): Resources.Theme? =
+        activitySnapshots[pluginId]?.themeRef?.get()
 
     `@JvmStatic`
-    fun getCurrentActivityContext(): Context? = activityContextRef?.get()
+    fun getCurrentActivityContext(pluginId: String): Context? =
+        activitySnapshots[pluginId]?.contextRef?.get()
 ...
     fun getPluginInflater(pluginId: String, defaultInflater: LayoutInflater): LayoutInflater {
         val pluginContext = getPluginContext(pluginId) ?: return defaultInflater
-        activityContextRef = WeakReference(defaultInflater.context)
-        activityThemeRef = WeakReference(defaultInflater.context.theme)
+        activitySnapshots[pluginId] =
+            ActivitySnapshot(
+                contextRef = WeakReference(defaultInflater.context),
+                themeRef = WeakReference(defaultInflater.context.theme),
+            )
         return defaultInflater.cloneInContext(pluginContext)
     }

Also clear activitySnapshots[pluginId] in unregisterPluginContext() and activitySnapshots.clear() in clearAllContexts().

Also applies to: 96-98

🤖 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/base/PluginFragment.kt`
around lines 21 - 31, The current global WeakReference fields activityThemeRef
and activityContextRef (and their accessors
getCurrentActivityTheme/getCurrentActivityContext) must be replaced with a
plugin-scoped snapshot map to avoid cross-plugin/activity leaks and
interleaving: introduce a Map keyed by pluginId (e.g., activitySnapshots:
MutableMap<String, Pair<WeakReference<Context>,
WeakReference<Resources.Theme>>>) and update getPluginInflater()/inflation path
to store and read the pair under the current pluginId instead of writing the two
globals; remove or stop using activityThemeRef/activityContextRef and change the
accessors to look up by pluginId. Also ensure you clear
activitySnapshots[pluginId] in unregisterPluginContext() and call
activitySnapshots.clear() in clearAllContexts() so snapshots are removed on
unload.

createDebugTask(target, extension)
createReleaseTask(target, extension)
configurePackageId(target)
createAssembleTask(target, extension, "debug")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe you could use a enum structure for the variants?

private fun detectCustomPackageId(): Boolean {
val cl = pluginClassLoader ?: return false
val pkg = pluginPackageInfo?.packageName ?: return false
try {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there any way to break this nested structure?

t.logger.lifecycle("Plugin assembled: ${outputFile.absolutePath}")
}
})
private fun generatePackageId(applicationId: String): Int {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you convert 0x7FFFFFFF, 0x7D and 0x02 into constants?

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