This takes care of allowing us to cancel the back button. The back button is a bear because it is strewn all over the place -- everywhere you can close something, there is some code looking for the back button that now needs to deal with being canceled. The main things changed are activity (of course), dialog, input method, search dialog. There are some other misc places in the framework (and some I missed here that I will get in a second pass). To facility all of this, the key dispatching APIs now provide a lot more support for dealing with looking for cancelled keys, and incidentally also provide an actual API for catching long key presses. This also helped clean up the code in PhoneWindow where it deals with all of the combinations of key pressed and releases. (And also allows people to override Activity.onKeyLongPress() to provide a different long press action for a standard key like search.) And while I was doing this, I reworked how we detect long presses by having this be part of the key event delivered by the window manager. This should greatly reduce (hopefully outright eliminate) the problems with long presses being mis-detected when an application is being slow. Change-Id: Ia19066b8d588d573df3eee6d96e1c90fdc19f57d
1152 lines
49 KiB
Java
1152 lines
49 KiB
Java
/*
|
|
* 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_VIRTUAL_KEYS = false;
|
|
static final boolean DEBUG_POINTERS = 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) 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);
|
|
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) {
|
|
Log.d(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 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 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 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() {
|
|
Log.d(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) {
|
|
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;
|
|
} else if (ev.scancode == RawInputEvent.BTN_2 &&
|
|
(classes&(RawInputEvent.CLASS_TOUCHSCREEN
|
|
|RawInputEvent.CLASS_TOUCHSCREEN_MT))
|
|
== RawInputEvent.CLASS_TOUCHSCREEN) {
|
|
di.mAbs.changed = true;
|
|
di.mAbs.mDown[1] = ev.value != 0;
|
|
} else if (ev.scancode == RawInputEvent.BTN_MOUSE &&
|
|
(classes&RawInputEvent.CLASS_TRACKBALL) != 0) {
|
|
di.mRel.changed = true;
|
|
di.mRel.mNextNumPointers = ev.value != 0 ? 1 : 0;
|
|
send = true;
|
|
}
|
|
|
|
} else if (ev.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;
|
|
}
|
|
|
|
} else if (ev.type == RawInputEvent.EV_ABS &&
|
|
(classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) {
|
|
// Finger 1
|
|
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;
|
|
|
|
// Finger 2
|
|
} else if (ev.scancode == RawInputEvent.ABS_HAT0X) {
|
|
di.mAbs.changed = true;
|
|
di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA
|
|
+ MotionEvent.SAMPLE_X] = ev.value;
|
|
} else if (ev.scancode == RawInputEvent.ABS_HAT0Y) {
|
|
di.mAbs.changed = true;
|
|
di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA
|
|
+ MotionEvent.SAMPLE_Y] = ev.value;
|
|
}
|
|
|
|
} 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;
|
|
}
|
|
}
|
|
|
|
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");
|
|
}
|
|
}
|
|
} 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 (ms.mDown[1]) {
|
|
System.arraycopy(di.curTouchVals,
|
|
MotionEvent.NUM_SAMPLE_DATA,
|
|
ms.mNextData,
|
|
ms.mNextNumPointers
|
|
* MotionEvent.NUM_SAMPLE_DATA,
|
|
MotionEvent.NUM_SAMPLE_DATA);
|
|
ms.mNextNumPointers++;
|
|
}
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
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);
|
|
}
|