Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit be4f3aeb authored by Hiroki Sato's avatar Hiroki Sato
Browse files

Introduce AccessibilityPointerMotionFilter

This introduces a new input event filter only for filtering motion
events from cursor devices.
This allows the filter to consume some amount of the relative motion
events before the curosor location in the screen is determined by
PointerChoreographer.
Accessibility fullscreen magnification will use this filter.

Bug: 379646448
Bug: 361817142
Test: Manually tested with feature changes that comes later
Flag: com.android.server.accessibility.enable_magnification_follows_mouse_with_pointer_motion_filter
Change-Id: Ic9cea2b9a894fd2259d7ae8c60abe5ea50cf87d9
parent b5400df9
Loading
Loading
Loading
Loading
+38 −0
Original line number Original line Diff line number Diff line
@@ -344,4 +344,42 @@ public abstract class InputManagerInternal {
     */
     */
    public abstract void applyBackupPayload(Map<Integer, byte[]> payload, int userId)
    public abstract void applyBackupPayload(Map<Integer, byte[]> payload, int userId)
            throws XmlPullParserException, IOException;
            throws XmlPullParserException, IOException;

    /**
     * An interface for filtering pointer motion event before cursor position is determined.
     * <p>
     * Different from {@code android.view.InputFilter}, this filter can filter motion events at
     * an early stage of the input pipeline, but only called for pointer's relative motion events.
     * Unless the user really needs to filter events before the cursor position in the display is
     * determined, use {@code android.view.InputFilter} instead.
     */
    public interface AccessibilityPointerMotionFilter {
        /**
         * Called everytime pointer's relative motion event happens.
         * The returned dx and dy will be used to move the cursor in the display.
         * <p>
         * This call happens on the input hot path and it is extremely performance sensitive. It
         * also must not call back into native code.
         *
         * @param dx        delta x of the event in pixels.
         * @param dy        delta y of the event in pixels.
         * @param currentX  the cursor x coordinate on the screen before the motion event.
         * @param currentY  the cursor y coordinate on the screen before the motion event.
         * @param displayId the display ID of the current cursor.
         * @return an array of length 2, delta x and delta y after filtering the motion. The delta
         *         values are in pixels and must be between 0 and original delta.
         */
        @NonNull
        float[] filterPointerMotionEvent(float dx, float dy, float currentX, float currentY,
                int displayId);
    }

    /**
     * Registers an {@code AccessibilityCursorFilter}.
     *
     * @param filter The filter to register. If a filter is already registered, the old filter is
     *               unregistered. {@code null} unregisters the filter that is already registered.
     */
    public abstract void registerAccessibilityPointerMotionFilter(
            @Nullable AccessibilityPointerMotionFilter filter);
}
}
+52 −1
Original line number Original line Diff line number Diff line
@@ -25,8 +25,8 @@ import static android.view.KeyEvent.KEYCODE_UNKNOWN;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;


import static com.android.hardware.input.Flags.enableCustomizableInputGestures;
import static com.android.hardware.input.Flags.enableCustomizableInputGestures;
import static com.android.hardware.input.Flags.touchpadVisualizer;
import static com.android.hardware.input.Flags.keyEventActivityDetection;
import static com.android.hardware.input.Flags.keyEventActivityDetection;
import static com.android.hardware.input.Flags.touchpadVisualizer;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER;
import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER;


@@ -460,6 +460,14 @@ public class InputManagerService extends IInputManager.Stub
    private boolean mShowKeyPresses = false;
    private boolean mShowKeyPresses = false;
    private boolean mShowRotaryInput = false;
    private boolean mShowRotaryInput = false;


    /**
     * A lock for the accessibility pointer motion filter. Don't call native methods while holding
     * this lock.
     */
    private final Object mAccessibilityPointerMotionFilterLock = new Object();
    private InputManagerInternal.AccessibilityPointerMotionFilter
            mAccessibilityPointerMotionFilter = null;

    /** Point of injection for test dependencies. */
    /** Point of injection for test dependencies. */
    @VisibleForTesting
    @VisibleForTesting
    static class Injector {
    static class Injector {
@@ -2591,6 +2599,23 @@ public class InputManagerService extends IInputManager.Stub
        }
        }
    }
    }


    // Native callback.
    @SuppressWarnings("unused")
    final float[] filterPointerMotion(float dx, float dy, float currentX, float currentY,
            int displayId) {
        // This call happens on the input hot path and it is extremely performance sensitive.
        // This must not call back into native code. This is called while the
        // PointerChoreographer's lock is held.
        synchronized (mAccessibilityPointerMotionFilterLock) {
            if (mAccessibilityPointerMotionFilter == null) {
                throw new IllegalStateException(
                        "filterCursor is invoked but no callback is registered.");
            }
            return mAccessibilityPointerMotionFilter.filterPointerMotionEvent(dx, dy, currentX,
                    currentY, displayId);
        }
    }

    // Native callback.
    // Native callback.
    @SuppressWarnings("unused")
    @SuppressWarnings("unused")
    @VisibleForTesting
    @VisibleForTesting
