Skip to content

Conversation

@parallelcc
Copy link
Owner

@parallelcc parallelcc commented Nov 2, 2025

User description

Fix #121


PR Type

Enhancement


Description

  • Add NavBarEventHelperHooker to intercept long press gestures

  • Hook into NavBarEventHelper's onLongPress method

  • Trigger circle-to-search on long press when gesture enabled

  • Integrate hooker into ModuleMain initialization chain


Diagram Walkthrough

flowchart LR
  ModuleMain["ModuleMain initialization"] -- "registers hooker" --> NavBarEventHelperHooker["NavBarEventHelperHooker"]
  NavBarEventHelperHooker -- "hooks onLongPress" --> OnLongPressHooker["OnLongPressHooker"]
  OnLongPressHooker -- "triggers on config enabled" --> CircleToSearch["Circle-to-search action"]
Loading

File Walkthrough

Relevant files
Enhancement
ModuleMain.kt
Register NavBarEventHelperHooker in module initialization

app/src/main/java/com/parallelc/micts/ModuleMain.kt

  • Import NavBarEventHelperHooker class
  • Add hooker registration in initialization chain with error handling
  • Chain after CircleToSearchHelper hook with recoverCatching pattern
+5/-0     
NavBarEventHelperHooker.kt
New NavBarEventHelper hooker for gesture interception       

app/src/main/java/com/parallelc/micts/hooker/NavBarEventHelperHooker.kt

  • New hooker class to intercept NavBarEventHelper long press events
  • Hooks onLongPress method with BeforeInvocation callback
  • Checks gesture trigger config and triggers circle-to-search action
  • Supports vibration feedback based on configuration
+52/-0   

@qodo-code-review
Copy link

qodo-code-review bot commented Nov 2, 2025

PR Compliance Guide 🔍

(Compliance updated until commit 7c3ff84)

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🟡
🎫 #121
🟢 The hook should intercept the correct long-press gesture path used by Mi Pad
launcher/navigation (not just home button).
The solution should respect the app’s configuration for enabling gesture-trigger and
vibration settings.
Integration into module initialization so the hook is registered during app/package load.
Long-pressing the gesture handle should trigger Circle-to-Search (CTS) on Xiaomi Pad 7 Pro
(Android 15, OS2.0.206) when Google is the default assistant and no background
restrictions exist.
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing auditing: The new long-press interception and action triggering does not emit any audit logs
capturing who/what triggered the action, timestamp, or outcome.

