Skip to content

ADFA-3218: use correct window context for debug overlay#1293

Open
itsaky-adfa wants to merge 2 commits into
stagefrom
fix/ADFA-3218
Open

ADFA-3218: use correct window context for debug overlay#1293
itsaky-adfa wants to merge 2 commits into
stagefrom
fix/ADFA-3218

Conversation

@itsaky-adfa
Copy link
Copy Markdown
Contributor

@itsaky-adfa itsaky-adfa commented May 12, 2026

See ADFA-3218 for more details.

@itsaky-adfa itsaky-adfa requested a review from a team May 12, 2026 15:59
@itsaky-adfa itsaky-adfa self-assigned this May 12, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR enables the debugger overlay to follow the application across multiple displays. DebugOverlayManager now accepts a display ID and creates a themed context for that specific display. DebuggerService dynamically creates overlay managers on-demand and adds a public method to relocate overlays. BaseEditorActivity starts the service with the current display ID and triggers relocation on resume. UI Designer result handling is refactored for explicit error flow and logging.

Changes

Display-aware debugger overlay management

Layer / File(s) Summary
DebugOverlayManager display support
app/src/main/java/com/itsaky/androidide/services/debug/DebugOverlayManager.kt
Constructor now accepts and stores attachedDisplayId. Factory method create(ctx, displayId) selects the target display using DisplayManagerCompat, builds a ContextThemeWrapper for API-level compatibility via isAtLeastR, and instantiates the manager with the display ID. Show/hide behavior and action refresh remain unchanged.
DebuggerService dynamic overlay management
app/src/main/java/com/itsaky/androidide/services/debug/DebuggerService.kt
overlayManager becomes nullable and initialized to null. New EXTRA_DISPLAY_ID intent constant. New public maybeMoveOverlayToDisplay(displayId) hides the current overlay, recreates the manager if the display differs, and shows it again. New createOverlayManagerIfNeeded(displayId) handles manager creation/recreation. New detachOverlay() safely hides the overlay with error handling. onBind reads displayId from extras and creates the manager. onDestroy detaches the overlay before cleanup. Show/hide/refresh operations use null-safe calls.
BaseEditorActivity overlay movement and UI Designer refactoring
app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt
Imports added for display helpers (ContextCompat, DisplayManagerCompat). Debugger service intent now includes the current display ID via ContextCompat.getDisplayOrDefault(...).displayId. onResume calls maybeMoveOverlayToDisplay(displayId) in runCatching to relocate the overlay on display change. handleUiDesignerResult refactored to validate result/data early with log.warn and return, only apply generated XML after handleStringsInjection succeeds, and surface injection failures via log.error and flashError. Code readability improvements throughout (reformatted imports, binding property, IME inset handling, bottom-sheet pivot, gesture dismissal logic, drawer gating, and various expression reflows) preserve existing semantics.

Sequence Diagram(s)

sequenceDiagram
  participant Activity as BaseEditorActivity
  participant Service as DebuggerService
  participant Manager as DebugOverlayManager
  Activity->>Service: startService(intent + displayId)
  Service->>Service: onBind(intent)
  Service->>Service: createOverlayManagerIfNeeded(displayId)
  Service->>Manager: create(ctx, displayId)
  Manager-->>Service: overlay manager
  Service-->>Activity: service binder
  Activity->>Service: maybeMoveOverlayToDisplay(newDisplayId)
  Service->>Service: hide current overlay
  Service->>Service: createOverlayManagerIfNeeded(newDisplayId)
  Service->>Manager: create(ctx, newDisplayId)
  Manager-->>Service: new overlay manager
  Service->>Service: show overlay on new display
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • appdevforall/CodeOnTheGo#953: Modifies DebugOverlayManager drag/touch handling and tooltip behavior, overlapping with overlay manager structural changes.
  • appdevforall/CodeOnTheGo#1134: Modifies BaseEditorActivity.onResume and immersiveController ordering, affecting the same lifecycle hook where overlay relocation is now performed.
  • appdevforall/CodeOnTheGo#1043: Gates debugger overlay visibility to foreground state in both BaseEditorActivity and DebuggerService, complementary to this display-aware overlay management.

Suggested reviewers

  • dara-abijo-adfa
  • Daniel-ADFA

