Skip to content

Use accessibility overlays for gesture zones - #3

Open
Dragonk wants to merge 5 commits into
tanujnotes:mainfrom
Dragonk:fix/accessibility-overlay
Open

Use accessibility overlays for gesture zones#3
Dragonk wants to merge 5 commits into
tanujnotes:mainfrom
Dragonk:fix/accessibility-overlay

Conversation

@Dragonk

@Dragonk Dragonk commented Aug 25, 2026

Copy link
Copy Markdown

Summary

Gesture windows have been migrated from TYPE_APPLICATION_OVERLAY to TYPE_ACCESSIBILITY_OVERLAY and are now owned by EdgeGestureAccessibilityService instead of a standalone foreground overlay service.

The window logic that lived in EdgeOverlayService (a foreground LifecycleService) was extracted into a reusable EdgeOverlayController that is created in EdgeGestureAccessibilityService.onServiceConnected() and torn down in onUnbind()/onDestroy(). Every gesture-related window — the touch zones, the back arrow, and the home handle — now uses WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY.

Why

Ogesture already depends on an AccessibilityService for the Back/Home/Recents navigation actions. Two problems came from drawing the gesture zones as ordinary application overlays:

  1. Protected screens hide them. Android Settings (and other secure system screens) set HIDE_NON_SYSTEM_OVERLAY_WINDOWS, which removes non-system application overlays — so Ogesture's gesture zones disappeared there and the user had to fall back to the phone's own navigation.
  2. A persistent system notification. Application overlays drawn with SYSTEM_ALERT_WINDOW cause Android's WindowManager to generate the ongoing "displaying over other apps" AlertWindowNotification for the app.

Because the gesture zones are an accessibility-driven feature owned by an already-enabled AccessibilityService, they fit TYPE_ACCESSIBILITY_OVERLAY: trusted windows that are not hidden on secure screens and do not require SYSTEM_ALERT_WINDOW. This uses the proper accessibility-overlay model for an accessibility-driven gesture feature — it does not bypass Android security; the accessibility service still requires explicit user consent.

What changed

  • New EdgeOverlayController owns the gesture-zone and indicator windows, created and destroyed by EdgeGestureAccessibilityService. It is decoupled from the concrete service through a small GestureDispatcher interface (trigger + replay).
  • TYPE_ACCESSIBILITY_OVERLAY for the touch-zone windows and for BackIndicator / HomeIndicator (new windowType constructor param defaulting to the accessibility overlay).
  • Removed SYSTEM_ALERT_WINDOW from the manifest, and all Settings.canDrawOverlays(...) checks, the ACTION_MANAGE_OVERLAY_PERMISSION setup flow, the "Display over other apps" setup requirement, and the gestures-off overlay failure path.
  • Removed the obsolete foreground overlay service EdgeOverlayService and its notification channel/running notification.
  • Removed BootReceiver — it only revived EdgeOverlayService. Android rebinds the AccessibilityService after process death/reboot on its own, and the controller re-attaches zones if the master switch is still on, so the boot receiver is no longer needed.
  • Removed obsolete permissions FOREGROUND_SERVICE, FOREGROUND_SERVICE_SPECIAL_USE, POST_NOTIFICATIONS, and RECEIVE_BOOT_COMPLETED, plus the specialUse service metadata and the androidx.lifecycle.service dependency.
  • Setup UI simplified — the setup card lists Accessibility + unrestricted battery only; enabling gestures depends only on the service being bound and unrestricted battery. The in-app "gestures won't work on Settings" notice and the related remember bullet were removed/rewritten.
  • Documentation updated — README "How it works" and the PRIVACY permission table reflect the accessibility-overlay architecture and no longer mention Display-over-apps, the foreground service, or the Settings limitation.
  • Tests updated — added pure-data regression tests for the gesture-zone set, swipe-direction mapping, and Back/Home/Recents action wiring; the instrumented test accepts the debug applicationId suffix.

Accessibility privacy is unchanged: canRetrieveWindowContent stays false, and only the foreground package name is read (for per-app pass-through) — no window-content scraping, no broadened event collection.

Behavior preserved

  • Left-edge and right-edge Back
  • Bottom Home
  • Bottom swipe-and-hold Recents
  • Gesture visual indicators (back arrow peek, home handle lift)
  • Haptic feedback
  • Per-app excluded / pass-through behavior
  • Unused-touch replay via dispatchGesture
  • The replay/interactivity workarounds (held-zone untouchable+alpha 0, indicator-hide-before-inject grace) — preserved verbatim; the behaviour change is limited to the window type and ownership
  • Taps and long presses near gesture zones
  • Keyboard foreground-tracking behavior
  • Rotation / display-geometry changes (re-lays out zones)
  • Service reconnect does not create duplicate windows (rebuild() detaches all first)

Validation

./gradlew testDebugUnitTest   -> BUILD SUCCESSFUL (unit tests pass, incl. new GestureZoneLayoutTest)
./gradlew lintDebug           -> BUILD SUCCESSFUL (0 errors, 0 fatals; 19 warnings, all pre-existing on main)
./gradlew assembleDebug       -> BUILD SUCCESSFUL
./gradlew assembleRelease     -> BUILD SUCCESSFUL

Static verification: production code has no functional dependency on TYPE_APPLICATION_OVERLAY, SYSTEM_ALERT_WINDOW, Settings.canDrawOverlays, or ACTION_MANAGE_OVERLAY_PERMISSION (the only remaining mentions are explanatory KDoc comments in EdgeOverlayController). The merged release manifest requests no SYSTEM_ALERT_WINDOW, FOREGROUND_SERVICE/FOREGROUND_SERVICE_SPECIAL_USE, POST_NOTIFICATIONS, or RECEIVE_BOOT_COMPLETED; the only service declared is the accessibility service and there is no receiver. Instrumentation tests were not run (no device available to the build environment).

