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

Commit ca3ad4f3 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Implement MouseToTouch compatibility" into main

parents 79071c28 d55fbea2
Loading
Loading
Loading
Loading
+204 −2
Original line number Diff line number Diff line
@@ -16,16 +16,23 @@

package android.view.input;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.os.Handler;
import android.util.Log;
import android.util.SparseArray;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputEventCompatProcessor;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;

/**
@@ -38,6 +45,20 @@ import java.util.List;
public class MouseToTouchProcessor extends InputEventCompatProcessor {
    private static final String TAG = MouseToTouchProcessor.class.getSimpleName();

    @IntDef({STATE_AWAITING, STATE_CONVERTING, STATE_NON_PRIMARY_CLICK})
    @Retention(RetentionPolicy.SOURCE)
    private @interface State {}
    private static final int STATE_AWAITING = 0;
    private static final int STATE_CONVERTING = 1;
    private static final int STATE_NON_PRIMARY_CLICK = 2;
    private @State int mState = STATE_AWAITING;

    /**
     * Map the processed event's id to the original event, or null if the event is synthesized
     * in this class.
     */
    private final SparseArray<InputEvent> mModifiedEventMap = new SparseArray<>();

    /**
     * Return {@code true} if this compatibility is required based on the given context.
     *
@@ -57,15 +78,196 @@ public class MouseToTouchProcessor extends InputEventCompatProcessor {
        super(context, handler);
    }

    @Nullable
    @Override
    public List<InputEvent> processInputEventForCompatibility(@NonNull InputEvent event) {
        // TODO(b/413207127): Implement the feature.
        if (!(event instanceof MotionEvent motionEvent)
                || !motionEvent.isFromSource(InputDevice.SOURCE_MOUSE)
                || motionEvent.getActionMasked() == MotionEvent.ACTION_OUTSIDE
                || motionEvent.getActionMasked() == MotionEvent.ACTION_SCROLL) {
            return null;
        }

        final List<InputEvent> result = processMotionEvent(motionEvent);
        if (result != null && !result.isEmpty()) {
            for (int i = 0; i < result.size() - 1; i++) {
                mModifiedEventMap.put(result.get(i).getId(), null);
            }
            mModifiedEventMap.put(result.getLast().getId(), event);
        }
        return result;
    }

    @Nullable
    private List<InputEvent> processMotionEvent(@NonNull MotionEvent event) {
        return switch (mState) {
            case STATE_AWAITING -> processEventInAwaitingState(event);
            case STATE_CONVERTING -> processEventInConvertingState(event);
            case STATE_NON_PRIMARY_CLICK -> processEventInNonPrimaryClickState(event);
            default -> null;
        };
    }

    @Nullable
    private List<InputEvent> processEventInAwaitingState(@NonNull MotionEvent event) {
        if (event.isHoverEvent()) {
            // Don't modify hover events, but clear the button state (e.g. BACK).
            if (event.getButtonState() != 0) {
                event.setButtonState(0);
                return List.of(event);
            }
            return null;
        }

        final int action = event.getActionMasked();
        if (isActionButtonEvent(action)) {
            // Button events, usually BACK and FORWARD, are dropped.
            return List.of();
        }
        if (action != MotionEvent.ACTION_DOWN) {
            Log.e(TAG, "Broken input sequence is observed. event=" + event);
            // Decide the next state based anyway on the primary button state.
        }
        boolean primaryButton = (event.getButtonState() & MotionEvent.BUTTON_PRIMARY)
                == MotionEvent.BUTTON_PRIMARY;
        if (primaryButton || isTouchpadGesture(event)) {
            mState = STATE_CONVERTING;
            return List.of(obtainRewrittenEventAsTouch(event));
        } else {
            mState = STATE_NON_PRIMARY_CLICK;
            return null;
        }
    }

    @Nullable
    private List<InputEvent> processEventInConvertingState(@NonNull MotionEvent event) {
        // STATE_CONVERTING starts with a primary button.
        // In this state, events are converted to touch events, and other button properties and
        // events are dropped.
        // Note that non-primary buttons can be also pressed and released during this state, and
        // ACTION_UP is dispatched only when all of (primary, secondary, and tertiary) buttons
        // are released.

        final int action = event.getActionMasked();
        if (isActionButtonEvent(action)) {
            // Button events are always dropped.
            return List.of();
        } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
            mState = STATE_AWAITING;
        }

        return List.of(obtainRewrittenEventAsTouch(event));
    }

    @Nullable
    private List<InputEvent> processEventInNonPrimaryClickState(@NonNull MotionEvent event) {
        final int action = event.getActionMasked();
        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
            mState = STATE_AWAITING;
        }
        // During a gesture that was started from the non-primary (e.g. right) click, no rewrite
        // happens even if there's primary (left) button click.
        return null;
    }

    @Nullable
    @Override
    public InputEvent processInputEventBeforeFinish(@NonNull InputEvent inputEvent) {
        final int idx = mModifiedEventMap.indexOfKey(inputEvent.getId());
        if (idx < 0) {
            return inputEvent;
        }

        final InputEvent originalEvent = mModifiedEventMap.valueAt(idx);
        mModifiedEventMap.removeAt(idx);
        if (inputEvent != originalEvent) {
            inputEvent.recycleIfNeededAfterDispatch();
        }
        return originalEvent;
    }

    @NonNull
    private static MotionEvent obtainRewrittenEventAsTouch(@NonNull MotionEvent original) {
        final int numPointers = original.getPointerCount();

        final PointerProperties[] pointerProps = new PointerProperties[numPointers];
        for (int i = 0; i < numPointers; i++) {
            pointerProps[i] = new PointerProperties();
            original.getPointerProperties(i, pointerProps[i]);
            pointerProps[i].toolType = MotionEvent.TOOL_TYPE_FINGER;
        }

        final long firstEventTime;
        final PointerCoords[] pointerCoords = new PointerCoords[numPointers];
        for (int pointerIdx = 0; pointerIdx < numPointers; pointerIdx++) {
            pointerCoords[pointerIdx] = new PointerCoords();
        }

        if (original.getHistorySize() > 0) {
            firstEventTime = original.getHistoricalEventTime(0);
            for (int pointerIdx = 0; pointerIdx < numPointers; pointerIdx++) {
                original.getHistoricalPointerCoords(pointerIdx, 0, pointerCoords[pointerIdx]);
            }
        } else {
            firstEventTime = original.getEventTime();
            for (int pointerIdx = 0; pointerIdx < numPointers; pointerIdx++) {
                original.getPointerCoords(pointerIdx, pointerCoords[pointerIdx]);
            }
        }

        final MotionEvent result =
                MotionEvent.obtain(
                        original.getDownTime(),
                        firstEventTime,
                        original.getAction(),
                        original.getPointerCount(),
                        pointerProps,
                        pointerCoords,
                        original.getMetaState(),
                        /* buttonState= */ 0,
                        original.getXPrecision(),
                        original.getYPrecision(),
                        original.getDeviceId(),
                        original.getEdgeFlags(),
                        InputDevice.SOURCE_TOUCHSCREEN,
                        original.getDisplayId(),
                        original.getFlags(),
                        original.getClassification());

        // If there are one or more history, add them to the event, and the last one is not from the
        // history but the current coords.
        for (int historyIdx = 1; historyIdx <= original.getHistorySize(); historyIdx++) {
            long eventTime;
            if (historyIdx == original.getHistorySize()) {
                eventTime = original.getEventTime();
                for (int pointerIdx = 0; pointerIdx < numPointers; pointerIdx++) {
                    original.getPointerCoords(pointerIdx, pointerCoords[pointerIdx]);
                }
            } else {
                eventTime = original.getHistoricalEventTime(historyIdx);
                for (int pointerIdx = 0; pointerIdx < numPointers; pointerIdx++) {
                    original.getHistoricalPointerCoords(
                            pointerIdx, historyIdx, pointerCoords[pointerIdx]);
                }
            }
            result.addBatch(eventTime, pointerCoords, original.getMetaState());
        }

        result.setActionButton(0);
        result.setButtonState(0);

        return result;
    }

    private static boolean isActionButtonEvent(int action) {
        return action == MotionEvent.ACTION_BUTTON_PRESS
                || action == MotionEvent.ACTION_BUTTON_RELEASE;
    }

    private static boolean isTouchpadGesture(MotionEvent event) {
        return event.getToolType(0) == MotionEvent.TOOL_TYPE_FINGER
                && (event.getClassification() == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE
                || event.getClassification() == MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE
                || event.getClassification() == MotionEvent.CLASSIFICATION_PINCH);
    }
}
+616 −0

File changed.

Preview size limit exceeded, changes collapsed.