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

Commit e3dd8848 authored by Dianne Hackborn's avatar Dianne Hackborn
Browse files

Implement virtual button support.

The kernel can now publish a property describing the layout of virtual
hardware buttons on the touchscreen.  These outside of the display
area (outside of the absolute x and y controller range the driver
reports), and when the user presses on them a key event will be
generated rather than a touch event.

This also includes a number of tweaks to the absolute controller
processing to make things work better on the new screens.  For
example, we now reject down events outside of the display area.

Still left to be done is the ability to cancel a key down event,
so the user can slide up from the virtual keys to the touch screen
without causing a virtual key to execute.
parent 334a6774
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -73,6 +73,9 @@ public:
    int getKeycodeState(int key) const;
    int getKeycodeState(int32_t deviceId, int key) const;
    
    status_t scancodeToKeycode(int32_t deviceId, int scancode,
            int32_t* outKeycode, uint32_t* outFlags) const;
    
    // special type codes when devices are added/removed.
    enum {
        DEVICE_ADDED = 0x10000000,
@@ -121,7 +124,7 @@ private:
    mutable Mutex   mLock;
    
    bool            mHaveFirstKeyboard;
    int32_t         mFirstKeyboardId; // the API is that the build in keyboard is id 0, so map it
    int32_t         mFirstKeyboardId; // the API is that the built-in keyboard is id 0, so map it
    
    struct device_ent {
        device_t* device;
+29 −0
Original line number Diff line number Diff line
@@ -240,6 +240,35 @@ int EventHub::getKeycodeState(int32_t deviceId, int code) const
    return 0;
}

status_t EventHub::scancodeToKeycode(int32_t deviceId, int scancode,
        int32_t* outKeycode, uint32_t* outFlags) const
{
    AutoMutex _l(mLock);
    device_t* device = getDevice(deviceId);
    
    if (device != NULL && device->layoutMap != NULL) {
        status_t err = device->layoutMap->map(scancode, outKeycode, outFlags);
        if (err == NO_ERROR) {
            return NO_ERROR;
        }
    }
    
    if (mHaveFirstKeyboard) {
        device = getDevice(mFirstKeyboardId);
        
        if (device != NULL && device->layoutMap != NULL) {
            status_t err = device->layoutMap->map(scancode, outKeycode, outFlags);
            if (err == NO_ERROR) {
                return NO_ERROR;
            }
        }
    }
    
    *outKeycode = 0;
    *outFlags = 0;
    return NAME_NOT_FOUND;
}

EventHub::device_t* EventHub::getDevice(int32_t deviceId) const
{
    if (deviceId == 0) deviceId = mFirstKeyboardId;
+70 −48
Original line number Diff line number Diff line
@@ -66,19 +66,56 @@ public class InputDevice {
        MotionEvent generateMotion(InputDevice device, long curTime, long curTimeNano,
                boolean isAbs, Display display, int orientation,
                int metaState) {
            if (!changed) {
                return null;
            }
            
            float scaledX = x;
            float scaledY = y;
            float temp;
            float scaledPressure = 1.0f;
            float scaledSize = 0;
            int edgeFlags = 0;
            
            int action;
            if (down != lastDown) {
                if (isAbs) {
                int w = display.getWidth()-1;
                int h = display.getHeight()-1;
                    final AbsoluteInfo absX = device.absX;
                    final AbsoluteInfo absY = device.absY;
                    if (down && absX != null && absY != null) {
                        // We don't let downs start unless we are
                        // inside of the screen.  There are two reasons for
                        // this: to avoid spurious touches when holding
                        // the edges of the device near the touchscreen,
                        // and to avoid reporting events if there are virtual
                        // keys on the touchscreen outside of the display
                        // area.
                        if (scaledX < absX.minValue || scaledX > absX.maxValue
                                || scaledY < absY.minValue || scaledY > absY.maxValue) {
                            if (false) Log.v("InputDevice", "Rejecting (" + scaledX + ","
                                    + scaledY + "): outside of ("
                                    + absX.minValue + "," + absY.minValue
                                    + ")-(" + absX.maxValue + ","
                                    + absY.maxValue + ")");
                            return null;
                        }
                    }
                } else {
                    x = y = 0;
                }
                lastDown = down;
                if (down) {
                    action = MotionEvent.ACTION_DOWN;
                    downTime = curTime;
                } else {
                    action = MotionEvent.ACTION_UP;
                }
                currentMove = null;
            } else {
                action = MotionEvent.ACTION_MOVE;
            }
            
            if (isAbs) {
                final int dispW = display.getWidth()-1;
                final int dispH = display.getHeight()-1;
                int w = dispW;
                int h = dispH;
                if (orientation == Surface.ROTATION_90
                        || orientation == Surface.ROTATION_270) {
                    int tmp = w;
@@ -120,17 +157,18 @@ public class InputDevice {
                        break;
                }

                if (scaledX == 0) {
                if (action != MotionEvent.ACTION_DOWN) {
                    if (scaledX <= 0) {
                        edgeFlags += MotionEvent.EDGE_LEFT;
                } else if (scaledX == display.getWidth() - 1.0f) {
                    } else if (scaledX >= dispW) {
                        edgeFlags += MotionEvent.EDGE_RIGHT;
                    }
                
                if (scaledY == 0) {
                    if (scaledY <= 0) {
                        edgeFlags += MotionEvent.EDGE_TOP;
                } else if (scaledY == display.getHeight() - 1.0f) {
                    } else if (scaledY >= dispH) {
                        edgeFlags += MotionEvent.EDGE_BOTTOM;
                    }
                }
                
            } else {
                scaledX *= xMoveScale;
@@ -153,24 +191,6 @@ public class InputDevice {
                }
            }
            
            changed = false;
            if (down != lastDown) {
                int action;
                lastDown = down;
                if (down) {
                    action = MotionEvent.ACTION_DOWN;
                    downTime = curTime;
                } else {
                    action = MotionEvent.ACTION_UP;
                }
                currentMove = null;
                if (!isAbs) {
                    x = y = 0;
                }
                return MotionEvent.obtainNano(downTime, curTime, curTimeNano, action,
                        scaledX, scaledY, scaledPressure, scaledSize, metaState,
                        xPrecision, yPrecision, device.id, edgeFlags);
            } else {
            if (currentMove != null) {
                if (false) Log.i("InputDevice", "Adding batch x=" + scaledX
                        + " y=" + scaledY + " to " + currentMove);
@@ -181,13 +201,15 @@ public class InputDevice {
                }
                return null;
            }
                MotionEvent me = MotionEvent.obtainNano(downTime, curTime, curTimeNano,
                        MotionEvent.ACTION_MOVE, scaledX, scaledY,
            
            MotionEvent me = MotionEvent.obtainNano(downTime, curTime,
                    curTimeNano, action, scaledX, scaledY,
                    scaledPressure, scaledSize, metaState,
                    xPrecision, yPrecision, device.id, edgeFlags);
            if (action == MotionEvent.ACTION_MOVE) {
                currentMove = me;
                return me;
            }
            return me;
        }
    }
    
+226 −19
Original line number Diff line number Diff line
@@ -30,10 +30,20 @@ import android.view.RawInputEvent;
import android.view.Surface;
import android.view.WindowManagerPolicy;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;

public abstract class KeyInputQueue {
    static final String TAG = "KeyInputQueue";

    SparseArray<InputDevice> mDevices = new SparseArray<InputDevice>();
    static final boolean DEBUG_VIRTUAL_KEYS = false;
    
    final SparseArray<InputDevice> mDevices = new SparseArray<InputDevice>();
    final ArrayList<VirtualKey> mVirtualKeys = new ArrayList<VirtualKey>();
    
    int mGlobalMetaState = 0;
    boolean mHaveGlobalMetaState = false;
@@ -44,10 +54,14 @@ public abstract class KeyInputQueue {
    int mCacheCount;

    Display mDisplay = null;
    int mDisplayWidth;
    int mDisplayHeight;
    
    int mOrientation = Surface.ROTATION_0;
    int[] mKeyRotationMap = null;
    
    VirtualKey mPressedVirtualKey = null;
    
    PowerManager.WakeLock mWakeLock;

    static final int[] KEY_90_MAP = new int[] {
@@ -110,11 +124,106 @@ public abstract class KeyInputQueue {
        QueuedEvent next;
    }

    /**
     * A key that exists as a part of the touch-screen, outside of the normal
     * display area of the screen.
     */
    static class VirtualKey {
        int scancode;
        int centerx;
        int centery;
        int width;
        int height;
        
        int hitLeft;
        int hitTop;
        int hitRight;
        int hitBottom;
        
        InputDevice lastDevice;
        int lastKeycode;
        
        boolean checkHit(int x, int y) {
            return (x >= hitLeft && x <= hitRight
                    && y >= hitTop && y <= hitBottom);
        }
        
        void computeHitRect(InputDevice dev, int dw, int dh) {
            if (dev == lastDevice) {
                return;
            }
            
            if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "computeHitRect for " + scancode
                    + ": dev=" + dev + " absX=" + dev.absX + " absY=" + dev.absY);
            
            lastDevice = dev;
            
            int minx = dev.absX.minValue;
            int maxx = dev.absX.maxValue;
            
            int halfw = width/2;
            int left = centerx - halfw;
            int right = centerx + halfw;
            hitLeft = minx + ((left*maxx-minx)/dw);
            hitRight = minx + ((right*maxx-minx)/dw);
            
            int miny = dev.absY.minValue;
            int maxy = dev.absY.maxValue;
            
            int halfh = height/2;
            int top = centery - halfh;
            int bottom = centery + halfh;
            hitTop = miny + ((top*maxy-miny)/dh);
            hitBottom = miny + ((bottom*maxy-miny)/dh);
        }
    }
    
    KeyInputQueue(Context context) {
        if (MEASURE_LATENCY) {
            lt = new LatencyTimer(100, 1000);
        }

        try {
            FileInputStream fis = new FileInputStream(
                    "/sys/board_properties/virtualkeys.synaptics-rmi-touchscreen");
            InputStreamReader isr = new InputStreamReader(fis);
            BufferedReader br = new BufferedReader(isr);
            String str = br.readLine();
            if (str != null) {
                String[] it = str.split(":");
                if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "***** VIRTUAL KEYS: " + it);
                final int N = it.length-6;
                for (int i=0; i<=N; i+=6) {
                    if (!"0x01".equals(it[i])) {
                        Log.w(TAG, "Unknown virtual key type at elem #" + i
                                + ": " + it[i]);
                        continue;
                    }
                    try {
                        VirtualKey sb = new VirtualKey();
                        sb.scancode = Integer.parseInt(it[i+1]);
                        sb.centerx = Integer.parseInt(it[i+2]);
                        sb.centery = Integer.parseInt(it[i+3]);
                        sb.width = Integer.parseInt(it[i+4]);
                        sb.height = Integer.parseInt(it[i+5]);
                        if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Virtual key "
                                + sb.scancode + ": center=" + sb.centerx + ","
                                + sb.centery + " size=" + sb.width + "x"
                                + sb.height);
                        mVirtualKeys.add(sb);
                    } catch (NumberFormatException e) {
                        Log.w(TAG, "Bad number at region " + i + " in: "
                                + str, e);
                    }
                }
            }
            br.close();
        } catch (FileNotFoundException e) {
            Log.i(TAG, "No virtual keys found");
        } catch (IOException e) {
            Log.w(TAG, "Error reading virtual keys", e);
        }
        
        PowerManager pm = (PowerManager)context.getSystemService(
                                                        Context.POWER_SERVICE);
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
@@ -131,6 +240,12 @@ public abstract class KeyInputQueue {

    public void setDisplay(Display display) {
        mDisplay = display;
        
        // We assume at this point that the display dimensions reflect the
        // natural, unrotated display.  We will perform hit tests for soft
        // buttons based on that display.
        mDisplayWidth = display.getWidth();
        mDisplayHeight = display.getHeight();
    }
    
    public void getInputConfiguration(Configuration config) {
@@ -173,6 +288,7 @@ public abstract class KeyInputQueue {
    public static native int getScancodeState(int deviceId, int sw);
    public static native int getKeycodeState(int sw);
    public static native int getKeycodeState(int deviceId, int sw);
    public static native int scancodeToKeycode(int deviceId, int scancode);
    public static native boolean hasKeys(int[] keycodes, boolean[] keyExists);
    
    public static KeyEvent newKeyEvent(InputDevice device, long downTime,
@@ -339,8 +455,54 @@ public abstract class KeyInputQueue {
                                }
                                
                                MotionEvent me;
                                me = di.mAbs.generateMotion(di, curTime, curTimeNano, true,
                                        mDisplay, mOrientation, mGlobalMetaState);
                                
                                InputDevice.MotionState ms = di.mAbs;
                                if (ms.changed) {
                                    ms.changed = false;
                                    
                                    boolean doMotion = true;
                                    
                                    // Look for virtual buttons.
                                    VirtualKey vk = mPressedVirtualKey;
                                    if (vk != null) {
                                        doMotion = false;
                                        if (!ms.down) {
                                            mPressedVirtualKey = null;
                                            ms.lastDown = ms.down;
                                            if (DEBUG_VIRTUAL_KEYS) Log.v(TAG,
                                                    "Generate key up for: " + vk.scancode);
                                            addLocked(di, curTimeNano, ev.flags,
                                                    RawInputEvent.CLASS_KEYBOARD,
                                                    newKeyEvent(di, di.mDownTime,
                                                            curTime, false,
                                                            vk.lastKeycode,
                                                            0, vk.scancode, 0));
                                        }
                                    } else if (ms.down && !ms.lastDown) {
                                        vk = findSoftButton(di);
                                        if (vk != null) {
                                            doMotion = false;
                                            mPressedVirtualKey = vk;
                                            vk.lastKeycode = scancodeToKeycode(
                                                    di.id, vk.scancode);
                                            ms.lastDown = ms.down;
                                            di.mDownTime = curTime;
                                            if (DEBUG_VIRTUAL_KEYS) Log.v(TAG,
                                                    "Generate key down for: " + vk.scancode
                                                    + " (keycode=" + vk.lastKeycode + ")");
                                            addLocked(di, curTimeNano, ev.flags,
                                                    RawInputEvent.CLASS_KEYBOARD,
                                                    newKeyEvent(di, di.mDownTime,
                                                            curTime, true,
                                                            vk.lastKeycode, 0,
                                                            vk.scancode, 0));
                                        }
                                    }
                                    
                                    if (doMotion) {
                                        me = ms.generateMotion(di, curTime,
                                                curTimeNano, true, mDisplay,
                                                mOrientation, mGlobalMetaState);
                                        if (false) Log.v(TAG, "Absolute: x=" + di.mAbs.x
                                                + " y=" + di.mAbs.y + " ev=" + me);
                                        if (me != null) {
@@ -350,8 +512,16 @@ public abstract class KeyInputQueue {
                                            addLocked(di, curTimeNano, ev.flags,
                                                    RawInputEvent.CLASS_TOUCHSCREEN, me);
                                        }
                                me = di.mRel.generateMotion(di, curTime, curTimeNano, false,
                                        mDisplay, mOrientation, mGlobalMetaState);
                                    }
                                }
                                
                                ms = di.mRel;
                                if (ms.changed) {
                                    ms.changed = false;
                                    
                                    me = ms.generateMotion(di, curTime,
                                            curTimeNano, false, mDisplay,
                                            mOrientation, mGlobalMetaState);
                                    if (false) Log.v(TAG, "Relative: x=" + di.mRel.x
                                            + " y=" + di.mRel.y + " ev=" + me);
                                    if (me != null) {
@@ -363,12 +533,49 @@ public abstract class KeyInputQueue {
                        }
                    }
                }
            catch (RuntimeException exc) {
                
            } catch (RuntimeException exc) {
                Log.e(TAG, "InputReaderThread uncaught exception", exc);
            }
        }
    };

    private VirtualKey findSoftButton(InputDevice dev) {
        final int N = mVirtualKeys.size();
        if (N <= 0) {
            return null;
        }
        
        final InputDevice.AbsoluteInfo absx = dev.absX;
        final InputDevice.AbsoluteInfo absy = dev.absY;
        final InputDevice.MotionState absm = dev.mAbs;
        if (absx == null || absy == null || absm == null) {
            return null;
        }
        
        if (absm.x >= absx.minValue && absm.x <= absx.maxValue
                && absm.y >= absy.minValue && absm.y <= absy.maxValue) {
            if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Input (" + absm.x
                    + "," + absm.y + ") inside of display");
            return null;
        }
        
        for (int i=0; i<N; i++) {
            VirtualKey sb = mVirtualKeys.get(i);
            sb.computeHitRect(dev, mDisplayWidth, mDisplayHeight);
            if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Hit test (" + absm.x + ","
                    + absm.y + ") in code " + sb.scancode + " - (" + sb.hitLeft
                    + "," + sb.hitTop + ")-(" + sb.hitRight + ","
                    + sb.hitBottom + ")");
            if (sb.checkHit(absm.x, absm.y)) {
                if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Hit!");
                return sb;
            }
        }
        
        return null;
    }
    
    /**
     * Returns a new meta state for the given keys and old state.
     */
+19 −0
Original line number Diff line number Diff line
@@ -205,6 +205,23 @@ android_server_KeyInputQueue_getKeycodeStateDevice(JNIEnv* env, jobject clazz,
    return st;
}

static jint
android_server_KeyInputQueue_scancodeToKeycode(JNIEnv* env, jobject clazz,
                                            jint deviceId, jint scancode)
{
    jint res = 0;
    gLock.lock();
    if (gHub != NULL) {
        int32_t keycode;
        uint32_t flags;
        gHub->scancodeToKeycode(deviceId, scancode, &keycode, &flags);
        res = keycode;
    }
    gLock.unlock();
    
    return res;
}

static jboolean
android_server_KeyInputQueue_hasKeys(JNIEnv* env, jobject clazz,
                                     jintArray keyCodes, jbooleanArray outFlags)
@@ -254,6 +271,8 @@ static JNINativeMethod gInputMethods[] = {
        (void*) android_server_KeyInputQueue_getKeycodeStateDevice },
    { "hasKeys", "([I[Z)Z",
        (void*) android_server_KeyInputQueue_hasKeys },
    { "scancodeToKeycode", "(II)I",
        (void*) android_server_KeyInputQueue_scancodeToKeycode },
};

int register_android_server_KeyInputQueue(JNIEnv* env)