@@ -3828,6 +3853,12 @@ public class InputManagerService extends IInputManager.Stub
                        payload.get(BACKUP_CATEGORY_INPUT_GESTURES), userId);
                        payload.get(BACKUP_CATEGORY_INPUT_GESTURES), userId);
            }
            }
        }
        }

        @Override
        public void registerAccessibilityPointerMotionFilter(
                AccessibilityPointerMotionFilter filter) {
            InputManagerService.this.registerAccessibilityPointerMotionFilter(filter);
        }
    }
    }


    @Override
    @Override
@@ -4014,6 +4045,26 @@ public class InputManagerService extends IInputManager.Stub
        mPointerIconCache.setAccessibilityScaleFactor(displayId, scaleFactor);
        mPointerIconCache.setAccessibilityScaleFactor(displayId, scaleFactor);
    }
    }


    void registerAccessibilityPointerMotionFilter(
            InputManagerInternal.AccessibilityPointerMotionFilter filter) {
        // `#filterPointerMotion` expects that when it's called, `mAccessibilityPointerMotionFilter`
        // is not null.
        // Also, to avoid potential lock contention, we shouldn't call native method while holding
        // the lock here. Native code calls `#filterPointerMotion` while PointerChoreographer's
        // lock is held.
        // Thus, we must set filter before we enable the filter in native, and reset the filter
        // after we disable the filter.
        // This also ensures the previously installed filter isn't called after the filter is
        // updated.
        mNative.setAccessibilityPointerMotionFilterEnabled(false);
        synchronized (mAccessibilityPointerMotionFilterLock) {
            mAccessibilityPointerMotionFilter = filter;
        }
        if (filter != null) {
            mNative.setAccessibilityPointerMotionFilterEnabled(true);
        }
    }

    interface KeyboardBacklightControllerInterface {
    interface KeyboardBacklightControllerInterface {
        default void incrementKeyboardBacklight(int deviceId) {}
        default void incrementKeyboardBacklight(int deviceId) {}
        default void decrementKeyboardBacklight(int deviceId) {}
        default void decrementKeyboardBacklight(int deviceId) {}
+13 −0
Original line number Original line Diff line number Diff line
@@ -315,6 +315,16 @@ interface NativeInputManagerService {
     */
     */
    boolean setKernelWakeEnabled(int deviceId, boolean enabled);
    boolean setKernelWakeEnabled(int deviceId, boolean enabled);


    /**
     * Set whether the accessibility pointer motion filter is enabled.
     * <p>
     * Once enabled, {@link InputManagerService#filterPointerMotion} is called for evety motion
     * event from pointer devices.
     *
     * @param enabled {@code true} if the filter is enabled, {@code false} otherwise.
     */
    void setAccessibilityPointerMotionFilterEnabled(boolean enabled);

    /** The native implementation of InputManagerService methods. */
    /** The native implementation of InputManagerService methods. */
    class NativeImpl implements NativeInputManagerService {
    class NativeImpl implements NativeInputManagerService {
        /** Pointer to native input manager service object, used by native code. */
        /** Pointer to native input manager service object, used by native code. */
@@ -628,5 +638,8 @@ interface NativeInputManagerService {


        @Override
        @Override
        public native boolean setKernelWakeEnabled(int deviceId, boolean enabled);
        public native boolean setKernelWakeEnabled(int deviceId, boolean enabled);

        @Override
        public native void setAccessibilityPointerMotionFilterEnabled(boolean enabled);
    }
    }
}
}
+34 −0
Original line number Original line Diff line number Diff line
@@ -124,6 +124,7 @@ static struct {
    jmethodID notifyStylusGestureStarted;
    jmethodID notifyStylusGestureStarted;
    jmethodID notifyVibratorState;
    jmethodID notifyVibratorState;
    jmethodID filterInputEvent;
    jmethodID filterInputEvent;
    jmethodID filterPointerMotion;
    jmethodID interceptKeyBeforeQueueing;
    jmethodID interceptKeyBeforeQueueing;
    jmethodID interceptMotionBeforeQueueingNonInteractive;
    jmethodID interceptMotionBeforeQueueingNonInteractive;
    jmethodID interceptKeyBeforeDispatching;
    jmethodID interceptKeyBeforeDispatching;
@@ -451,6 +452,8 @@ public:
    void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId,
    void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId,
                                       const vec2& position) override;
                                       const vec2& position) override;
    void notifyMouseCursorFadedOnTyping() override;
    void notifyMouseCursorFadedOnTyping() override;
    std::optional<vec2> filterPointerMotionForAccessibility(
            const vec2& current, const vec2& delta, const ui::LogicalDisplayId& displayId) override;


    /* --- InputFilterPolicyInterface implementation --- */
    /* --- InputFilterPolicyInterface implementation --- */
    void notifyStickyModifierStateChanged(uint32_t modifierState,
    void notifyStickyModifierStateChanged(uint32_t modifierState,
@@ -938,6 +941,27 @@ void NativeInputManager::notifyStickyModifierStateChanged(uint32_t modifierState
    checkAndClearExceptionFromCallback(env, "notifyStickyModifierStateChanged");
    checkAndClearExceptionFromCallback(env, "notifyStickyModifierStateChanged");
}
}


std::optional<vec2> NativeInputManager::filterPointerMotionForAccessibility(
        const vec2& current, const vec2& delta, const ui::LogicalDisplayId& displayId) {
    JNIEnv* env = jniEnv();
    ScopedFloatArrayRO filtered(env,
                                jfloatArray(
                                        env->CallObjectMethod(mServiceObj,
                                                              gServiceClassInfo.filterPointerMotion,
                                                              delta.x, delta.y, current.x,
                                                              current.y, displayId.val())));
    if (checkAndClearExceptionFromCallback(env, "filterPointerMotionForAccessibilityLocked")) {
        ALOGE("Disabling accessibility pointer motion filter due to an error. "
              "The filter state in Java and PointerChoreographer would no longer be in sync.");
        return std::nullopt;
    }
    LOG_ALWAYS_FATAL_IF(filtered.size() != 2,
                        "Accessibility pointer motion filter is misbehaving. Returned array size "
                        "%zu should be 2.",
                        filtered.size());
    return vec2{filtered[0], filtered[1]};
}

sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(ui::LogicalDisplayId displayId) {
sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(ui::LogicalDisplayId displayId) {
    JNIEnv* env = jniEnv();
    JNIEnv* env = jniEnv();
    jlong nativeSurfaceControlPtr =
    jlong nativeSurfaceControlPtr =
@@ -3271,6 +3295,12 @@ static jboolean nativeSetKernelWakeEnabled(JNIEnv* env, jobject nativeImplObj, j
    return im->getInputManager()->getReader().setKernelWakeEnabled(deviceId, enabled);
    return im->getInputManager()->getReader().setKernelWakeEnabled(deviceId, enabled);
}
}


static void nativeSetAccessibilityPointerMotionFilterEnabled(JNIEnv* env, jobject nativeImplObj,
                                                             jboolean enabled) {
    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
    im->getInputManager()->getChoreographer().setAccessibilityPointerMotionFilterEnabled(enabled);
}

// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------


static const JNINativeMethod gInputManagerMethods[] = {
static const JNINativeMethod gInputManagerMethods[] = {
@@ -3398,6 +3428,8 @@ static const JNINativeMethod gInputManagerMethods[] = {
        {"setInputMethodConnectionIsActive", "(Z)V", (void*)nativeSetInputMethodConnectionIsActive},
        {"setInputMethodConnectionIsActive", "(Z)V", (void*)nativeSetInputMethodConnectionIsActive},
        {"getLastUsedInputDeviceId", "()I", (void*)nativeGetLastUsedInputDeviceId},
        {"getLastUsedInputDeviceId", "()I", (void*)nativeGetLastUsedInputDeviceId},
        {"setKernelWakeEnabled", "(IZ)Z", (void*)nativeSetKernelWakeEnabled},
        {"setKernelWakeEnabled", "(IZ)Z", (void*)nativeSetKernelWakeEnabled},
        {"setAccessibilityPointerMotionFilterEnabled", "(Z)V",
         (void*)nativeSetAccessibilityPointerMotionFilterEnabled},
};
};


#define FIND_CLASS(var, className) \
#define FIND_CLASS(var, className) \
@@ -3482,6 +3514,8 @@ int register_android_server_InputManager(JNIEnv* env) {
    GET_METHOD_ID(gServiceClassInfo.filterInputEvent, clazz,
    GET_METHOD_ID(gServiceClassInfo.filterInputEvent, clazz,
            "filterInputEvent", "(Landroid/view/InputEvent;I)Z");
            "filterInputEvent", "(Landroid/view/InputEvent;I)Z");


    GET_METHOD_ID(gServiceClassInfo.filterPointerMotion, clazz, "filterPointerMotion", "(FFFFI)[F");

    GET_METHOD_ID(gServiceClassInfo.interceptKeyBeforeQueueing, clazz,
    GET_METHOD_ID(gServiceClassInfo.interceptKeyBeforeQueueing, clazz,
            "interceptKeyBeforeQueueing", "(Landroid/view/KeyEvent;I)I");
            "interceptKeyBeforeQueueing", "(Landroid/view/KeyEvent;I)I");