| /* |
| * 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.os.Environment; |
| import android.os.LatencyTimer; |
| import android.os.PowerManager; |
| import android.os.SystemClock; |
| import android.util.Log; |
| 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.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 final boolean BAD_TOUCH_HACK = true; |
| |
| 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) 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); |
| } |
| } |
| |
| 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) 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); |
| } |
| } |
| |
| 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) Log.v(TAG, "addExcludedDevice " + name); |
| addExcludedDevice(name); |
| } |
| } |
| } catch (FileNotFoundException e) { |
| // It's ok if the file does not exist. |
| } catch (Exception e) { |
| Log.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); |
| } |
| |
| 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(); |
| } |
| |
| 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; |
| //Log.i("foo", "***** HAVE TOUCHSCREEN!"); |
| } |
| if ((d.classes&RawInputEvent.CLASS_ALPHAKEY) != 0) { |
| config.keyboard |
| = Configuration.KEYBOARD_QWERTY; |
| //Log.i("foo", "***** HAVE QWERTY!"); |
| } |
| if ((d.classes&RawInputEvent.CLASS_TRACKBALL) != 0) { |
| config.navigation |
| = Configuration.NAVIGATION_TRACKBALL; |
| //Log.i("foo", "***** HAVE TRACKBALL!"); |
| } else if ((d.classes&RawInputEvent.CLASS_DPAD) != 0) { |
| config.navigation |
| = Configuration.NAVIGATION_DPAD; |
| //Log.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 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 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); |
| } |
| |
| Thread mThread = new Thread("InputDeviceReader") { |
| public void run() { |
| if (DEBUG) Log.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) { |
| Log.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); |
| Log.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) { |
| Log.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 { |
| Log.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(); |
| //Log.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; |
| } |
| |
| // 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) Log.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) Log.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; |
| } |
| |
| // Process movement events from trackball (mouse) protocol. |
| } else if (ev.type == RawInputEvent.EV_REL && |
| (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; |
| } |
| } |
| |
| // 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) Log.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) Log.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.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); |
| } |
| |
| boolean doMotion = !monitorVirtualKey(di, |
| ev, curTime, curTimeNano); |
| |
| if (doMotion && ms.mNextNumPointers > 0 |
| && ms.mLastNumPointers == 0) { |
| 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 (false) Log.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) { |
| Log.i(TAG, "Enqueueing: " + me); |
| } |
| addLocked(di, curTimeNano, ev.flags, |
| RawInputEvent.CLASS_TOUCHSCREEN, 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.finish(); |
| } |
| |
| ms = di.mRel; |
| if (ms.changed) { |
| ms.changed = false; |
| |
| me = ms.generateRelMotion(di, curTime, |
| curTimeNano, |
| mOrientation, mGlobalMetaState); |
| if (false) Log.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.finish(); |
| } |
| } |
| } |
| } |
| |
| } catch (RuntimeException exc) { |
| Log.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) Log.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) Log.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) Log.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) Log.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; |
| Log.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) Log.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) Log.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) { |
| //Log.i(TAG, "Recycle event: " + ev); |
| if (ev.event == ev.inputDevice.mAbs.currentMove) { |
| ev.inputDevice.mAbs.currentMove = null; |
| } |
| if (ev.event == ev.inputDevice.mRel.currentMove) { |
| if (false) Log.i(TAG, "Detach rel " + ev.event); |
| ev.inputDevice.mRel.currentMove = null; |
| ev.inputDevice.mRel.mNextData[MotionEvent.SAMPLE_X] = 0; |
| ev.inputDevice.mRel.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) { |
| Log.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) { |
| Log.i(TAG, " " + name + ": min=" + info.minValue |
| + " max=" + info.maxValue |
| + " flat=" + info.flat |
| + " fuzz=" + info.fuzz); |
| info.range = info.maxValue-info.minValue; |
| return info; |
| } |
| Log.i(TAG, " " + name + ": unknown values"); |
| return null; |
| } |
| private static native boolean readEvent(RawInputEvent outEvent); |
| } |