/* * 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<InputDevice>(); 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_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!"); } } } } } 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 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() { 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) { if (false) Log.i(TAG, "Detach rel " + ev.event); 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); }