diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
commit | 54b6cfa9a9e5b861a9930af873580d6dc20f773c (patch) | |
tree | 35051494d2af230dce54d6b31c6af8fc24091316 /services/java/com/android/server/KeyInputQueue.java |
Initial Contribution
Diffstat (limited to 'services/java/com/android/server/KeyInputQueue.java')
-rw-r--r-- | services/java/com/android/server/KeyInputQueue.java | 620 |
1 files changed, 620 insertions, 0 deletions
diff --git a/services/java/com/android/server/KeyInputQueue.java b/services/java/com/android/server/KeyInputQueue.java new file mode 100644 index 000000000000..c67754b5c9d7 --- /dev/null +++ b/services/java/com/android/server/KeyInputQueue.java @@ -0,0 +1,620 @@ +/* + * 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.SystemClock; +import android.os.PowerManager; +import android.util.Log; +import android.util.SparseArray; +import android.view.Display; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.RawInputEvent; +import android.view.Surface; +import android.view.WindowManagerPolicy; + +public abstract class KeyInputQueue { + static final String TAG = "KeyInputQueue"; + + SparseArray<InputDevice> mDevices = new SparseArray(); + + int mGlobalMetaState = 0; + boolean mHaveGlobalMetaState = false; + + final QueuedEvent mFirst; + final QueuedEvent mLast; + QueuedEvent mCache; + int mCacheCount; + + Display mDisplay = null; + + int mOrientation = Surface.ROTATION_0; + int[] mKeyRotationMap = 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; + + public interface FilterCallback { + int filterEvent(QueuedEvent ev); + } + + static class QueuedEvent { + InputDevice inputDevice; + long when; + 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.when = that.when; + 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; + } + + KeyInputQueue(Context context) { + 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; + } + + public void getInputConfiguration(Configuration config) { + synchronized (mFirst) { + config.touchscreen = Configuration.TOUCHSCREEN_FINGER; + //Resources.Configuration.TOUCHSCREEN_NOTOUCH; + config.keyboard = Configuration.KEYBOARD_QWERTY; + config.navigation = Configuration.NAVIGATION_TRACKBALL; + 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_TRACKBALL) != 0) { + config.navigation + = Configuration.NAVIGATION_TRACKBALL; + //Log.i("foo", "***** HAVE TRACKBALL!"); + } + } + } + } + } + + public static native String getDeviceName(int deviceId); + public static native int getDeviceClasses(int deviceId); + 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 getScancodeState(int sw); + 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 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); + } + + Thread mThread = new Thread("InputDeviceReader") { + public void run() { + android.os.Process.setThreadPriority( + android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY); + + try { + RawInputEvent ev = new RawInputEvent(); + while (true) { + 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); + mDevices.put(ev.deviceId, di); + configChanged = true; + } + } else if (ev.type == RawInputEvent.EV_DEVICE_REMOVED) { + synchronized (mFirst) { + Log.i(TAG, "Device removed: id=0x" + + Integer.toHexString(ev.deviceId)); + di = mDevices.get(ev.deviceId); + if (di != null) { + mDevices.delete(ev.deviceId); + configChanged = true; + } else { + Log.w(TAG, "Bad device id: " + ev.deviceId); + } + } + } else { + di = getInputDevice(ev.deviceId); + + // 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 (di == null) { + continue; + } + + if (configChanged) { + synchronized (mFirst) { + addLocked(di, SystemClock.uptimeMillis(), 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(); + //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.mDownTime = curTime; + } else { + down = false; + } + int keycode = rotateKeyCodeLocked(ev.keycode); + addLocked(di, curTime, ev.flags, + RawInputEvent.CLASS_KEYBOARD, + newKeyEvent(di, di.mDownTime, curTime, down, + keycode, 0, scancode, + ((ev.flags & WindowManagerPolicy.FLAG_WOKE_HERE) != 0) + ? KeyEvent.FLAG_WOKE_HERE : 0)); + } else if (ev.type == RawInputEvent.EV_KEY) { + if (ev.scancode == RawInputEvent.BTN_TOUCH && + (classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) { + di.mAbs.changed = true; + di.mAbs.down = ev.value != 0; + } + if (ev.scancode == RawInputEvent.BTN_MOUSE && + (classes&RawInputEvent.CLASS_TRACKBALL) != 0) { + di.mRel.changed = true; + di.mRel.down = ev.value != 0; + send = true; + } + + } else if (ev.type == RawInputEvent.EV_ABS && + (classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) { + if (ev.scancode == RawInputEvent.ABS_X) { + di.mAbs.changed = true; + di.mAbs.x = ev.value; + } else if (ev.scancode == RawInputEvent.ABS_Y) { + di.mAbs.changed = true; + di.mAbs.y = ev.value; + } else if (ev.scancode == RawInputEvent.ABS_PRESSURE) { + di.mAbs.changed = true; + di.mAbs.pressure = ev.value; + } else if (ev.scancode == RawInputEvent.ABS_TOOL_WIDTH) { + di.mAbs.changed = true; + di.mAbs.size = ev.value; + } + + } 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.x += ev.value; + } else if (ev.scancode == RawInputEvent.REL_Y) { + di.mRel.changed = true; + di.mRel.y += ev.value; + } + } + + if (send || ev.type == RawInputEvent.EV_SYN) { + if (mDisplay != null) { + if (!mHaveGlobalMetaState) { + computeGlobalMetaStateLocked(); + } + + MotionEvent me; + me = di.mAbs.generateMotion(di, curTime, 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, curTime, ev.flags, + RawInputEvent.CLASS_TOUCHSCREEN, me); + } + me = di.mRel.generateMotion(di, curTime, 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, curTime, ev.flags, + RawInputEvent.CLASS_TRACKBALL, me); + } + } + } + } + } + } + catch (RuntimeException exc) { + Log.e(TAG, "InputReaderThread uncaught exception", exc); + } + } + }; + + /** + * 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; + } + } + + 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) { + ev.inputDevice.mRel.currentMove = null; + ev.inputDevice.mRel.x = 0; + ev.inputDevice.mRel.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 when, + 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.when = when; + 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 when, int flags, + int classType, Object event) { + boolean poke = mFirst.next == mLast; + + QueuedEvent ev = obtainLocked(device, when, flags, classType, event); + QueuedEvent p = mLast.prev; + while (p != mFirst && ev.when < p.when) { + p = p.prev; + } + + ev.next = p.next; + ev.prev = p; + p.next = ev; + ev.next.prev = ev; + ev.inQueue = true; + + if (poke) { + mFirst.notify(); + mWakeLock.acquire(); + } + } + + private InputDevice newInputDevice(int deviceId) { + int classes = getDeviceClasses(deviceId); + String name = getDeviceName(deviceId); + Log.i(TAG, "Device added: id=0x" + Integer.toHexString(deviceId) + + ", name=" + name + + ", classes=" + Integer.toHexString(classes)); + InputDevice.AbsoluteInfo absX; + InputDevice.AbsoluteInfo absY; + InputDevice.AbsoluteInfo absPressure; + InputDevice.AbsoluteInfo absSize; + 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"); + } else { + absX = null; + absY = null; + absPressure = null; + absSize = null; + } + + 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); +} |