🐰 A rabbit's ode to displays so bright,
Where overlays now dance in perfect flight!
From screen to screen they smoothly roam,
Finding every window their new home!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'use correct window context for debug overlay' clearly and specifically summarizes the main change across all modified files.
Description check ✅ Passed The description references the Jira ticket ADFA-3218 which documents the purpose of these changes, establishing a clear connection to the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 fix/ADFA-3218

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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
Copy Markdown
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 (4)
app/src/main/java/com/itsaky/androidide/services/debug/DebuggerService.kt (2)

124-130: ⚡ Quick win

Simplify overlay relocation logic.

The maybeMoveOverlayToDisplay method hides the overlay before calling createOverlayManagerIfNeeded, which also hides the overlay if the displayId changed (lines 134). This results in hideOverlay() being called twice when relocating to a new display.

♻️ Simplify to avoid double hide
 fun maybeMoveOverlayToDisplay(displayId: Int) {
   if (overlayManager?.attachedDisplayId == displayId) return
 
-  hideOverlay()
   createOverlayManagerIfNeeded(displayId)
   showOverlay()
 }

The createOverlayManagerIfNeeded method already handles hiding when needed (line 134).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/itsaky/androidide/services/debug/DebuggerService.kt`
around lines 124 - 130, The method maybeMoveOverlayToDisplay redundantly calls
hideOverlay() before createOverlayManagerIfNeeded, causing hideOverlay to run
twice when the display actually changes; remove the initial hideOverlay() call
in maybeMoveOverlayToDisplay and rely on createOverlayManagerIfNeeded(displayId)
(which already hides the overlay when the displayId differs from
overlayManager?.attachedDisplayId) and then call showOverlay() as before so the
overlay is only hidden once during relocation.

132-145: ⚡ Quick win

Redundant null check in overlay manager recreation.

Lines 133-136 check if the displayId differs and hide/null the manager, but line 138 immediately checks if (overlayManager == null). The second check is redundant since we just set it to null. Consider restructuring for clarity.

♻️ Simplify the logic
 private fun createOverlayManagerIfNeeded(displayId: Int) {
   if (overlayManager?.attachedDisplayId != displayId) {
     hideOverlay()
-    overlayManager = null
-  }
-
-  if (overlayManager == null) {
     overlayManager =
       DebugOverlayManager.create(
         ctx = this,
         displayId = displayId
       )
   }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/itsaky/androidide/services/debug/DebuggerService.kt`
around lines 132 - 145, The createOverlayManagerIfNeeded function contains a
redundant null check: instead of first checking
overlayManager?.attachedDisplayId != displayId then setting overlayManager =
null and later checking if (overlayManager == null), simplify the flow by
checking whether overlayManager exists and has a matching attachedDisplayId; if
it doesn't, call hideOverlay(), set overlayManager = null, and then
unconditionally create a new manager via DebugOverlayManager.create with ctx =
this and displayId; update references to overlayManager, attachedDisplayId,
hideOverlay, and DebugOverlayManager.create in the createOverlayManagerIfNeeded
method to reflect this simplified logic.
app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt (1)

899-904: ⚡ Quick win

Consider more specific exception handling for overlay relocation.

The runCatching block (line 900) catches all exceptions when moving the debugger overlay. While this prevents crashes, it may mask specific issues like SecurityException if overlay permissions change, or IllegalStateException if the service is not bound. Consider catching specific exception types to aid debugging.

🛡️ More specific exception handling
-runCatching {
-  debuggerService?.maybeMoveOverlayToDisplay(displayId)
-}.onFailure { err ->
-  log.warn("Unable to move debugger overlay to display {}", displayId, err)
-}
+try {
+  debuggerService?.maybeMoveOverlayToDisplay(displayId)
+} catch (e: SecurityException) {
+  log.error("Overlay permission denied when moving to display {}", displayId, e)
+} catch (e: IllegalStateException) {
+  log.warn("Unable to move debugger overlay to display {} (service state issue)", displayId, e)
+} catch (e: Exception) {
+  log.warn("Unable to move debugger overlay to display {}", displayId, e)
+}