Real-device validation

Tested on a real Xiaomi/HyperOS Android device using a locally built release APK (signed with the standard Android debug keystore; no signing key was created or committed). Confirmed:

  • edge gestures remain functional inside Android Settings and protected Settings screens that previously hid the application overlays;
  • gesture overlays are no longer suppressed as ordinary application overlays on those screens;
  • the persistent Android "displaying over other apps" system notification for Ogesture is gone.

This directly validates the two main user-facing goals of the migration.

Built test APKs are written to dist/ (gitignored, untracked). No APKs,
signing keys, or build artifacts are committed.
The gesture touch zones and visual indicators were TYPE_APPLICATION_OVERLAY
windows owned by a standalone foreground LifecycleService (EdgeOverlayService).
Those application overlays are hidden by HIDE_NON_SYSTEM_OVERLAY_WINDOWS on
secure system screens (Settings, SubSettings), so gestures stopped working
there, and Android showed the persistent "displaying over other apps"
notification because of SYSTEM_ALERT_WINDOW.

Extract the window logic from EdgeOverlayService into a reusable
EdgeOverlayController that is created, owned, and destroyed by the already-
enabled EdgeGestureAccessibilityService. Every window the controller and the
indicators add now uses WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY,
bound to the active AccessibilityService lifecycle: trusted windows that are
not hidden on secure screens and do not require SYSTEM_ALERT_WINDOW.

The controller is decoupled from the concrete service through a
GestureDispatcher interface (trigger + replay), keeping it testable. The
existing replay/interactivity workarounds (held-zone untouchable+alpha 0,
indicator-hide-before-inject grace) are preserved verbatim; behaviour change
is limited to the window type and ownership.

Remove the now-obsolete foreground service and boot receiver:
- delete EdgeOverlayService (foreground service, notification channel,
  canDrawOverlays watchdog, start/stop helpers)
- delete BootReceiver (only revived EdgeOverlayService; the AccessibilityService
  is re-bound by Android on process death/reboot without it)
- drop SYSTEM_ALERT_WINDOW, FOREGROUND_SERVICE, FOREGROUND_SERVICE_SPECIAL_USE,
  POST_NOTIFICATIONS, RECEIVE_BOOT_COMPLETED from the manifest
- drop the specialUse service metadata and the EdgeOverlayService declaration
- drop the androidx.lifecycle.service / lifecycle-service catalog dependency
  (no LifecycleService remains)

The accessibility service watchdog now only re-checks unrestricted battery and
re-asserts the controller; the canDrawOverlays failure path is gone. Accessibility
privacy is unchanged (canRetrieveWindowContent stays false; only the foreground
package name is read for per-app pass-through). Lifecycle on service
bind/unbind/rebind, configuration and display-geometry changes, and rotation are
handled by the controller via the owner service.
With gesture windows owned by the AccessibilityService as TYPE_ACCESSIBILITY_OVERLAY,
SYSTEM_ALERT_WINDOW / canDrawOverlays / ACTION_MANAGE_OVERLAY_PERMISSION are no
longer needed. Remove that requirement from the setup card and the master-switch
gating, the periodic overlay-permission re-check, and the gestures-off overlay
toast path. Enabling gestures now depends only on the AccessibilityService being
bound and unrestricted battery usage.

- MainActivity: remove overlayGranted state, its ON_RESUME/1s poll, the
  ACTION_MANAGE_OVERLAY_PERMISSION intent, and the offReason overlay branch;
  canEnable no longer requires overlay. Replace the "restricted screens"
  remember bullet with an on-device/local-only one (the Settings limitation
  this change fixes is no longer true).
- PermissionsCard: drop the overlay RequirementRow and the onRequestOverlay
  callback; the card now lists accessibility + battery only.
- MainViewModel.setMasterEnabled: just write the datastore flag — the
  AccessibilityService observes the flow and attaches/detaches zones itself,
  so there is no foreground service to start/stop.
- CompatibilityScreen: drop the "System screens" note claiming gestures won't
  work on Settings (no longer accurate).
- strings: remove notification channel/running, permission_overlay,
  permission_grant, toast_gestures_off_overlay, compat_settings_*; reword the
  remember bullet to remember_on_device.
- ExampleInstrumentedTest: accept the .debug applicationId suffix produced by
  the debug build type alongside com.ogesture.
- README "How it works": gesture overlays are accessibility-overlay windows
  owned by Ogesture's accessibility service, so they work on secure system
  screens and do not require "Display over other apps". Permissions list
  reduced from three to two (accessibility + unrestricted battery). Remove the
  known limitation that gestures cannot work on Settings pages — this change
  addresses it.
- PRIVACY: permission table no longer lists SYSTEM_ALERT_WINDOW,
  FOREGROUND_SERVICE/FOREGROUND_SERVICE_SPECIAL_USE, POST_NOTIFICATIONS, or
  RECEIVE_BOOT_COMPLETED; accessibility now owns the edge overlays and performs
  the navigation actions.
Pure-data regression guards for the zone set (bottom/left/right), the
swipe-direction mapping per zone, the Back/Home/Recents action wiring (incl.
the bottom zone's swipe-and-hold Recents long action), and that every zone
has positive thickness/length. The accessibility-overlay migration must
preserve all of this; a silent change to the layout or action wiring now
fails here.
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.

1 participant