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.
diff --git a/include/ui/EventHub.h b/include/ui/EventHub.h
index e12c4f1..d9c0af2 100644
--- a/include/ui/EventHub.h
+++ b/include/ui/EventHub.h
@@ -73,6 +73,9 @@
     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 @@
     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;
diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp
index 13c30a7..a72f055 100644
--- a/libs/ui/EventHub.cpp
+++ b/libs/ui/EventHub.cpp
@@ -240,6 +240,35 @@
     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;
diff --git a/services/java/com/android/server/InputDevice.java b/services/java/com/android/server/InputDevice.java
index 9c1f942..a71c39a 100644
--- a/services/java/com/android/server/InputDevice.java
+++ b/services/java/com/android/server/InputDevice.java
@@ -66,19 +66,56 @@
         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) {
+                    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) {
-                int w = display.getWidth()-1;
-                int h = display.getHeight()-1;
+                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,16 +157,17 @@
                         break;
                 }
 
-                if (scaledX == 0) {
-                    edgeFlags += MotionEvent.EDGE_LEFT;
-                } else if (scaledX == display.getWidth() - 1.0f) {
-                    edgeFlags += MotionEvent.EDGE_RIGHT;
-                }
-                
-                if (scaledY == 0) {
-                    edgeFlags += MotionEvent.EDGE_TOP;
-                } else if (scaledY == display.getHeight() - 1.0f) {
-                    edgeFlags += MotionEvent.EDGE_BOTTOM;
+                if (action != MotionEvent.ACTION_DOWN) {
+                    if (scaledX <= 0) {
+                        edgeFlags += MotionEvent.EDGE_LEFT;
+                    } else if (scaledX >= dispW) {
+                        edgeFlags += MotionEvent.EDGE_RIGHT;
+                    }
+                    if (scaledY <= 0) {
+                        edgeFlags += MotionEvent.EDGE_TOP;
+                    } else if (scaledY >= dispH) {
+                        edgeFlags += MotionEvent.EDGE_BOTTOM;
+                    }
                 }
                 
             } else {
@@ -153,41 +191,25 @@
                 }
             }
             
-            changed = false;
-            if (down != lastDown) {
-                int action;
-                lastDown = down;
-                if (down) {
-                    action = MotionEvent.ACTION_DOWN;
-                    downTime = curTime;
-                } else {
-                    action = MotionEvent.ACTION_UP;
+            if (currentMove != null) {
+                if (false) Log.i("InputDevice", "Adding batch x=" + scaledX
+                        + " y=" + scaledY + " to " + currentMove);
+                currentMove.addBatch(curTime, scaledX, scaledY,
+                        scaledPressure, scaledSize, metaState);
+                if (WindowManagerPolicy.WATCH_POINTER) {
+                    Log.i("KeyInputQueue", "Updating: " + currentMove);
                 }
-                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);
-                    currentMove.addBatch(curTime, scaledX, scaledY,
-                            scaledPressure, scaledSize, metaState);
-                    if (WindowManagerPolicy.WATCH_POINTER) {
-                        Log.i("KeyInputQueue", "Updating: " + currentMove);
-                    }
-                    return null;
-                }
-                MotionEvent me = MotionEvent.obtainNano(downTime, curTime, curTimeNano,
-                        MotionEvent.ACTION_MOVE, scaledX, scaledY,
-                        scaledPressure, scaledSize, metaState,
-                        xPrecision, yPrecision, device.id, edgeFlags);
-                currentMove = me;
-                return me;
+                return null;
             }
+            
+            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;
         }
     }
     
diff --git a/services/java/com/android/server/KeyInputQueue.java b/services/java/com/android/server/KeyInputQueue.java
index 78cdf8b..fd6b813 100644
--- a/services/java/com/android/server/KeyInputQueue.java
+++ b/services/java/com/android/server/KeyInputQueue.java
@@ -30,10 +30,20 @@
 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 @@
     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 @@
         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 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 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,36 +455,127 @@
                                 }
                                 
                                 MotionEvent me;
-                                me = di.mAbs.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) {
-                                    if (WindowManagerPolicy.WATCH_POINTER) {
-                                        Log.i(TAG, "Enqueueing: " + me);
+                                
+                                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));
+                                        }
                                     }
-                                    addLocked(di, curTimeNano, ev.flags,
-                                            RawInputEvent.CLASS_TOUCHSCREEN, me);
+                                    
+                                    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) {
+                                            if (WindowManagerPolicy.WATCH_POINTER) {
+                                                Log.i(TAG, "Enqueueing: " + me);
+                                            }
+                                            addLocked(di, curTimeNano, ev.flags,
+                                                    RawInputEvent.CLASS_TOUCHSCREEN, me);
+                                        }
+                                    }
                                 }
-                                me = di.mRel.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) {
-                                    addLocked(di, curTimeNano, ev.flags,
-                                            RawInputEvent.CLASS_TRACKBALL, me);
+                                
+                                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) {
+                                        addLocked(di, curTimeNano, ev.flags,
+                                                RawInputEvent.CLASS_TRACKBALL, me);
+                                    }
                                 }
                             }
                         }
                     }
                 }
-            }
-            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.
      */
diff --git a/services/jni/com_android_server_KeyInputQueue.cpp b/services/jni/com_android_server_KeyInputQueue.cpp
index 63830d5..afca1f7 100644
--- a/services/jni/com_android_server_KeyInputQueue.cpp
+++ b/services/jni/com_android_server_KeyInputQueue.cpp
@@ -205,6 +205,23 @@
     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 @@
         (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)