/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.os.Environment; import android.os.LatencyTimer; import android.os.PowerManager; import android.os.SystemClock; import android.os.SystemProperties; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; import android.view.Display; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.RawInputEvent; import android.view.Surface; import android.view.WindowManagerPolicy; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.ArrayList; public abstract class KeyInputQueue { static final String TAG = "KeyInputQueue"; static final boolean DEBUG = false; static final boolean DEBUG_VIRTUAL_KEYS = false; static final boolean DEBUG_POINTERS = false; /** * Turn on some hacks we have to improve the touch interaction with a * certain device whose screen currently is not all that good. */ static boolean BAD_TOUCH_HACK = false; /** * Turn on some hacks to improve touch interaction with another device * where touch coordinate data can get corrupted. */ static boolean JUMPY_TOUCH_HACK = false; private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml"; final SparseArray<InputDevice> mDevices = new SparseArray<InputDevice>(); final SparseArray<InputDevice> mIgnoredDevices = new SparseArray<InputDevice>(); final ArrayList<VirtualKey> mVirtualKeys = new ArrayList<VirtualKey>(); final HapticFeedbackCallback mHapticFeedbackCallback; int mGlobalMetaState = 0; boolean mHaveGlobalMetaState = false; final QueuedEvent mFirst; final QueuedEvent mLast; QueuedEvent mCache; 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[] { KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_DOWN, }; static final int[] KEY_180_MAP = new int[] { KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT, }; static final int[] KEY_270_MAP = new int[] { KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_DPAD_DOWN, }; public static final int FILTER_REMOVE = 0; public static final int FILTER_KEEP = 1; public static final int FILTER_ABORT = -1; private static final boolean MEASURE_LATENCY = false; private LatencyTimer lt; public interface FilterCallback { int filterEvent(QueuedEvent ev); } public interface HapticFeedbackCallback { void virtualKeyFeedback(KeyEvent event); } static class QueuedEvent { InputDevice inputDevice; long whenNano; int flags; // From the raw event int classType; // One of the class constants in InputEvent Object event; boolean inQueue; void copyFrom(QueuedEvent that) { this.inputDevice = that.inputDevice; this.whenNano = that.whenNano; this.flags = that.flags; this.classType = that.classType; this.event = that.event; } @Override public String toString() { return "QueuedEvent{" + Integer.toHexString(System.identityHashCode(this)) + " " + event + "}"; } // not copied QueuedEvent prev; 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) Slog.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); } } private void readVirtualKeys(String deviceName) { try { FileInputStream fis = new FileInputStream( "/sys/board_properties/virtualkeys." + deviceName); InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr, 2048); String str = br.readLine(); if (str != null) { String[] it = str.split(":"); if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "***** VIRTUAL KEYS: " + it); final int N = it.length-6; for (int i=0; i<=N; i+=6) { if (!"0x01".equals(it[i])) { Slog.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) Slog.v(TAG, "Virtual key " + sb.scancode + ": center=" + sb.centerx + "," + sb.centery + " size=" + sb.width + "x" + sb.height); mVirtualKeys.add(sb); } catch (NumberFormatException e) { Slog.w(TAG, "Bad number at region " + i + " in: " + str, e); } } } br.close(); } catch (FileNotFoundException e) { Slog.i(TAG, "No virtual keys found"); } catch (IOException e) { Slog.w(TAG, "Error reading virtual keys", e); } } private void readExcludedDevices() { // Read partner-provided list of excluded input devices XmlPullParser parser = null; // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system". File confFile = new File(Environment.getRootDirectory(), EXCLUDED_DEVICES_PATH); FileReader confreader = null; try { confreader = new FileReader(confFile); parser = Xml.newPullParser(); parser.setInput(confreader); XmlUtils.beginDocument(parser, "devices"); while (true) { XmlUtils.nextElement(parser); if (!"device".equals(parser.getName())) { break; } String name = parser.getAttributeValue(null, "name"); if (name != null) { if (DEBUG) Slog.v(TAG, "addExcludedDevice " + name); addExcludedDevice(name); } } } catch (FileNotFoundException e) { // It's ok if the file does not exist. } catch (Exception e) { Slog.e(TAG, "Exception while parsing '" + confFile.getAbsolutePath() + "'", e); } finally { try { if (confreader != null) confreader.close(); } catch (IOException e) { } } } KeyInputQueue(Context context, HapticFeedbackCallback hapticFeedbackCallback) { if (MEASURE_LATENCY) { lt = new LatencyTimer(100, 1000); } Resources r = context.getResources(); BAD_TOUCH_HACK = r.getBoolean(com.android.internal.R.bool.config_filterTouchEvents); JUMPY_TOUCH_HACK = r.getBoolean(com.android.internal.R.bool.config_filterJumpyTouchEvents); mHapticFeedbackCallback = hapticFeedbackCallback; readExcludedDevices(); PowerManager pm = (PowerManager)context.getSystemService( Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "KeyInputQueue"); mWakeLock.setReferenceCounted(false); mFirst = new QueuedEvent(); mLast = new QueuedEvent(); mFirst.next = mLast; mLast.prev = mFirst; mThread.start(); } 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(); START_CURSOR_X = mDisplay.getWidth()/2; START_CURSOR_Y = mDisplay.getHeight()/2; } public void getInputConfiguration(Configuration config) { synchronized (mFirst) { config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; config.keyboard = Configuration.KEYBOARD_NOKEYS; config.navigation = Configuration.NAVIGATION_NONAV; final int N = mDevices.size(); for (int i=0; i<N; i++) { InputDevice d = mDevices.valueAt(i); if (d != null) { if ((d.classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) { config.touchscreen = Configuration.TOUCHSCREEN_FINGER; //Slog.i("foo", "***** HAVE TOUCHSCREEN!"); } if ((d.classes&RawInputEvent.CLASS_ALPHAKEY) != 0) { config.keyboard = Configuration.KEYBOARD_QWERTY; //Slog.i("foo", "***** HAVE QWERTY!"); } if ((d.classes&RawInputEvent.CLASS_TRACKBALL) != 0) { config.navigation = Configuration.NAVIGATION_TRACKBALL; //Slog.i("foo", "***** HAVE TRACKBALL!"); } else if ((d.classes&RawInputEvent.CLASS_DPAD) != 0) { config.navigation = Configuration.NAVIGATION_DPAD; //Slog.i("foo", "***** HAVE DPAD!"); } } } } } public int getScancodeState(int code) { synchronized (mFirst) { VirtualKey vk = mPressedVirtualKey; if (vk != null) { if (vk.scancode == code) { return 2; } } return nativeGetScancodeState(code); } } public int getScancodeState(int deviceId, int code) { synchronized (mFirst) { VirtualKey vk = mPressedVirtualKey; if (vk != null) { if (vk.scancode == code) { return 2; } } return nativeGetScancodeState(deviceId, code); } } public int getTrackballScancodeState(int code) { synchronized (mFirst) { final int N = mDevices.size(); for (int i=0; i<N; i++) { InputDevice dev = mDevices.valueAt(i); if ((dev.classes&RawInputEvent.CLASS_TRACKBALL) != 0) { int res = nativeGetScancodeState(dev.id, code); if (res > 0) { return res; } } } } return 0; } public int getDPadScancodeState(int code) { synchronized (mFirst) { final int N = mDevices.size(); for (int i=0; i<N; i++) { InputDevice dev = mDevices.valueAt(i); if ((dev.classes&RawInputEvent.CLASS_DPAD) != 0) { int res = nativeGetScancodeState(dev.id, code); if (res > 0) { return res; } } } } return 0; } public int getKeycodeState(int code) { synchronized (mFirst) { VirtualKey vk = mPressedVirtualKey; if (vk != null) { if (vk.lastKeycode == code) { return 2; } } return nativeGetKeycodeState(code); } } public int getKeycodeState(int deviceId, int code) { synchronized (mFirst) { VirtualKey vk = mPressedVirtualKey; if (vk != null) { if (vk.lastKeycode == code) { return 2; } } return nativeGetKeycodeState(deviceId, code); } } public int getTrackballKeycodeState(int code) { synchronized (mFirst) { final int N = mDevices.size(); for (int i=0; i<N; i++) { InputDevice dev = mDevices.valueAt(i); if ((dev.classes&RawInputEvent.CLASS_TRACKBALL) != 0) { int res = nativeGetKeycodeState(dev.id, code); if (res > 0) { return res; } } } } return 0; } public int getDPadKeycodeState(int code) { synchronized (mFirst) { final int N = mDevices.size(); for (int i=0; i<N; i++) { InputDevice dev = mDevices.valueAt(i); if ((dev.classes&RawInputEvent.CLASS_DPAD) != 0) { int res = nativeGetKeycodeState(dev.id, code); if (res > 0) { return res; } } } } return 0; } public static native String getDeviceName(int deviceId); public static native int getDeviceClasses(int deviceId); public static native void addExcludedDevice(String deviceName); public static native boolean getAbsoluteInfo(int deviceId, int axis, InputDevice.AbsoluteInfo outInfo); public static native int getSwitchState(int sw); public static native int getSwitchState(int deviceId, int sw); public static native int nativeGetScancodeState(int code); public static native int nativeGetScancodeState(int deviceId, int code); public static native int nativeGetKeycodeState(int code); public static native int nativeGetKeycodeState(int deviceId, int code); 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, long eventTime, boolean down, int keycode, int repeatCount, int scancode, int flags) { return new KeyEvent( downTime, eventTime, down ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP, keycode, repeatCount, device != null ? device.mMetaKeysState : 0, device != null ? device.id : -1, scancode, flags | KeyEvent.FLAG_FROM_SYSTEM); } public static int CursorX = -1; public static int CursorY = -1; public int START_CURSOR_X; public int START_CURSOR_Y; Thread mThread = new Thread("InputDeviceReader") { public void run() { if (DEBUG) Slog.v(TAG, "InputDeviceReader.run()"); android.os.Process.setThreadPriority( android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY); RawInputEvent ev = new RawInputEvent(); while (true) { try { InputDevice di; // block, doesn't release the monitor readEvent(ev); boolean send = false; boolean configChanged = false; if (false) { Slog.i(TAG, "Input event: dev=0x" + Integer.toHexString(ev.deviceId) + " type=0x" + Integer.toHexString(ev.type) + " scancode=" + ev.scancode + " keycode=" + ev.keycode + " value=" + ev.value); } if (ev.type == RawInputEvent.EV_DEVICE_ADDED) { synchronized (mFirst) { di = newInputDevice(ev.deviceId); if (di.classes != 0) { // If this device is some kind of input class, // we care about it. mDevices.put(ev.deviceId, di); if ((di.classes & RawInputEvent.CLASS_TOUCHSCREEN) != 0) { readVirtualKeys(di.name); } // The configuration may have changed because // of this device. configChanged = true; } else { // We won't do anything with this device. mIgnoredDevices.put(ev.deviceId, di); Slog.i(TAG, "Ignoring non-input device: id=0x" + Integer.toHexString(di.id) + ", name=" + di.name); } } } else if (ev.type == RawInputEvent.EV_DEVICE_REMOVED) { synchronized (mFirst) { if (false) { Slog.i(TAG, "Device removed: id=0x" + Integer.toHexString(ev.deviceId)); } di = mDevices.get(ev.deviceId); if (di != null) { mDevices.delete(ev.deviceId); // The configuration may have changed because // of this device. configChanged = true; } else if ((di=mIgnoredDevices.get(ev.deviceId)) != null) { mIgnoredDevices.remove(ev.deviceId); } else { Slog.w(TAG, "Removing bad device id: " + Integer.toHexString(ev.deviceId)); continue; } } } else { di = getInputDevice(ev.deviceId); if (di == null) { // This may be some junk from an ignored device. continue; } // first crack at it send = preprocessEvent(di, ev); if (ev.type == RawInputEvent.EV_KEY) { di.mMetaKeysState = makeMetaState(ev.keycode, ev.value != 0, di.mMetaKeysState); mHaveGlobalMetaState = false; } } if (configChanged) { synchronized (mFirst) { addLocked(di, System.nanoTime(), 0, RawInputEvent.CLASS_CONFIGURATION_CHANGED, null); } } if (!send) { continue; } synchronized (mFirst) { // NOTE: The event timebase absolutely must be the same // timebase as SystemClock.uptimeMillis(). //curTime = gotOne ? ev.when : SystemClock.uptimeMillis(); final long curTime = SystemClock.uptimeMillis(); final long curTimeNano = System.nanoTime(); //Slog.i(TAG, "curTime=" + curTime + ", systemClock=" + SystemClock.uptimeMillis()); final int classes = di.classes; final int type = ev.type; final int scancode = ev.scancode; send = false; // Is it a key event? if (type == RawInputEvent.EV_KEY && (classes&RawInputEvent.CLASS_KEYBOARD) != 0 && (scancode < RawInputEvent.BTN_FIRST || scancode > RawInputEvent.BTN_LAST)) { boolean down; if (ev.value != 0) { down = true; di.mKeyDownTime = curTime; } else { down = false; } int keycode = rotateKeyCodeLocked(ev.keycode); addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, newKeyEvent(di, di.mKeyDownTime, curTime, down, keycode, 0, scancode, ((ev.flags & WindowManagerPolicy.FLAG_WOKE_HERE) != 0) ? KeyEvent.FLAG_WOKE_HERE : 0)); } else if (ev.type == RawInputEvent.EV_KEY) { // Single touch protocol: touch going down or up. if (ev.scancode == RawInputEvent.BTN_TOUCH && (classes&(RawInputEvent.CLASS_TOUCHSCREEN |RawInputEvent.CLASS_TOUCHSCREEN_MT)) == RawInputEvent.CLASS_TOUCHSCREEN) { di.mAbs.changed = true; di.mAbs.mDown[0] = ev.value != 0; // Trackball (mouse) protocol: press down or up. } else if (ev.scancode == RawInputEvent.BTN_MOUSE && (classes&RawInputEvent.CLASS_TRACKBALL) != 0) { di.mRel.changed = true; di.mRel.mNextNumPointers = ev.value != 0 ? 1 : 0; send = true; } else if (ev.scancode == RawInputEvent.BTN_MOUSE && ((classes & RawInputEvent.CLASS_MOUSE) != 0)) { if(CursorX == -1 || CursorY == -1) { CursorX = START_CURSOR_X; CursorY = START_CURSOR_Y; switch(mDisplay.getOrientation()) { case Surface.ROTATION_90: case Surface.ROTATION_270: CursorY = START_CURSOR_X; CursorX = START_CURSOR_Y; break; default: break; } } di.mMouseRel.changed = true; di.mMouseRel.mNextNumPointers = ev.value != 0 ? 3 : 2; di.mMouseRel.mNextData[MotionEvent.SAMPLE_Y] = CursorY; di.mMouseRel.mNextData[MotionEvent.SAMPLE_X] = CursorX; send = true; } // Process position events from multitouch protocol. } else if (ev.type == RawInputEvent.EV_ABS && (classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) { if (ev.scancode == RawInputEvent.ABS_MT_TOUCH_MAJOR) { di.mAbs.changed = true; di.mAbs.mNextData[di.mAbs.mAddingPointerOffset + MotionEvent.SAMPLE_PRESSURE] = ev.value; } else if (ev.scancode == RawInputEvent.ABS_MT_POSITION_X) { di.mAbs.changed = true; di.mAbs.mNextData[di.mAbs.mAddingPointerOffset + MotionEvent.SAMPLE_X] = ev.value; if (DEBUG_POINTERS) Slog.v(TAG, "MT @" + di.mAbs.mAddingPointerOffset + " X:" + ev.value); } else if (ev.scancode == RawInputEvent.ABS_MT_POSITION_Y) { di.mAbs.changed = true; di.mAbs.mNextData[di.mAbs.mAddingPointerOffset + MotionEvent.SAMPLE_Y] = ev.value; if (DEBUG_POINTERS) Slog.v(TAG, "MT @" + di.mAbs.mAddingPointerOffset + " Y:" + ev.value); } else if (ev.scancode == RawInputEvent.ABS_MT_WIDTH_MAJOR) { di.mAbs.changed = true; di.mAbs.mNextData[di.mAbs.mAddingPointerOffset + MotionEvent.SAMPLE_SIZE] = ev.value; } // Process position events from single touch protocol. } else if (ev.type == RawInputEvent.EV_ABS && (classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) { if (ev.scancode == RawInputEvent.ABS_X) { di.mAbs.changed = true; di.curTouchVals[MotionEvent.SAMPLE_X] = ev.value; } else if (ev.scancode == RawInputEvent.ABS_Y) { di.mAbs.changed = true; di.curTouchVals[MotionEvent.SAMPLE_Y] = ev.value; } else if (ev.scancode == RawInputEvent.ABS_PRESSURE) { di.mAbs.changed = true; di.curTouchVals[MotionEvent.SAMPLE_PRESSURE] = ev.value; di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA + MotionEvent.SAMPLE_PRESSURE] = ev.value; } else if (ev.scancode == RawInputEvent.ABS_TOOL_WIDTH) { di.mAbs.changed = true; di.curTouchVals[MotionEvent.SAMPLE_SIZE] = ev.value; di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA + MotionEvent.SAMPLE_SIZE] = ev.value; } else if (ev.scancode == RawInputEvent.ABS_HAT0X) { // multi-touch 2-finger support di.mAbs.changed = true; di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA + MotionEvent.SAMPLE_X] = ev.value; } else if (ev.scancode == RawInputEvent.ABS_HAT0Y) { di.mAbs.changed = true; di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA + MotionEvent.SAMPLE_Y] = ev.value; } // Process movement events from trackball (mouse) protocol. } else if (ev.type == RawInputEvent.EV_REL) { if ((classes&RawInputEvent.CLASS_TRACKBALL) != 0) { // Add this relative movement into our totals. if (ev.scancode == RawInputEvent.REL_X) { di.mRel.changed = true; di.mRel.mNextData[MotionEvent.SAMPLE_X] += ev.value; } else if (ev.scancode == RawInputEvent.REL_Y) { di.mRel.changed = true; di.mRel.mNextData[MotionEvent.SAMPLE_Y] += ev.value; } } else if ((classes&RawInputEvent.CLASS_MOUSE) != 0) { int dx = 0, dy = 0; if (ev.scancode == RawInputEvent.REL_X) { if(CursorX == -1 || CursorY == -1) { CursorX = START_CURSOR_X; CursorY = START_CURSOR_Y; switch(mDisplay.getOrientation()) { case Surface.ROTATION_90: case Surface.ROTATION_270: CursorY = START_CURSOR_X; CursorX = START_CURSOR_Y; break; default: break; } } dx = (int)ev.value; di.mMouseRel.changed = true; } else if (ev.scancode == RawInputEvent.REL_Y) { if(CursorX == -1 || CursorY == -1) { CursorX = START_CURSOR_X; CursorY = START_CURSOR_Y; switch(mDisplay.getOrientation()) { case Surface.ROTATION_90: case Surface.ROTATION_270: CursorY = START_CURSOR_X; CursorX = START_CURSOR_Y; break; default: break; } } di.mMouseRel.changed = true; dy = (int)ev.value; } CursorX += dx; CursorY += dy; CursorX = (CursorX < 0) ? 0 : (CursorX > mDisplay.getWidth() ? (mDisplay.getWidth() - 1) : CursorX); CursorY = (CursorY < 0) ? 0 : (CursorY > mDisplay.getHeight() ? (mDisplay.getHeight() - 1) : CursorY); di.mMouseRel.mNextData[MotionEvent.SAMPLE_X] = CursorX; di.mMouseRel.mNextData[MotionEvent.SAMPLE_Y] = CursorY; } } // Handle multitouch protocol sync: tells us that the // driver has returned all data for -one- of the pointers // that is currently down. if (ev.type == RawInputEvent.EV_SYN && ev.scancode == RawInputEvent.SYN_MT_REPORT && di.mAbs != null) { di.mAbs.changed = true; if (di.mAbs.mNextData[MotionEvent.SAMPLE_PRESSURE] > 0) { // If the value is <= 0, the pointer is not // down, so keep it in the count. if (di.mAbs.mNextData[di.mAbs.mAddingPointerOffset + MotionEvent.SAMPLE_PRESSURE] != 0) { final int num = di.mAbs.mNextNumPointers+1; di.mAbs.mNextNumPointers = num; if (DEBUG_POINTERS) Slog.v(TAG, "MT_REPORT: now have " + num + " pointers"); final int newOffset = (num <= InputDevice.MAX_POINTERS) ? (num * MotionEvent.NUM_SAMPLE_DATA) : (InputDevice.MAX_POINTERS * MotionEvent.NUM_SAMPLE_DATA); di.mAbs.mAddingPointerOffset = newOffset; di.mAbs.mNextData[newOffset + MotionEvent.SAMPLE_PRESSURE] = 0; } else { if (DEBUG_POINTERS) Slog.v(TAG, "MT_REPORT: no pointer"); } } // Handle general event sync: all data for the current // event update has been delivered. } else if (send || (ev.type == RawInputEvent.EV_SYN && ev.scancode == RawInputEvent.SYN_REPORT)) { if (mDisplay != null) { if (!mHaveGlobalMetaState) { computeGlobalMetaStateLocked(); } MotionEvent me; InputDevice.MotionState ms = di.mAbs; if (ms.changed) { ms.everChanged = true; ms.changed = false; if ((classes&(RawInputEvent.CLASS_TOUCHSCREEN |RawInputEvent.CLASS_TOUCHSCREEN_MT)) == RawInputEvent.CLASS_TOUCHSCREEN) { ms.mNextNumPointers = 0; if (ms.mDown[0]) { System.arraycopy(di.curTouchVals, 0, ms.mNextData, 0, MotionEvent.NUM_SAMPLE_DATA); ms.mNextNumPointers++; } } if (BAD_TOUCH_HACK) { ms.dropBadPoint(di); } if (JUMPY_TOUCH_HACK) { ms.dropJumpyPoint(di); } boolean doMotion = !monitorVirtualKey(di, ev, curTime, curTimeNano); if (doMotion && ms.mNextNumPointers > 0 && (ms.mLastNumPointers == 0 || ms.mSkipLastPointers)) { doMotion = !generateVirtualKeyDown(di, ev, curTime, curTimeNano); } if (doMotion) { // XXX Need to be able to generate // multiple events here, for example // if two fingers change up/down state // at the same time. do { me = ms.generateAbsMotion(di, curTime, curTimeNano, mDisplay, mOrientation, mGlobalMetaState); if (DEBUG_POINTERS) Slog.v(TAG, "Absolute: x=" + di.mAbs.mNextData[MotionEvent.SAMPLE_X] + " y=" + di.mAbs.mNextData[MotionEvent.SAMPLE_Y] + " ev=" + me); if (me != null) { if (WindowManagerPolicy.WATCH_POINTER) { Slog.i(TAG, "Enqueueing: " + me); } if ((classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_TOUCHSCREEN, me); else if ((classes&RawInputEvent.CLASS_MOUSE) != 0) addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_MOUSE, me); } } while (ms.hasMore()); } else { // We are consuming movement in the // virtual key area... but still // propagate this to the previous // data for comparisons. int num = ms.mNextNumPointers; if (num > InputDevice.MAX_POINTERS) { num = InputDevice.MAX_POINTERS; } System.arraycopy(ms.mNextData, 0, ms.mLastData, 0, num * MotionEvent.NUM_SAMPLE_DATA); ms.mLastNumPointers = num; ms.mSkipLastPointers = true; } ms.finish(); } ms = di.mRel; if (ms.changed) { ms.everChanged = true; ms.changed = false; me = ms.generateRelMotion(di, curTime, curTimeNano, mOrientation, mGlobalMetaState); if (false) Slog.v(TAG, "Relative: x=" + di.mRel.mNextData[MotionEvent.SAMPLE_X] + " y=" + di.mRel.mNextData[MotionEvent.SAMPLE_Y] + " ev=" + me); if (me != null) { addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_TRACKBALL, me); } } ms = di.mMouseRel; if (ms.changed) { ms.changed = false; me = ms.generateMouseMotion(di, curTime, curTimeNano, mOrientation, mGlobalMetaState); if (me != null) { addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_MOUSE, me); } ms.finish(); } } } } } catch (RuntimeException exc) { Slog.e(TAG, "InputReaderThread uncaught exception", exc); } } } }; private boolean isInsideDisplay(InputDevice dev) { 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 true; } if (absm.mNextData[MotionEvent.SAMPLE_X] >= absx.minValue && absm.mNextData[MotionEvent.SAMPLE_X] <= absx.maxValue && absm.mNextData[MotionEvent.SAMPLE_Y] >= absy.minValue && absm.mNextData[MotionEvent.SAMPLE_Y] <= absy.maxValue) { if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Input (" + absm.mNextData[MotionEvent.SAMPLE_X] + "," + absm.mNextData[MotionEvent.SAMPLE_Y] + ") inside of display"); return true; } return false; } private VirtualKey findVirtualKey(InputDevice dev) { final int N = mVirtualKeys.size(); if (N <= 0) { return null; } final InputDevice.MotionState absm = dev.mAbs; for (int i=0; i<N; i++) { VirtualKey sb = mVirtualKeys.get(i); sb.computeHitRect(dev, mDisplayWidth, mDisplayHeight); if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Hit test (" + absm.mNextData[MotionEvent.SAMPLE_X] + "," + absm.mNextData[MotionEvent.SAMPLE_Y] + ") in code " + sb.scancode + " - (" + sb.hitLeft + "," + sb.hitTop + ")-(" + sb.hitRight + "," + sb.hitBottom + ")"); if (sb.checkHit(absm.mNextData[MotionEvent.SAMPLE_X], absm.mNextData[MotionEvent.SAMPLE_Y])) { if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Hit!"); return sb; } } return null; } private boolean generateVirtualKeyDown(InputDevice di, RawInputEvent ev, long curTime, long curTimeNano) { if (isInsideDisplay(di)) { // Didn't consume event. return false; } VirtualKey vk = findVirtualKey(di); if (vk != null) { final InputDevice.MotionState ms = di.mAbs; mPressedVirtualKey = vk; vk.lastKeycode = scancodeToKeycode(di.id, vk.scancode); ms.mLastNumPointers = ms.mNextNumPointers; di.mKeyDownTime = curTime; if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Generate key down for: " + vk.scancode + " (keycode=" + vk.lastKeycode + ")"); KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, true, vk.lastKeycode, 0, vk.scancode, KeyEvent.FLAG_VIRTUAL_HARD_KEY); mHapticFeedbackCallback.virtualKeyFeedback(event); addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, event); } // We always consume the event, even if we didn't // generate a key event. 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. // Note that for all of this we are only looking at the // first pointer, since what we are handling here is the // first pointer going down, and this is the coordinate // that will be used to dispatch the event. if (false) { final InputDevice.AbsoluteInfo absx = di.absX; final InputDevice.AbsoluteInfo absy = di.absY; final InputDevice.MotionState absm = di.mAbs; Slog.v(TAG, "Rejecting (" + absm.mNextData[MotionEvent.SAMPLE_X] + "," + absm.mNextData[MotionEvent.SAMPLE_Y] + "): outside of (" + absx.minValue + "," + absy.minValue + ")-(" + absx.maxValue + "," + absx.maxValue + ")"); } return true; } private boolean monitorVirtualKey(InputDevice di, RawInputEvent ev, long curTime, long curTimeNano) { VirtualKey vk = mPressedVirtualKey; if (vk == null) { return false; } final InputDevice.MotionState ms = di.mAbs; if (ms.mNextNumPointers <= 0) { mPressedVirtualKey = null; ms.mLastNumPointers = 0; if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Generate key up for: " + vk.scancode); KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, false, vk.lastKeycode, 0, vk.scancode, KeyEvent.FLAG_VIRTUAL_HARD_KEY); mHapticFeedbackCallback.virtualKeyFeedback(event); addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, event); return true; } else if (isInsideDisplay(di)) { // Whoops the pointer has moved into // the display area! Cancel the // virtual key and start a pointer // motion. mPressedVirtualKey = null; if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Cancel key up for: " + vk.scancode); KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, false, vk.lastKeycode, 0, vk.scancode, KeyEvent.FLAG_CANCELED | KeyEvent.FLAG_VIRTUAL_HARD_KEY); mHapticFeedbackCallback.virtualKeyFeedback(event); addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, event); ms.mLastNumPointers = 0; return false; } return true; } /** * Returns a new meta state for the given keys and old state. */ private static final int makeMetaState(int keycode, boolean down, int old) { int mask; switch (keycode) { case KeyEvent.KEYCODE_ALT_LEFT: mask = KeyEvent.META_ALT_LEFT_ON; break; case KeyEvent.KEYCODE_ALT_RIGHT: mask = KeyEvent.META_ALT_RIGHT_ON; break; case KeyEvent.KEYCODE_SHIFT_LEFT: mask = KeyEvent.META_SHIFT_LEFT_ON; break; case KeyEvent.KEYCODE_SHIFT_RIGHT: mask = KeyEvent.META_SHIFT_RIGHT_ON; break; case KeyEvent.KEYCODE_SYM: mask = KeyEvent.META_SYM_ON; break; default: return old; } int result = ~(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON) & (down ? (old | mask) : (old & ~mask)); if (0 != (result & (KeyEvent.META_ALT_LEFT_ON | KeyEvent.META_ALT_RIGHT_ON))) { result |= KeyEvent.META_ALT_ON; } if (0 != (result & (KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_RIGHT_ON))) { result |= KeyEvent.META_SHIFT_ON; } return result; } private void computeGlobalMetaStateLocked() { int i = mDevices.size(); mGlobalMetaState = 0; while ((--i) >= 0) { mGlobalMetaState |= mDevices.valueAt(i).mMetaKeysState; } mHaveGlobalMetaState = true; } /* * Return true if you want the event to get passed on to the * rest of the system, and false if you've handled it and want * it dropped. */ abstract boolean preprocessEvent(InputDevice device, RawInputEvent event); InputDevice getInputDevice(int deviceId) { synchronized (mFirst) { return getInputDeviceLocked(deviceId); } } private InputDevice getInputDeviceLocked(int deviceId) { return mDevices.get(deviceId); } public void setOrientation(int orientation) { synchronized(mFirst) { mOrientation = orientation; switch (orientation) { case Surface.ROTATION_90: mKeyRotationMap = KEY_90_MAP; break; case Surface.ROTATION_180: mKeyRotationMap = KEY_180_MAP; break; case Surface.ROTATION_270: mKeyRotationMap = KEY_270_MAP; break; default: mKeyRotationMap = null; break; } } } public int rotateKeyCode(int keyCode) { synchronized(mFirst) { return rotateKeyCodeLocked(keyCode); } } private int rotateKeyCodeLocked(int keyCode) { int[] map = mKeyRotationMap; if (map != null) { final int N = map.length; for (int i=0; i<N; i+=2) { if (map[i] == keyCode) { return map[i+1]; } } } return keyCode; } boolean hasEvents() { synchronized (mFirst) { return mFirst.next != mLast; } } /* * returns true if we returned an event, and false if we timed out */ QueuedEvent getEvent(long timeoutMS) { long begin = SystemClock.uptimeMillis(); final long end = begin+timeoutMS; long now = begin; synchronized (mFirst) { while (mFirst.next == mLast && end > now) { try { mWakeLock.release(); mFirst.wait(end-now); } catch (InterruptedException e) { } now = SystemClock.uptimeMillis(); if (begin > now) { begin = now; } } if (mFirst.next == mLast) { return null; } QueuedEvent p = mFirst.next; mFirst.next = p.next; mFirst.next.prev = mFirst; p.inQueue = false; return p; } } /** * Return true if the queue has an up event pending that corresponds * to the same key as the given key event. */ boolean hasKeyUpEvent(KeyEvent origEvent) { synchronized (mFirst) { final int keyCode = origEvent.getKeyCode(); QueuedEvent cur = mLast.prev; while (cur.prev != null) { if (cur.classType == RawInputEvent.CLASS_KEYBOARD) { KeyEvent ke = (KeyEvent)cur.event; if (ke.getAction() == KeyEvent.ACTION_UP && ke.getKeyCode() == keyCode) { return true; } } cur = cur.prev; } } return false; } void recycleEvent(QueuedEvent ev) { synchronized (mFirst) { //Slog.i(TAG, "Recycle event: " + ev); if (ev.event == ev.inputDevice.mAbs.currentMove) { ev.inputDevice.mAbs.currentMove = null; } if (ev.event == ev.inputDevice.mRel.currentMove) { ev.inputDevice.mRel.currentMove = null; ev.inputDevice.mRel.mNextData[MotionEvent.SAMPLE_X] = 0; ev.inputDevice.mRel.mNextData[MotionEvent.SAMPLE_Y] = 0; } if (ev.event == ev.inputDevice.mMouseRel.currentMove) { ev.inputDevice.mMouseRel.currentMove = null; ev.inputDevice.mMouseRel.mNextData[MotionEvent.SAMPLE_X] = 0; ev.inputDevice.mMouseRel.mNextData[MotionEvent.SAMPLE_Y] = 0; } recycleLocked(ev); } } void filterQueue(FilterCallback cb) { synchronized (mFirst) { QueuedEvent cur = mLast.prev; while (cur.prev != null) { switch (cb.filterEvent(cur)) { case FILTER_REMOVE: cur.prev.next = cur.next; cur.next.prev = cur.prev; break; case FILTER_ABORT: return; } cur = cur.prev; } } } private QueuedEvent obtainLocked(InputDevice device, long whenNano, int flags, int classType, Object event) { QueuedEvent ev; if (mCacheCount == 0) { ev = new QueuedEvent(); } else { ev = mCache; ev.inQueue = false; mCache = ev.next; mCacheCount--; } ev.inputDevice = device; ev.whenNano = whenNano; ev.flags = flags; ev.classType = classType; ev.event = event; return ev; } private void recycleLocked(QueuedEvent ev) { if (ev.inQueue) { throw new RuntimeException("Event already in queue!"); } if (mCacheCount < 10) { mCacheCount++; ev.next = mCache; mCache = ev; ev.inQueue = true; } } private void addLocked(InputDevice device, long whenNano, int flags, int classType, Object event) { boolean poke = mFirst.next == mLast; QueuedEvent ev = obtainLocked(device, whenNano, flags, classType, event); QueuedEvent p = mLast.prev; while (p != mFirst && ev.whenNano < p.whenNano) { p = p.prev; } ev.next = p.next; ev.prev = p; p.next = ev; ev.next.prev = ev; ev.inQueue = true; if (poke) { long time; if (MEASURE_LATENCY) { time = System.nanoTime(); } mFirst.notify(); mWakeLock.acquire(); if (MEASURE_LATENCY) { lt.sample("1 addLocked-queued event ", System.nanoTime() - time); } } } private InputDevice newInputDevice(int deviceId) { int classes = getDeviceClasses(deviceId); String name = getDeviceName(deviceId); InputDevice.AbsoluteInfo absX = null; InputDevice.AbsoluteInfo absY = null; InputDevice.AbsoluteInfo absPressure = null; InputDevice.AbsoluteInfo absSize = null; if (classes != 0) { Slog.i(TAG, "Device added: id=0x" + Integer.toHexString(deviceId) + ", name=" + name + ", classes=" + Integer.toHexString(classes)); if ((classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) { absX = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_MT_POSITION_X, "X"); absY = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_MT_POSITION_Y, "Y"); absPressure = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_MT_TOUCH_MAJOR, "Pressure"); absSize = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_MT_WIDTH_MAJOR, "Size"); } else if ((classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) { absX = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_X, "X"); absY = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_Y, "Y"); absPressure = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_PRESSURE, "Pressure"); absSize = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_TOOL_WIDTH, "Size"); } } return new InputDevice(deviceId, classes, name, absX, absY, absPressure, absSize); } private InputDevice.AbsoluteInfo loadAbsoluteInfo(int id, int channel, String name) { InputDevice.AbsoluteInfo info = new InputDevice.AbsoluteInfo(); if (getAbsoluteInfo(id, channel, info) && info.minValue != info.maxValue) { Slog.i(TAG, " " + name + ": min=" + info.minValue + " max=" + info.maxValue + " flat=" + info.flat + " fuzz=" + info.fuzz); info.range = info.maxValue-info.minValue; return info; } Slog.i(TAG, " " + name + ": unknown values"); return null; } private static native boolean readEvent(RawInputEvent outEvent); void dump(PrintWriter pw, String prefix) { synchronized (mFirst) { for (int i=0; i<mDevices.size(); i++) { InputDevice dev = mDevices.valueAt(i); pw.print(prefix); pw.print("Device #"); pw.print(mDevices.keyAt(i)); pw.print(" "); pw.print(dev.name); pw.print(" (classes=0x"); pw.print(Integer.toHexString(dev.classes)); pw.println("):"); pw.print(prefix); pw.print(" mKeyDownTime="); pw.print(dev.mKeyDownTime); pw.print(" mMetaKeysState="); pw.println(dev.mMetaKeysState); if (dev.absX != null) { pw.print(prefix); pw.print(" absX: "); dev.absX.dump(pw); pw.println(""); } if (dev.absY != null) { pw.print(prefix); pw.print(" absY: "); dev.absY.dump(pw); pw.println(""); } if (dev.absPressure != null) { pw.print(prefix); pw.print(" absPressure: "); dev.absPressure.dump(pw); pw.println(""); } if (dev.absSize != null) { pw.print(prefix); pw.print(" absSize: "); dev.absSize.dump(pw); pw.println(""); } if (dev.mAbs.everChanged) { pw.print(prefix); pw.println(" mAbs:"); dev.mAbs.dump(pw, prefix + " "); } if (dev.mRel.everChanged) { pw.print(prefix); pw.println(" mRel:"); dev.mRel.dump(pw, prefix + " "); } } pw.println(" "); for (int i=0; i<mIgnoredDevices.size(); i++) { InputDevice dev = mIgnoredDevices.valueAt(i); pw.print(prefix); pw.print("Ignored Device #"); pw.print(mIgnoredDevices.keyAt(i)); pw.print(" "); pw.print(dev.name); pw.print(" (classes=0x"); pw.print(Integer.toHexString(dev.classes)); pw.println(")"); } pw.println(" "); for (int i=0; i<mVirtualKeys.size(); i++) { VirtualKey vk = mVirtualKeys.get(i); pw.print(prefix); pw.print("Virtual Key #"); pw.print(i); pw.println(":"); pw.print(prefix); pw.print(" scancode="); pw.println(vk.scancode); pw.print(prefix); pw.print(" centerx="); pw.print(vk.centerx); pw.print(" centery="); pw.print(vk.centery); pw.print(" width="); pw.print(vk.width); pw.print(" height="); pw.println(vk.height); pw.print(prefix); pw.print(" hitLeft="); pw.print(vk.hitLeft); pw.print(" hitTop="); pw.print(vk.hitTop); pw.print(" hitRight="); pw.print(vk.hitRight); pw.print(" hitBottom="); pw.println(vk.hitBottom); if (vk.lastDevice != null) { pw.print(prefix); pw.print(" lastDevice=#"); pw.println(vk.lastDevice.id); } if (vk.lastKeycode != 0) { pw.print(prefix); pw.print(" lastKeycode="); pw.println(vk.lastKeycode); } } pw.println(" "); pw.print(prefix); pw.print(" Default keyboard: "); pw.println(SystemProperties.get("hw.keyboards.0.devname")); pw.print(prefix); pw.print(" mGlobalMetaState="); pw.print(mGlobalMetaState); pw.print(" mHaveGlobalMetaState="); pw.println(mHaveGlobalMetaState); pw.print(prefix); pw.print(" mDisplayWidth="); pw.print(mDisplayWidth); pw.print(" mDisplayHeight="); pw.println(mDisplayHeight); pw.print(prefix); pw.print(" mOrientation="); pw.println(mOrientation); if (mPressedVirtualKey != null) { pw.print(prefix); pw.print(" mPressedVirtualKey.scancode="); pw.println(mPressedVirtualKey.scancode); } } } }