Based on learnings, prefer narrow exception handling that catches specific exception types instead of broad catch-all blocks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt`
around lines 899 - 904, The runCatching that wraps
debuggerService?.maybeMoveOverlayToDisplay(displayId) is too broad; replace it
with explicit try/catch around the call in BaseEditorActivity (the block using
ContextCompat.getDisplayOrDefault and maybeMoveOverlayToDisplay) and handle
expected exceptions separately — e.g., catch SecurityException (log
permission-specific message and suggestion), IllegalStateException (log service
binding/state info), and optionally RemoteException/RuntimeException if the
debuggerService is remote — logging the displayId and full error for each;
rethrow or propagate only truly unexpected exceptions if appropriate.
app/src/main/java/com/itsaky/androidide/services/debug/DebugOverlayManager.kt (1)

173-189: ⚡ Quick win

Consider handling display not found more explicitly.

When displayManager.getDisplay(displayId) returns null (line 182), the code falls back to the base context. While this provides safe fallback behavior, the log message at line 180 claims to be "trying to get window context" but doesn't indicate whether the display was actually found. Consider logging when the display is not found to aid debugging.

📝 Suggested logging improvement
 val displayManager = DisplayManagerCompat.getInstance(ctx)
 val targetDisplay = displayManager.getDisplay(displayId)
+if (targetDisplay == null) {
+  logger.warn("Display {} not found, falling back to default display", displayId)
+}
 val displayContext =
   if (targetDisplay != null) ctx.createDisplayContext(targetDisplay) else ctx
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/main/java/com/itsaky/androidide/services/debug/DebugOverlayManager.kt`
around lines 173 - 189, The create function's windowContext resolution logs an
attempt to get a display but never logs whether
DisplayManagerCompat.getInstance(ctx).getDisplay(displayId) actually returned a
display; update the logic around targetDisplay/getDisplay to emit an explicit
logger.debug or logger.warn when targetDisplay is null (e.g., "display not found
for id={}" with displayId) and when it is found (e.g., "using display id={}"),
so developers can see if the fallback to ctx occurred; keep the existing
fallback to ctx/createDisplayContext and retain the isAtLeastR() branch for
createWindowContext.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt`:
- Around line 899-904: The runCatching that wraps
debuggerService?.maybeMoveOverlayToDisplay(displayId) is too broad; replace it
with explicit try/catch around the call in BaseEditorActivity (the block using
ContextCompat.getDisplayOrDefault and maybeMoveOverlayToDisplay) and handle
expected exceptions separately — e.g., catch SecurityException (log
permission-specific message and suggestion), IllegalStateException (log service
binding/state info), and optionally RemoteException/RuntimeException if the
debuggerService is remote — logging the displayId and full error for each;
rethrow or propagate only truly unexpected exceptions if appropriate.

In `@app/src/main/java/com/itsaky/androidide/services/debug/DebuggerService.kt`:
- Around line 124-130: The method maybeMoveOverlayToDisplay redundantly calls
hideOverlay() before createOverlayManagerIfNeeded, causing hideOverlay to run
twice when the display actually changes; remove the initial hideOverlay() call
in maybeMoveOverlayToDisplay and rely on createOverlayManagerIfNeeded(displayId)
(which already hides the overlay when the displayId differs from
overlayManager?.attachedDisplayId) and then call showOverlay() as before so the
overlay is only hidden once during relocation.
- Around line 132-145: The createOverlayManagerIfNeeded function contains a
redundant null check: instead of first checking
overlayManager?.attachedDisplayId != displayId then setting overlayManager =
null and later checking if (overlayManager == null), simplify the flow by
checking whether overlayManager exists and has a matching attachedDisplayId; if
it doesn't, call hideOverlay(), set overlayManager = null, and then
unconditionally create a new manager via DebugOverlayManager.create with ctx =
this and displayId; update references to overlayManager, attachedDisplayId,
hideOverlay, and DebugOverlayManager.create in the createOverlayManagerIfNeeded
method to reflect this simplified logic.

In
`@app/src/main/java/com/itsaky/androidide/services/debug/DebugOverlayManager.kt`:
- Around line 173-189: The create function's windowContext resolution logs an
attempt to get a display but never logs whether
DisplayManagerCompat.getInstance(ctx).getDisplay(displayId) actually returned a
display; update the logic around targetDisplay/getDisplay to emit an explicit
logger.debug or logger.warn when targetDisplay is null (e.g., "display not found
for id={}" with displayId) and when it is found (e.g., "using display id={}"),
so developers can see if the fallback to ctx occurred; keep the existing
fallback to ctx/createDisplayContext and retain the isAtLeastR() branch for
createWindowContext.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d30844e1-1710-43c9-9bc0-14f776b7e38c

📥 Commits

Reviewing files that changed from the base of the PR and between dfb2648 and ea10271.

📒 Files selected for processing (3)
  • app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt
  • app/src/main/java/com/itsaky/androidide/services/debug/DebugOverlayManager.kt
  • app/src/main/java/com/itsaky/androidide/services/debug/DebuggerService.kt

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