blob: f30346b22cbcc330b62fe077bec5436de62377d4 [file] [log] [blame]
/*
* 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();
}
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);
}
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;
}
// 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;
}
// 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) 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);
}
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.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);
}
}
}
}
}
} 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) {
if (false) Slog.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) {
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);
}
}
}
}