Referred Code
companion object {
    @JvmStatic
    @BeforeInvocation
    fun before(callback: BeforeHookCallback) {
        val prefs = module!!.getRemotePreferences(CONFIG_NAME)
        if (prefs.getBoolean(KEY_GESTURE_TRIGGER, DEFAULT_CONFIG[KEY_GESTURE_TRIGGER] as Boolean)) {
            val context = runCatching { mContext.get(callback.thisObject) as? Context }.getOrNull()
            triggerCircleToSearch(
                1,
                context,
                prefs.getBoolean(
                    KEY_VIBRATE,
                    DEFAULT_CONFIG[KEY_VIBRATE] as Boolean
                )
            )
            callback.returnAndSkip(null)
        }
    }
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Edge cases unhandled: The hook assumes availability of context and preferences without fallbacks or logging on
failure, and does not handle null MotionEvent or trigger failures explicitly.

Referred Code
val prefs = module!!.getRemotePreferences(CONFIG_NAME)
if (prefs.getBoolean(KEY_GESTURE_TRIGGER, DEFAULT_CONFIG[KEY_GESTURE_TRIGGER] as Boolean)) {
    val context = runCatching { mContext.get(callback.thisObject) as? Context }.getOrNull()
    triggerCircleToSearch(
        1,
        context,
        prefs.getBoolean(
            KEY_VIBRATE,
            DEFAULT_CONFIG[KEY_VIBRATE] as Boolean
        )
    )
    callback.returnAndSkip(null)
}
Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Reflection risk: Reflection access to mContext and method hooking lacks validation/guards which could fail
or change across versions, and external preferences are trusted without validation.

Referred Code
fun hook(param: PackageLoadedParam) {
    val navStubGestureEventManager = param.classLoader.loadClass("com.miui.home.recents.cts.NavBarEventHelper")
    mContext = navStubGestureEventManager.getDeclaredField("mContext")
    mContext.isAccessible = true
    module!!.hook(navStubGestureEventManager.getDeclaredMethod("onLongPress", MotionEvent::class.java), OnLongPressHooker::class.java)
}
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

Previous compliance checks

Compliance check up to commit 7c3ff84
Security Compliance
Fragile reflection access

Description: Reflection is used to access the private field mContext of
com.miui.home.recents.cts.NavBarEventHelper and force it accessible, which may break
across versions and could trigger crashes or unintended behavior if the target class/field
signature changes.
NavBarEventHelperHooker.kt [20-27]

Referred Code
private lateinit var mContext: Field

fun hook(param: PackageLoadedParam) {
    val navStubGestureEventManager = param.classLoader.loadClass("com.miui.home.recents.cts.NavBarEventHelper")
    mContext = navStubGestureEventManager.getDeclaredField("mContext")
    mContext.isAccessible = true
    module!!.hook(navStubGestureEventManager.getDeclaredMethod("onLongPress", MotionEvent::class.java), OnLongPressHooker::class.java)
}
Ticket Compliance
🟡
🎫 #121
🟢 Hook the appropriate MIUI/Launcher component handling gesture long-press to invoke CTS.
Ensure the behavior respects the app’s configuration for enabling gesture trigger.
Provide vibration feedback on trigger when the vibration config is enabled.
Maintain other trigger methods (home button, manual launch) unaffected.
Solution should work on Android 15 / MIUI OS2 environment (device-specific context
resolution may be required).
Long-press on the gesture handle should trigger Circle-to-Search (CTS) on Xiaomi Pad 7
Pro.
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing audit logs: The new long-press interception and circle-to-search trigger perform a critical user
action without adding any structured audit logging for who/when/what outcome.

Referred Code
fun before(callback: BeforeHookCallback) {
    val prefs = module!!.getRemotePreferences(CONFIG_NAME)
    if (prefs.getBoolean(KEY_GESTURE_TRIGGER, DEFAULT_CONFIG[KEY_GESTURE_TRIGGER] as Boolean)) {
        val context = runCatching { mContext.get(callback.thisObject) as? Context }.getOrNull()
        triggerCircleToSearch(
            1,
            context,
            prefs.getBoolean(
                KEY_VIBRATE,
                DEFAULT_CONFIG[KEY_VIBRATE] as Boolean
            )
        )
        callback.returnAndSkip(null)
    }
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Edge cases unhandled: The hook assumes presence of field mContext and method onLongPress without guarding for
reflection failures or null context beyond a silent getOrNull, lacking actionable error
handling.

Referred Code
fun hook(param: PackageLoadedParam) {
    val navStubGestureEventManager = param.classLoader.loadClass("com.miui.home.recents.cts.NavBarEventHelper")
    mContext = navStubGestureEventManager.getDeclaredField("mContext")
    mContext.isAccessible = true
    module!!.hook(navStubGestureEventManager.getDeclaredMethod("onLongPress", MotionEvent::class.java), OnLongPressHooker::class.java)
}
Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Preference trust: External preference values are used to control behavior without validation and the hook
triggers an action based on MotionEvent without input sanitization checks.

Referred Code
val prefs = module!!.getRemotePreferences(CONFIG_NAME)
if (prefs.getBoolean(KEY_GESTURE_TRIGGER, DEFAULT_CONFIG[KEY_GESTURE_TRIGGER] as Boolean)) {
    val context = runCatching { mContext.get(callback.thisObject) as? Context }.getOrNull()
    triggerCircleToSearch(
        1,
        context,
        prefs.getBoolean(
            KEY_VIBRATE,
            DEFAULT_CONFIG[KEY_VIBRATE] as Boolean
        )
    )

@qodo-code-review
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Avoid using a static reflected field

Avoid using a static lateinit var for the mContext field by resolving it
directly within the before hook to prevent potential
UninitializedPropertyAccessException and improve robustness.

app/src/main/java/com/parallelc/micts/hooker/NavBarEventHelperHooker.kt [18-52]

 class NavBarEventHelperHooker {
     companion object {
-        private lateinit var mContext: Field
-
         fun hook(param: PackageLoadedParam) {
             val navStubGestureEventManager = param.classLoader.loadClass("com.miui.home.recents.cts.NavBarEventHelper")
-            mContext = navStubGestureEventManager.getDeclaredField("mContext")
-            mContext.isAccessible = true
             module!!.hook(navStubGestureEventManager.getDeclaredMethod("onLongPress", MotionEvent::class.java), OnLongPressHooker::class.java)
         }
 
         @XposedHooker
         class OnLongPressHooker : Hooker {
             companion object {
                 @JvmStatic
                 @BeforeInvocation
                 fun before(callback: BeforeHookCallback) {
                     val prefs = module!!.getRemotePreferences(CONFIG_NAME)
                     if (prefs.getBoolean(KEY_GESTURE_TRIGGER, DEFAULT_CONFIG[KEY_GESTURE_TRIGGER] as Boolean)) {
-                        val context = runCatching { mContext.get(callback.thisObject) as? Context }.getOrNull()
+                        val context = runCatching {
+                            val field = callback.thisObject.javaClass.getDeclaredField("mContext")
+                            field.isAccessible = true
+                            field.get(callback.thisObject) as? Context
+                        }.getOrNull()
                         triggerCircleToSearch(
                             1,
                             context,
                             prefs.getBoolean(
                                 KEY_VIBRATE,
                                 DEFAULT_CONFIG[KEY_VIBRATE] as Boolean
                             )
                         )
                         callback.returnAndSkip(null)
                     }
                 }
             }
         }
     }
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies a design flaw with using a static lateinit var for a reflected field, which could lead to an UninitializedPropertyAccessException, and proposes a more robust, self-contained solution.

Medium
Possible issue
Add null check before use

Add a null check for the context variable before passing it to
triggerCircleToSearch to prevent a potential NullPointerException.

app/src/main/java/com/parallelc/micts/hooker/NavBarEventHelperHooker.kt [37-46]

 val context = runCatching { mContext.get(callback.thisObject) as? Context }.getOrNull()
-triggerCircleToSearch(
-    1,
-    context,
-    prefs.getBoolean(
-        KEY_VIBRATE,
-        DEFAULT_CONFIG[KEY_VIBRATE] as Boolean
+context?.let {
+    triggerCircleToSearch(
+        1,
+        it,
+        prefs.getBoolean(
+            KEY_VIBRATE,
+            DEFAULT_CONFIG[KEY_VIBRATE] as Boolean
+        )
     )
-)
-callback.returnAndSkip(null)
+    callback.returnAndSkip(null)
+}
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential NullPointerException if the context is null and provides an idiomatic Kotlin solution, improving the code's robustness.

Medium
  • More

@parallelcc parallelcc merged commit 67396a4 into main Nov 2, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] [Mi Pad] Trigger by long pressing gesture handle doesn't work

2 participants