From e3dd884815b2aaeec4241859722ab603e0b1466b Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Tue, 14 Jul 2009 12:06:54 -0700 Subject: [PATCH] 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. --- include/ui/EventHub.h | 5 +- libs/ui/EventHub.cpp | 29 +++ .../java/com/android/server/InputDevice.java | 120 +++++---- .../com/android/server/KeyInputQueue.java | 245 ++++++++++++++++-- .../jni/com_android_server_KeyInputQueue.cpp | 19 ++ 5 files changed, 349 insertions(+), 69 deletions(-) diff --git a/include/ui/EventHub.h b/include/ui/EventHub.h index e12c4f13ea0d8..d9c0af2117035 100644 --- a/include/ui/EventHub.h +++ b/include/ui/EventHub.h @@ -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; diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp index 13c30a795cb2f..a72f055c388c0 100644 --- a/libs/ui/EventHub.cpp +++ b/libs/ui/EventHub.cpp @@ -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; diff --git a/services/java/com/android/server/InputDevice.java b/services/java/com/android/server/InputDevice.java index 9c1f9421a8184..a71c39a0b3b5c 100644 --- a/services/java/com/android/server/InputDevice.java +++ b/services/java/com/android/server/InputDevice.java @@ -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) { + 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 @@ public class InputDevice { 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 @@ 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; + 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 78cdf8b97eef2..fd6b813e7adab 100644 --- a/services/java/com/android/server/KeyInputQueue.java +++ b/services/java/com/android/server/KeyInputQueue.java @@ -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 mDevices = new SparseArray(); + static final boolean DEBUG_VIRTUAL_KEYS = false; + + final SparseArray mDevices = new SparseArray(); + final ArrayList mVirtualKeys = new ArrayList(); 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,36 +455,127 @@ public abstract class KeyInputQueue { } 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)); + } + } + + 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); + } } - 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; iscancodeToKeycode(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)