1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server; 18 19 import android.content.Context; 20 import android.content.res.Configuration; 21 import android.os.Environment; 22 import android.os.LatencyTimer; 23 import android.os.PowerManager; 24 import android.os.SystemClock; 25 import android.util.Log; 26 import android.util.SparseArray; 27 import android.util.Xml; 28 import android.view.Display; 29 import android.view.KeyEvent; 30 import android.view.MotionEvent; 31 import android.view.RawInputEvent; 32 import android.view.Surface; 33 import android.view.WindowManagerPolicy; 34 35 import com.android.internal.util.XmlUtils; 36 37 import org.xmlpull.v1.XmlPullParser; 38 39 import java.io.BufferedReader; 40 import java.io.File; 41 import java.io.FileInputStream; 42 import java.io.FileNotFoundException; 43 import java.io.FileReader; 44 import java.io.IOException; 45 import java.io.InputStreamReader; 46 import java.util.ArrayList; 47 48 public abstract class KeyInputQueue { 49 static final String TAG = "KeyInputQueue"; 50 51 static final boolean DEBUG = false; 52 static final boolean DEBUG_VIRTUAL_KEYS = false; 53 static final boolean DEBUG_POINTERS = false; 54 55 /** 56 * Turn on some hacks we have to improve the touch interaction with a 57 * certain device whose screen currently is not all that good. 58 */ 59 static boolean BAD_TOUCH_HACK = false; 60 61 private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml"; 62 63 final SparseArray<InputDevice> mDevices = new SparseArray<InputDevice>(); 64 final SparseArray<InputDevice> mIgnoredDevices = new SparseArray<InputDevice>(); 65 final ArrayList<VirtualKey> mVirtualKeys = new ArrayList<VirtualKey>(); 66 final HapticFeedbackCallback mHapticFeedbackCallback; 67 68 int mGlobalMetaState = 0; 69 boolean mHaveGlobalMetaState = false; 70 71 final QueuedEvent mFirst; 72 final QueuedEvent mLast; 73 QueuedEvent mCache; 74 int mCacheCount; 75 76 Display mDisplay = null; 77 int mDisplayWidth; 78 int mDisplayHeight; 79 80 int mOrientation = Surface.ROTATION_0; 81 int[] mKeyRotationMap = null; 82 83 VirtualKey mPressedVirtualKey = null; 84 85 PowerManager.WakeLock mWakeLock; 86 87 static final int[] KEY_90_MAP = new int[] { 88 KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT, 89 KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_DPAD_UP, 90 KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_LEFT, 91 KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_DOWN, 92 }; 93 94 static final int[] KEY_180_MAP = new int[] { 95 KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_UP, 96 KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_DPAD_LEFT, 97 KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN, 98 KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT, 99 }; 100 101 static final int[] KEY_270_MAP = new int[] { 102 KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_LEFT, 103 KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_UP, 104 KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_RIGHT, 105 KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_DPAD_DOWN, 106 }; 107 108 public static final int FILTER_REMOVE = 0; 109 public static final int FILTER_KEEP = 1; 110 public static final int FILTER_ABORT = -1; 111 112 private static final boolean MEASURE_LATENCY = false; 113 private LatencyTimer lt; 114 115 public interface FilterCallback { filterEvent(QueuedEvent ev)116 int filterEvent(QueuedEvent ev); 117 } 118 119 public interface HapticFeedbackCallback { virtualKeyFeedback(KeyEvent event)120 void virtualKeyFeedback(KeyEvent event); 121 } 122 123 static class QueuedEvent { 124 InputDevice inputDevice; 125 long whenNano; 126 int flags; // From the raw event 127 int classType; // One of the class constants in InputEvent 128 Object event; 129 boolean inQueue; 130 copyFrom(QueuedEvent that)131 void copyFrom(QueuedEvent that) { 132 this.inputDevice = that.inputDevice; 133 this.whenNano = that.whenNano; 134 this.flags = that.flags; 135 this.classType = that.classType; 136 this.event = that.event; 137 } 138 139 @Override toString()140 public String toString() { 141 return "QueuedEvent{" 142 + Integer.toHexString(System.identityHashCode(this)) 143 + " " + event + "}"; 144 } 145 146 // not copied 147 QueuedEvent prev; 148 QueuedEvent next; 149 } 150 151 /** 152 * A key that exists as a part of the touch-screen, outside of the normal 153 * display area of the screen. 154 */ 155 static class VirtualKey { 156 int scancode; 157 int centerx; 158 int centery; 159 int width; 160 int height; 161 162 int hitLeft; 163 int hitTop; 164 int hitRight; 165 int hitBottom; 166 167 InputDevice lastDevice; 168 int lastKeycode; 169 checkHit(int x, int y)170 boolean checkHit(int x, int y) { 171 return (x >= hitLeft && x <= hitRight 172 && y >= hitTop && y <= hitBottom); 173 } 174 computeHitRect(InputDevice dev, int dw, int dh)175 void computeHitRect(InputDevice dev, int dw, int dh) { 176 if (dev == lastDevice) { 177 return; 178 } 179 180 if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "computeHitRect for " + scancode 181 + ": dev=" + dev + " absX=" + dev.absX + " absY=" + dev.absY); 182 183 lastDevice = dev; 184 185 int minx = dev.absX.minValue; 186 int maxx = dev.absX.maxValue; 187 188 int halfw = width/2; 189 int left = centerx - halfw; 190 int right = centerx + halfw; 191 hitLeft = minx + ((left*maxx-minx)/dw); 192 hitRight = minx + ((right*maxx-minx)/dw); 193 194 int miny = dev.absY.minValue; 195 int maxy = dev.absY.maxValue; 196 197 int halfh = height/2; 198 int top = centery - halfh; 199 int bottom = centery + halfh; 200 hitTop = miny + ((top*maxy-miny)/dh); 201 hitBottom = miny + ((bottom*maxy-miny)/dh); 202 } 203 } 204 readVirtualKeys(String deviceName)205 private void readVirtualKeys(String deviceName) { 206 try { 207 FileInputStream fis = new FileInputStream( 208 "/sys/board_properties/virtualkeys." + deviceName); 209 InputStreamReader isr = new InputStreamReader(fis); 210 BufferedReader br = new BufferedReader(isr, 2048); 211 String str = br.readLine(); 212 if (str != null) { 213 String[] it = str.split(":"); 214 if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "***** VIRTUAL KEYS: " + it); 215 final int N = it.length-6; 216 for (int i=0; i<=N; i+=6) { 217 if (!"0x01".equals(it[i])) { 218 Log.w(TAG, "Unknown virtual key type at elem #" + i 219 + ": " + it[i]); 220 continue; 221 } 222 try { 223 VirtualKey sb = new VirtualKey(); 224 sb.scancode = Integer.parseInt(it[i+1]); 225 sb.centerx = Integer.parseInt(it[i+2]); 226 sb.centery = Integer.parseInt(it[i+3]); 227 sb.width = Integer.parseInt(it[i+4]); 228 sb.height = Integer.parseInt(it[i+5]); 229 if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Virtual key " 230 + sb.scancode + ": center=" + sb.centerx + "," 231 + sb.centery + " size=" + sb.width + "x" 232 + sb.height); 233 mVirtualKeys.add(sb); 234 } catch (NumberFormatException e) { 235 Log.w(TAG, "Bad number at region " + i + " in: " 236 + str, e); 237 } 238 } 239 } 240 br.close(); 241 } catch (FileNotFoundException e) { 242 Log.i(TAG, "No virtual keys found"); 243 } catch (IOException e) { 244 Log.w(TAG, "Error reading virtual keys", e); 245 } 246 } 247 readExcludedDevices()248 private void readExcludedDevices() { 249 // Read partner-provided list of excluded input devices 250 XmlPullParser parser = null; 251 // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system". 252 File confFile = new File(Environment.getRootDirectory(), EXCLUDED_DEVICES_PATH); 253 FileReader confreader = null; 254 try { 255 confreader = new FileReader(confFile); 256 parser = Xml.newPullParser(); 257 parser.setInput(confreader); 258 XmlUtils.beginDocument(parser, "devices"); 259 260 while (true) { 261 XmlUtils.nextElement(parser); 262 if (!"device".equals(parser.getName())) { 263 break; 264 } 265 String name = parser.getAttributeValue(null, "name"); 266 if (name != null) { 267 if (DEBUG) Log.v(TAG, "addExcludedDevice " + name); 268 addExcludedDevice(name); 269 } 270 } 271 } catch (FileNotFoundException e) { 272 // It's ok if the file does not exist. 273 } catch (Exception e) { 274 Log.e(TAG, "Exception while parsing '" + confFile.getAbsolutePath() + "'", e); 275 } finally { 276 try { if (confreader != null) confreader.close(); } catch (IOException e) { } 277 } 278 } 279 KeyInputQueue(Context context, HapticFeedbackCallback hapticFeedbackCallback)280 KeyInputQueue(Context context, HapticFeedbackCallback hapticFeedbackCallback) { 281 if (MEASURE_LATENCY) { 282 lt = new LatencyTimer(100, 1000); 283 } 284 285 BAD_TOUCH_HACK = context.getResources().getBoolean( 286 com.android.internal.R.bool.config_filterTouchEvents); 287 288 mHapticFeedbackCallback = hapticFeedbackCallback; 289 290 readExcludedDevices(); 291 292 PowerManager pm = (PowerManager)context.getSystemService( 293 Context.POWER_SERVICE); 294 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 295 "KeyInputQueue"); 296 mWakeLock.setReferenceCounted(false); 297 298 mFirst = new QueuedEvent(); 299 mLast = new QueuedEvent(); 300 mFirst.next = mLast; 301 mLast.prev = mFirst; 302 303 mThread.start(); 304 } 305 setDisplay(Display display)306 public void setDisplay(Display display) { 307 mDisplay = display; 308 309 // We assume at this point that the display dimensions reflect the 310 // natural, unrotated display. We will perform hit tests for soft 311 // buttons based on that display. 312 mDisplayWidth = display.getWidth(); 313 mDisplayHeight = display.getHeight(); 314 } 315 getInputConfiguration(Configuration config)316 public void getInputConfiguration(Configuration config) { 317 synchronized (mFirst) { 318 config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; 319 config.keyboard = Configuration.KEYBOARD_NOKEYS; 320 config.navigation = Configuration.NAVIGATION_NONAV; 321 322 final int N = mDevices.size(); 323 for (int i=0; i<N; i++) { 324 InputDevice d = mDevices.valueAt(i); 325 if (d != null) { 326 if ((d.classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) { 327 config.touchscreen 328 = Configuration.TOUCHSCREEN_FINGER; 329 //Log.i("foo", "***** HAVE TOUCHSCREEN!"); 330 } 331 if ((d.classes&RawInputEvent.CLASS_ALPHAKEY) != 0) { 332 config.keyboard 333 = Configuration.KEYBOARD_QWERTY; 334 //Log.i("foo", "***** HAVE QWERTY!"); 335 } 336 if ((d.classes&RawInputEvent.CLASS_TRACKBALL) != 0) { 337 config.navigation 338 = Configuration.NAVIGATION_TRACKBALL; 339 //Log.i("foo", "***** HAVE TRACKBALL!"); 340 } else if ((d.classes&RawInputEvent.CLASS_DPAD) != 0) { 341 config.navigation 342 = Configuration.NAVIGATION_DPAD; 343 //Log.i("foo", "***** HAVE DPAD!"); 344 } 345 } 346 } 347 } 348 } 349 getScancodeState(int code)350 public int getScancodeState(int code) { 351 synchronized (mFirst) { 352 VirtualKey vk = mPressedVirtualKey; 353 if (vk != null) { 354 if (vk.scancode == code) { 355 return 2; 356 } 357 } 358 return nativeGetScancodeState(code); 359 } 360 } 361 getScancodeState(int deviceId, int code)362 public int getScancodeState(int deviceId, int code) { 363 synchronized (mFirst) { 364 VirtualKey vk = mPressedVirtualKey; 365 if (vk != null) { 366 if (vk.scancode == code) { 367 return 2; 368 } 369 } 370 return nativeGetScancodeState(deviceId, code); 371 } 372 } 373 getKeycodeState(int code)374 public int getKeycodeState(int code) { 375 synchronized (mFirst) { 376 VirtualKey vk = mPressedVirtualKey; 377 if (vk != null) { 378 if (vk.lastKeycode == code) { 379 return 2; 380 } 381 } 382 return nativeGetKeycodeState(code); 383 } 384 } 385 getKeycodeState(int deviceId, int code)386 public int getKeycodeState(int deviceId, int code) { 387 synchronized (mFirst) { 388 VirtualKey vk = mPressedVirtualKey; 389 if (vk != null) { 390 if (vk.lastKeycode == code) { 391 return 2; 392 } 393 } 394 return nativeGetKeycodeState(deviceId, code); 395 } 396 } 397 getDeviceName(int deviceId)398 public static native String getDeviceName(int deviceId); getDeviceClasses(int deviceId)399 public static native int getDeviceClasses(int deviceId); addExcludedDevice(String deviceName)400 public static native void addExcludedDevice(String deviceName); getAbsoluteInfo(int deviceId, int axis, InputDevice.AbsoluteInfo outInfo)401 public static native boolean getAbsoluteInfo(int deviceId, int axis, 402 InputDevice.AbsoluteInfo outInfo); getSwitchState(int sw)403 public static native int getSwitchState(int sw); getSwitchState(int deviceId, int sw)404 public static native int getSwitchState(int deviceId, int sw); nativeGetScancodeState(int code)405 public static native int nativeGetScancodeState(int code); nativeGetScancodeState(int deviceId, int code)406 public static native int nativeGetScancodeState(int deviceId, int code); nativeGetKeycodeState(int code)407 public static native int nativeGetKeycodeState(int code); nativeGetKeycodeState(int deviceId, int code)408 public static native int nativeGetKeycodeState(int deviceId, int code); scancodeToKeycode(int deviceId, int scancode)409 public static native int scancodeToKeycode(int deviceId, int scancode); hasKeys(int[] keycodes, boolean[] keyExists)410 public static native boolean hasKeys(int[] keycodes, boolean[] keyExists); 411 newKeyEvent(InputDevice device, long downTime, long eventTime, boolean down, int keycode, int repeatCount, int scancode, int flags)412 public static KeyEvent newKeyEvent(InputDevice device, long downTime, 413 long eventTime, boolean down, int keycode, int repeatCount, 414 int scancode, int flags) { 415 return new KeyEvent( 416 downTime, eventTime, 417 down ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP, 418 keycode, repeatCount, 419 device != null ? device.mMetaKeysState : 0, 420 device != null ? device.id : -1, scancode, 421 flags | KeyEvent.FLAG_FROM_SYSTEM); 422 } 423 424 Thread mThread = new Thread("InputDeviceReader") { 425 public void run() { 426 if (DEBUG) Log.v(TAG, "InputDeviceReader.run()"); 427 android.os.Process.setThreadPriority( 428 android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY); 429 430 RawInputEvent ev = new RawInputEvent(); 431 while (true) { 432 try { 433 InputDevice di; 434 435 // block, doesn't release the monitor 436 readEvent(ev); 437 438 boolean send = false; 439 boolean configChanged = false; 440 441 if (false) { 442 Log.i(TAG, "Input event: dev=0x" 443 + Integer.toHexString(ev.deviceId) 444 + " type=0x" + Integer.toHexString(ev.type) 445 + " scancode=" + ev.scancode 446 + " keycode=" + ev.keycode 447 + " value=" + ev.value); 448 } 449 450 if (ev.type == RawInputEvent.EV_DEVICE_ADDED) { 451 synchronized (mFirst) { 452 di = newInputDevice(ev.deviceId); 453 if (di.classes != 0) { 454 // If this device is some kind of input class, 455 // we care about it. 456 mDevices.put(ev.deviceId, di); 457 if ((di.classes & RawInputEvent.CLASS_TOUCHSCREEN) != 0) { 458 readVirtualKeys(di.name); 459 } 460 // The configuration may have changed because 461 // of this device. 462 configChanged = true; 463 } else { 464 // We won't do anything with this device. 465 mIgnoredDevices.put(ev.deviceId, di); 466 Log.i(TAG, "Ignoring non-input device: id=0x" 467 + Integer.toHexString(di.id) 468 + ", name=" + di.name); 469 } 470 } 471 } else if (ev.type == RawInputEvent.EV_DEVICE_REMOVED) { 472 synchronized (mFirst) { 473 if (false) { 474 Log.i(TAG, "Device removed: id=0x" 475 + Integer.toHexString(ev.deviceId)); 476 } 477 di = mDevices.get(ev.deviceId); 478 if (di != null) { 479 mDevices.delete(ev.deviceId); 480 // The configuration may have changed because 481 // of this device. 482 configChanged = true; 483 } else if ((di=mIgnoredDevices.get(ev.deviceId)) != null) { 484 mIgnoredDevices.remove(ev.deviceId); 485 } else { 486 Log.w(TAG, "Removing bad device id: " 487 + Integer.toHexString(ev.deviceId)); 488 continue; 489 } 490 } 491 } else { 492 di = getInputDevice(ev.deviceId); 493 if (di == null) { 494 // This may be some junk from an ignored device. 495 continue; 496 } 497 498 // first crack at it 499 send = preprocessEvent(di, ev); 500 501 if (ev.type == RawInputEvent.EV_KEY) { 502 di.mMetaKeysState = makeMetaState(ev.keycode, 503 ev.value != 0, di.mMetaKeysState); 504 mHaveGlobalMetaState = false; 505 } 506 } 507 508 if (configChanged) { 509 synchronized (mFirst) { 510 addLocked(di, System.nanoTime(), 0, 511 RawInputEvent.CLASS_CONFIGURATION_CHANGED, 512 null); 513 } 514 } 515 516 if (!send) { 517 continue; 518 } 519 520 synchronized (mFirst) { 521 // NOTE: The event timebase absolutely must be the same 522 // timebase as SystemClock.uptimeMillis(). 523 //curTime = gotOne ? ev.when : SystemClock.uptimeMillis(); 524 final long curTime = SystemClock.uptimeMillis(); 525 final long curTimeNano = System.nanoTime(); 526 //Log.i(TAG, "curTime=" + curTime + ", systemClock=" + SystemClock.uptimeMillis()); 527 528 final int classes = di.classes; 529 final int type = ev.type; 530 final int scancode = ev.scancode; 531 send = false; 532 533 // Is it a key event? 534 if (type == RawInputEvent.EV_KEY && 535 (classes&RawInputEvent.CLASS_KEYBOARD) != 0 && 536 (scancode < RawInputEvent.BTN_FIRST || 537 scancode > RawInputEvent.BTN_LAST)) { 538 boolean down; 539 if (ev.value != 0) { 540 down = true; 541 di.mKeyDownTime = curTime; 542 } else { 543 down = false; 544 } 545 int keycode = rotateKeyCodeLocked(ev.keycode); 546 addLocked(di, curTimeNano, ev.flags, 547 RawInputEvent.CLASS_KEYBOARD, 548 newKeyEvent(di, di.mKeyDownTime, curTime, down, 549 keycode, 0, scancode, 550 ((ev.flags & WindowManagerPolicy.FLAG_WOKE_HERE) != 0) 551 ? KeyEvent.FLAG_WOKE_HERE : 0)); 552 553 } else if (ev.type == RawInputEvent.EV_KEY) { 554 // Single touch protocol: touch going down or up. 555 if (ev.scancode == RawInputEvent.BTN_TOUCH && 556 (classes&(RawInputEvent.CLASS_TOUCHSCREEN 557 |RawInputEvent.CLASS_TOUCHSCREEN_MT)) 558 == RawInputEvent.CLASS_TOUCHSCREEN) { 559 di.mAbs.changed = true; 560 di.mAbs.mDown[0] = ev.value != 0; 561 562 // Trackball (mouse) protocol: press down or up. 563 } else if (ev.scancode == RawInputEvent.BTN_MOUSE && 564 (classes&RawInputEvent.CLASS_TRACKBALL) != 0) { 565 di.mRel.changed = true; 566 di.mRel.mNextNumPointers = ev.value != 0 ? 1 : 0; 567 send = true; 568 } 569 570 // Process position events from multitouch protocol. 571 } else if (ev.type == RawInputEvent.EV_ABS && 572 (classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) { 573 if (ev.scancode == RawInputEvent.ABS_MT_TOUCH_MAJOR) { 574 di.mAbs.changed = true; 575 di.mAbs.mNextData[di.mAbs.mAddingPointerOffset 576 + MotionEvent.SAMPLE_PRESSURE] = ev.value; 577 } else if (ev.scancode == RawInputEvent.ABS_MT_POSITION_X) { 578 di.mAbs.changed = true; 579 di.mAbs.mNextData[di.mAbs.mAddingPointerOffset 580 + MotionEvent.SAMPLE_X] = ev.value; 581 if (DEBUG_POINTERS) Log.v(TAG, "MT @" 582 + di.mAbs.mAddingPointerOffset 583 + " X:" + ev.value); 584 } else if (ev.scancode == RawInputEvent.ABS_MT_POSITION_Y) { 585 di.mAbs.changed = true; 586 di.mAbs.mNextData[di.mAbs.mAddingPointerOffset 587 + MotionEvent.SAMPLE_Y] = ev.value; 588 if (DEBUG_POINTERS) Log.v(TAG, "MT @" 589 + di.mAbs.mAddingPointerOffset 590 + " Y:" + ev.value); 591 } else if (ev.scancode == RawInputEvent.ABS_MT_WIDTH_MAJOR) { 592 di.mAbs.changed = true; 593 di.mAbs.mNextData[di.mAbs.mAddingPointerOffset 594 + MotionEvent.SAMPLE_SIZE] = ev.value; 595 } 596 597 // Process position events from single touch protocol. 598 } else if (ev.type == RawInputEvent.EV_ABS && 599 (classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) { 600 if (ev.scancode == RawInputEvent.ABS_X) { 601 di.mAbs.changed = true; 602 di.curTouchVals[MotionEvent.SAMPLE_X] = ev.value; 603 } else if (ev.scancode == RawInputEvent.ABS_Y) { 604 di.mAbs.changed = true; 605 di.curTouchVals[MotionEvent.SAMPLE_Y] = ev.value; 606 } else if (ev.scancode == RawInputEvent.ABS_PRESSURE) { 607 di.mAbs.changed = true; 608 di.curTouchVals[MotionEvent.SAMPLE_PRESSURE] = ev.value; 609 di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA 610 + MotionEvent.SAMPLE_PRESSURE] = ev.value; 611 } else if (ev.scancode == RawInputEvent.ABS_TOOL_WIDTH) { 612 di.mAbs.changed = true; 613 di.curTouchVals[MotionEvent.SAMPLE_SIZE] = ev.value; 614 di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA 615 + MotionEvent.SAMPLE_SIZE] = ev.value; 616 } 617 618 // Process movement events from trackball (mouse) protocol. 619 } else if (ev.type == RawInputEvent.EV_REL && 620 (classes&RawInputEvent.CLASS_TRACKBALL) != 0) { 621 // Add this relative movement into our totals. 622 if (ev.scancode == RawInputEvent.REL_X) { 623 di.mRel.changed = true; 624 di.mRel.mNextData[MotionEvent.SAMPLE_X] += ev.value; 625 } else if (ev.scancode == RawInputEvent.REL_Y) { 626 di.mRel.changed = true; 627 di.mRel.mNextData[MotionEvent.SAMPLE_Y] += ev.value; 628 } 629 } 630 631 // Handle multitouch protocol sync: tells us that the 632 // driver has returned all data for -one- of the pointers 633 // that is currently down. 634 if (ev.type == RawInputEvent.EV_SYN 635 && ev.scancode == RawInputEvent.SYN_MT_REPORT 636 && di.mAbs != null) { 637 di.mAbs.changed = true; 638 if (di.mAbs.mNextData[MotionEvent.SAMPLE_PRESSURE] > 0) { 639 // If the value is <= 0, the pointer is not 640 // down, so keep it in the count. 641 642 if (di.mAbs.mNextData[di.mAbs.mAddingPointerOffset 643 + MotionEvent.SAMPLE_PRESSURE] != 0) { 644 final int num = di.mAbs.mNextNumPointers+1; 645 di.mAbs.mNextNumPointers = num; 646 if (DEBUG_POINTERS) Log.v(TAG, 647 "MT_REPORT: now have " + num + " pointers"); 648 final int newOffset = (num <= InputDevice.MAX_POINTERS) 649 ? (num * MotionEvent.NUM_SAMPLE_DATA) 650 : (InputDevice.MAX_POINTERS * 651 MotionEvent.NUM_SAMPLE_DATA); 652 di.mAbs.mAddingPointerOffset = newOffset; 653 di.mAbs.mNextData[newOffset 654 + MotionEvent.SAMPLE_PRESSURE] = 0; 655 } else { 656 if (DEBUG_POINTERS) Log.v(TAG, "MT_REPORT: no pointer"); 657 } 658 } 659 660 // Handle general event sync: all data for the current 661 // event update has been delivered. 662 } else if (send || (ev.type == RawInputEvent.EV_SYN 663 && ev.scancode == RawInputEvent.SYN_REPORT)) { 664 if (mDisplay != null) { 665 if (!mHaveGlobalMetaState) { 666 computeGlobalMetaStateLocked(); 667 } 668 669 MotionEvent me; 670 671 InputDevice.MotionState ms = di.mAbs; 672 if (ms.changed) { 673 ms.changed = false; 674 675 if ((classes&(RawInputEvent.CLASS_TOUCHSCREEN 676 |RawInputEvent.CLASS_TOUCHSCREEN_MT)) 677 == RawInputEvent.CLASS_TOUCHSCREEN) { 678 ms.mNextNumPointers = 0; 679 if (ms.mDown[0]) { 680 System.arraycopy(di.curTouchVals, 0, 681 ms.mNextData, 0, 682 MotionEvent.NUM_SAMPLE_DATA); 683 ms.mNextNumPointers++; 684 } 685 } 686 687 if (BAD_TOUCH_HACK) { 688 ms.dropBadPoint(di); 689 } 690 691 boolean doMotion = !monitorVirtualKey(di, 692 ev, curTime, curTimeNano); 693 694 if (doMotion && ms.mNextNumPointers > 0 695 && (ms.mLastNumPointers == 0 696 || ms.mSkipLastPointers)) { 697 doMotion = !generateVirtualKeyDown(di, 698 ev, curTime, curTimeNano); 699 } 700 701 if (doMotion) { 702 // XXX Need to be able to generate 703 // multiple events here, for example 704 // if two fingers change up/down state 705 // at the same time. 706 do { 707 me = ms.generateAbsMotion(di, curTime, 708 curTimeNano, mDisplay, 709 mOrientation, mGlobalMetaState); 710 if (DEBUG_POINTERS) Log.v(TAG, "Absolute: x=" 711 + di.mAbs.mNextData[MotionEvent.SAMPLE_X] 712 + " y=" 713 + di.mAbs.mNextData[MotionEvent.SAMPLE_Y] 714 + " ev=" + me); 715 if (me != null) { 716 if (WindowManagerPolicy.WATCH_POINTER) { 717 Log.i(TAG, "Enqueueing: " + me); 718 } 719 addLocked(di, curTimeNano, ev.flags, 720 RawInputEvent.CLASS_TOUCHSCREEN, me); 721 } 722 } while (ms.hasMore()); 723 } else { 724 // We are consuming movement in the 725 // virtual key area... but still 726 // propagate this to the previous 727 // data for comparisons. 728 int num = ms.mNextNumPointers; 729 if (num > InputDevice.MAX_POINTERS) { 730 num = InputDevice.MAX_POINTERS; 731 } 732 System.arraycopy(ms.mNextData, 0, 733 ms.mLastData, 0, 734 num * MotionEvent.NUM_SAMPLE_DATA); 735 ms.mLastNumPointers = num; 736 ms.mSkipLastPointers = true; 737 } 738 739 ms.finish(); 740 } 741 742 ms = di.mRel; 743 if (ms.changed) { 744 ms.changed = false; 745 746 me = ms.generateRelMotion(di, curTime, 747 curTimeNano, 748 mOrientation, mGlobalMetaState); 749 if (false) Log.v(TAG, "Relative: x=" 750 + di.mRel.mNextData[MotionEvent.SAMPLE_X] 751 + " y=" 752 + di.mRel.mNextData[MotionEvent.SAMPLE_Y] 753 + " ev=" + me); 754 if (me != null) { 755 addLocked(di, curTimeNano, ev.flags, 756 RawInputEvent.CLASS_TRACKBALL, me); 757 } 758 759 ms.finish(); 760 } 761 } 762 } 763 } 764 765 } catch (RuntimeException exc) { 766 Log.e(TAG, "InputReaderThread uncaught exception", exc); 767 } 768 } 769 } 770 }; 771 isInsideDisplay(InputDevice dev)772 private boolean isInsideDisplay(InputDevice dev) { 773 final InputDevice.AbsoluteInfo absx = dev.absX; 774 final InputDevice.AbsoluteInfo absy = dev.absY; 775 final InputDevice.MotionState absm = dev.mAbs; 776 if (absx == null || absy == null || absm == null) { 777 return true; 778 } 779 780 if (absm.mNextData[MotionEvent.SAMPLE_X] >= absx.minValue 781 && absm.mNextData[MotionEvent.SAMPLE_X] <= absx.maxValue 782 && absm.mNextData[MotionEvent.SAMPLE_Y] >= absy.minValue 783 && absm.mNextData[MotionEvent.SAMPLE_Y] <= absy.maxValue) { 784 if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Input (" 785 + absm.mNextData[MotionEvent.SAMPLE_X] 786 + "," + absm.mNextData[MotionEvent.SAMPLE_Y] 787 + ") inside of display"); 788 return true; 789 } 790 791 return false; 792 } 793 findVirtualKey(InputDevice dev)794 private VirtualKey findVirtualKey(InputDevice dev) { 795 final int N = mVirtualKeys.size(); 796 if (N <= 0) { 797 return null; 798 } 799 800 final InputDevice.MotionState absm = dev.mAbs; 801 for (int i=0; i<N; i++) { 802 VirtualKey sb = mVirtualKeys.get(i); 803 sb.computeHitRect(dev, mDisplayWidth, mDisplayHeight); 804 if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Hit test (" 805 + absm.mNextData[MotionEvent.SAMPLE_X] + "," 806 + absm.mNextData[MotionEvent.SAMPLE_Y] + ") in code " 807 + sb.scancode + " - (" + sb.hitLeft 808 + "," + sb.hitTop + ")-(" + sb.hitRight + "," 809 + sb.hitBottom + ")"); 810 if (sb.checkHit(absm.mNextData[MotionEvent.SAMPLE_X], 811 absm.mNextData[MotionEvent.SAMPLE_Y])) { 812 if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Hit!"); 813 return sb; 814 } 815 } 816 817 return null; 818 } 819 generateVirtualKeyDown(InputDevice di, RawInputEvent ev, long curTime, long curTimeNano)820 private boolean generateVirtualKeyDown(InputDevice di, RawInputEvent ev, 821 long curTime, long curTimeNano) { 822 if (isInsideDisplay(di)) { 823 // Didn't consume event. 824 return false; 825 } 826 827 828 VirtualKey vk = findVirtualKey(di); 829 if (vk != null) { 830 final InputDevice.MotionState ms = di.mAbs; 831 mPressedVirtualKey = vk; 832 vk.lastKeycode = scancodeToKeycode(di.id, vk.scancode); 833 ms.mLastNumPointers = ms.mNextNumPointers; 834 di.mKeyDownTime = curTime; 835 if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, 836 "Generate key down for: " + vk.scancode 837 + " (keycode=" + vk.lastKeycode + ")"); 838 KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, true, 839 vk.lastKeycode, 0, vk.scancode, 840 KeyEvent.FLAG_VIRTUAL_HARD_KEY); 841 mHapticFeedbackCallback.virtualKeyFeedback(event); 842 addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, 843 event); 844 } 845 846 // We always consume the event, even if we didn't 847 // generate a key event. There are two reasons for 848 // this: to avoid spurious touches when holding 849 // the edges of the device near the touchscreen, 850 // and to avoid reporting events if there are virtual 851 // keys on the touchscreen outside of the display 852 // area. 853 // Note that for all of this we are only looking at the 854 // first pointer, since what we are handling here is the 855 // first pointer going down, and this is the coordinate 856 // that will be used to dispatch the event. 857 if (false) { 858 final InputDevice.AbsoluteInfo absx = di.absX; 859 final InputDevice.AbsoluteInfo absy = di.absY; 860 final InputDevice.MotionState absm = di.mAbs; 861 Log.v(TAG, "Rejecting (" 862 + absm.mNextData[MotionEvent.SAMPLE_X] + "," 863 + absm.mNextData[MotionEvent.SAMPLE_Y] + "): outside of (" 864 + absx.minValue + "," + absy.minValue 865 + ")-(" + absx.maxValue + "," 866 + absx.maxValue + ")"); 867 } 868 return true; 869 } 870 monitorVirtualKey(InputDevice di, RawInputEvent ev, long curTime, long curTimeNano)871 private boolean monitorVirtualKey(InputDevice di, RawInputEvent ev, 872 long curTime, long curTimeNano) { 873 VirtualKey vk = mPressedVirtualKey; 874 if (vk == null) { 875 return false; 876 } 877 878 final InputDevice.MotionState ms = di.mAbs; 879 if (ms.mNextNumPointers <= 0) { 880 mPressedVirtualKey = null; 881 ms.mLastNumPointers = 0; 882 if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Generate key up for: " + vk.scancode); 883 KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, false, 884 vk.lastKeycode, 0, vk.scancode, 885 KeyEvent.FLAG_VIRTUAL_HARD_KEY); 886 mHapticFeedbackCallback.virtualKeyFeedback(event); 887 addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, 888 event); 889 return true; 890 891 } else if (isInsideDisplay(di)) { 892 // Whoops the pointer has moved into 893 // the display area! Cancel the 894 // virtual key and start a pointer 895 // motion. 896 mPressedVirtualKey = null; 897 if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Cancel key up for: " + vk.scancode); 898 KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, false, 899 vk.lastKeycode, 0, vk.scancode, 900 KeyEvent.FLAG_CANCELED | KeyEvent.FLAG_VIRTUAL_HARD_KEY); 901 mHapticFeedbackCallback.virtualKeyFeedback(event); 902 addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, 903 event); 904 ms.mLastNumPointers = 0; 905 return false; 906 } 907 908 return true; 909 } 910 911 /** 912 * Returns a new meta state for the given keys and old state. 913 */ makeMetaState(int keycode, boolean down, int old)914 private static final int makeMetaState(int keycode, boolean down, int old) { 915 int mask; 916 switch (keycode) { 917 case KeyEvent.KEYCODE_ALT_LEFT: 918 mask = KeyEvent.META_ALT_LEFT_ON; 919 break; 920 case KeyEvent.KEYCODE_ALT_RIGHT: 921 mask = KeyEvent.META_ALT_RIGHT_ON; 922 break; 923 case KeyEvent.KEYCODE_SHIFT_LEFT: 924 mask = KeyEvent.META_SHIFT_LEFT_ON; 925 break; 926 case KeyEvent.KEYCODE_SHIFT_RIGHT: 927 mask = KeyEvent.META_SHIFT_RIGHT_ON; 928 break; 929 case KeyEvent.KEYCODE_SYM: 930 mask = KeyEvent.META_SYM_ON; 931 break; 932 default: 933 return old; 934 } 935 int result = ~(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON) 936 & (down ? (old | mask) : (old & ~mask)); 937 if (0 != (result & (KeyEvent.META_ALT_LEFT_ON | KeyEvent.META_ALT_RIGHT_ON))) { 938 result |= KeyEvent.META_ALT_ON; 939 } 940 if (0 != (result & (KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_RIGHT_ON))) { 941 result |= KeyEvent.META_SHIFT_ON; 942 } 943 return result; 944 } 945 computeGlobalMetaStateLocked()946 private void computeGlobalMetaStateLocked() { 947 int i = mDevices.size(); 948 mGlobalMetaState = 0; 949 while ((--i) >= 0) { 950 mGlobalMetaState |= mDevices.valueAt(i).mMetaKeysState; 951 } 952 mHaveGlobalMetaState = true; 953 } 954 955 /* 956 * Return true if you want the event to get passed on to the 957 * rest of the system, and false if you've handled it and want 958 * it dropped. 959 */ preprocessEvent(InputDevice device, RawInputEvent event)960 abstract boolean preprocessEvent(InputDevice device, RawInputEvent event); 961 getInputDevice(int deviceId)962 InputDevice getInputDevice(int deviceId) { 963 synchronized (mFirst) { 964 return getInputDeviceLocked(deviceId); 965 } 966 } 967 getInputDeviceLocked(int deviceId)968 private InputDevice getInputDeviceLocked(int deviceId) { 969 return mDevices.get(deviceId); 970 } 971 setOrientation(int orientation)972 public void setOrientation(int orientation) { 973 synchronized(mFirst) { 974 mOrientation = orientation; 975 switch (orientation) { 976 case Surface.ROTATION_90: 977 mKeyRotationMap = KEY_90_MAP; 978 break; 979 case Surface.ROTATION_180: 980 mKeyRotationMap = KEY_180_MAP; 981 break; 982 case Surface.ROTATION_270: 983 mKeyRotationMap = KEY_270_MAP; 984 break; 985 default: 986 mKeyRotationMap = null; 987 break; 988 } 989 } 990 } 991 rotateKeyCode(int keyCode)992 public int rotateKeyCode(int keyCode) { 993 synchronized(mFirst) { 994 return rotateKeyCodeLocked(keyCode); 995 } 996 } 997 rotateKeyCodeLocked(int keyCode)998 private int rotateKeyCodeLocked(int keyCode) { 999 int[] map = mKeyRotationMap; 1000 if (map != null) { 1001 final int N = map.length; 1002 for (int i=0; i<N; i+=2) { 1003 if (map[i] == keyCode) { 1004 return map[i+1]; 1005 } 1006 } 1007 } 1008 return keyCode; 1009 } 1010 hasEvents()1011 boolean hasEvents() { 1012 synchronized (mFirst) { 1013 return mFirst.next != mLast; 1014 } 1015 } 1016 1017 /* 1018 * returns true if we returned an event, and false if we timed out 1019 */ getEvent(long timeoutMS)1020 QueuedEvent getEvent(long timeoutMS) { 1021 long begin = SystemClock.uptimeMillis(); 1022 final long end = begin+timeoutMS; 1023 long now = begin; 1024 synchronized (mFirst) { 1025 while (mFirst.next == mLast && end > now) { 1026 try { 1027 mWakeLock.release(); 1028 mFirst.wait(end-now); 1029 } 1030 catch (InterruptedException e) { 1031 } 1032 now = SystemClock.uptimeMillis(); 1033 if (begin > now) { 1034 begin = now; 1035 } 1036 } 1037 if (mFirst.next == mLast) { 1038 return null; 1039 } 1040 QueuedEvent p = mFirst.next; 1041 mFirst.next = p.next; 1042 mFirst.next.prev = mFirst; 1043 p.inQueue = false; 1044 return p; 1045 } 1046 } 1047 1048 /** 1049 * Return true if the queue has an up event pending that corresponds 1050 * to the same key as the given key event. 1051 */ hasKeyUpEvent(KeyEvent origEvent)1052 boolean hasKeyUpEvent(KeyEvent origEvent) { 1053 synchronized (mFirst) { 1054 final int keyCode = origEvent.getKeyCode(); 1055 QueuedEvent cur = mLast.prev; 1056 while (cur.prev != null) { 1057 if (cur.classType == RawInputEvent.CLASS_KEYBOARD) { 1058 KeyEvent ke = (KeyEvent)cur.event; 1059 if (ke.getAction() == KeyEvent.ACTION_UP 1060 && ke.getKeyCode() == keyCode) { 1061 return true; 1062 } 1063 } 1064 cur = cur.prev; 1065 } 1066 } 1067 1068 return false; 1069 } 1070 recycleEvent(QueuedEvent ev)1071 void recycleEvent(QueuedEvent ev) { 1072 synchronized (mFirst) { 1073 //Log.i(TAG, "Recycle event: " + ev); 1074 if (ev.event == ev.inputDevice.mAbs.currentMove) { 1075 ev.inputDevice.mAbs.currentMove = null; 1076 } 1077 if (ev.event == ev.inputDevice.mRel.currentMove) { 1078 if (false) Log.i(TAG, "Detach rel " + ev.event); 1079 ev.inputDevice.mRel.currentMove = null; 1080 ev.inputDevice.mRel.mNextData[MotionEvent.SAMPLE_X] = 0; 1081 ev.inputDevice.mRel.mNextData[MotionEvent.SAMPLE_Y] = 0; 1082 } 1083 recycleLocked(ev); 1084 } 1085 } 1086 filterQueue(FilterCallback cb)1087 void filterQueue(FilterCallback cb) { 1088 synchronized (mFirst) { 1089 QueuedEvent cur = mLast.prev; 1090 while (cur.prev != null) { 1091 switch (cb.filterEvent(cur)) { 1092 case FILTER_REMOVE: 1093 cur.prev.next = cur.next; 1094 cur.next.prev = cur.prev; 1095 break; 1096 case FILTER_ABORT: 1097 return; 1098 } 1099 cur = cur.prev; 1100 } 1101 } 1102 } 1103 obtainLocked(InputDevice device, long whenNano, int flags, int classType, Object event)1104 private QueuedEvent obtainLocked(InputDevice device, long whenNano, 1105 int flags, int classType, Object event) { 1106 QueuedEvent ev; 1107 if (mCacheCount == 0) { 1108 ev = new QueuedEvent(); 1109 } else { 1110 ev = mCache; 1111 ev.inQueue = false; 1112 mCache = ev.next; 1113 mCacheCount--; 1114 } 1115 ev.inputDevice = device; 1116 ev.whenNano = whenNano; 1117 ev.flags = flags; 1118 ev.classType = classType; 1119 ev.event = event; 1120 return ev; 1121 } 1122 recycleLocked(QueuedEvent ev)1123 private void recycleLocked(QueuedEvent ev) { 1124 if (ev.inQueue) { 1125 throw new RuntimeException("Event already in queue!"); 1126 } 1127 if (mCacheCount < 10) { 1128 mCacheCount++; 1129 ev.next = mCache; 1130 mCache = ev; 1131 ev.inQueue = true; 1132 } 1133 } 1134 addLocked(InputDevice device, long whenNano, int flags, int classType, Object event)1135 private void addLocked(InputDevice device, long whenNano, int flags, 1136 int classType, Object event) { 1137 boolean poke = mFirst.next == mLast; 1138 1139 QueuedEvent ev = obtainLocked(device, whenNano, flags, classType, event); 1140 QueuedEvent p = mLast.prev; 1141 while (p != mFirst && ev.whenNano < p.whenNano) { 1142 p = p.prev; 1143 } 1144 1145 ev.next = p.next; 1146 ev.prev = p; 1147 p.next = ev; 1148 ev.next.prev = ev; 1149 ev.inQueue = true; 1150 1151 if (poke) { 1152 long time; 1153 if (MEASURE_LATENCY) { 1154 time = System.nanoTime(); 1155 } 1156 mFirst.notify(); 1157 mWakeLock.acquire(); 1158 if (MEASURE_LATENCY) { 1159 lt.sample("1 addLocked-queued event ", System.nanoTime() - time); 1160 } 1161 } 1162 } 1163 newInputDevice(int deviceId)1164 private InputDevice newInputDevice(int deviceId) { 1165 int classes = getDeviceClasses(deviceId); 1166 String name = getDeviceName(deviceId); 1167 InputDevice.AbsoluteInfo absX = null; 1168 InputDevice.AbsoluteInfo absY = null; 1169 InputDevice.AbsoluteInfo absPressure = null; 1170 InputDevice.AbsoluteInfo absSize = null; 1171 if (classes != 0) { 1172 Log.i(TAG, "Device added: id=0x" + Integer.toHexString(deviceId) 1173 + ", name=" + name 1174 + ", classes=" + Integer.toHexString(classes)); 1175 if ((classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) { 1176 absX = loadAbsoluteInfo(deviceId, 1177 RawInputEvent.ABS_MT_POSITION_X, "X"); 1178 absY = loadAbsoluteInfo(deviceId, 1179 RawInputEvent.ABS_MT_POSITION_Y, "Y"); 1180 absPressure = loadAbsoluteInfo(deviceId, 1181 RawInputEvent.ABS_MT_TOUCH_MAJOR, "Pressure"); 1182 absSize = loadAbsoluteInfo(deviceId, 1183 RawInputEvent.ABS_MT_WIDTH_MAJOR, "Size"); 1184 } else if ((classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) { 1185 absX = loadAbsoluteInfo(deviceId, 1186 RawInputEvent.ABS_X, "X"); 1187 absY = loadAbsoluteInfo(deviceId, 1188 RawInputEvent.ABS_Y, "Y"); 1189 absPressure = loadAbsoluteInfo(deviceId, 1190 RawInputEvent.ABS_PRESSURE, "Pressure"); 1191 absSize = loadAbsoluteInfo(deviceId, 1192 RawInputEvent.ABS_TOOL_WIDTH, "Size"); 1193 } 1194 } 1195 1196 return new InputDevice(deviceId, classes, name, absX, absY, absPressure, absSize); 1197 } 1198 loadAbsoluteInfo(int id, int channel, String name)1199 private InputDevice.AbsoluteInfo loadAbsoluteInfo(int id, int channel, 1200 String name) { 1201 InputDevice.AbsoluteInfo info = new InputDevice.AbsoluteInfo(); 1202 if (getAbsoluteInfo(id, channel, info) 1203 && info.minValue != info.maxValue) { 1204 Log.i(TAG, " " + name + ": min=" + info.minValue 1205 + " max=" + info.maxValue 1206 + " flat=" + info.flat 1207 + " fuzz=" + info.fuzz); 1208 info.range = info.maxValue-info.minValue; 1209 return info; 1210 } 1211 Log.i(TAG, " " + name + ": unknown values"); 1212 return null; 1213 } readEvent(RawInputEvent outEvent)1214 private static native boolean readEvent(RawInputEvent outEvent); 1215 } 1216