/* * 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.util.Log; import android.view.Display; import android.view.MotionEvent; import android.view.Surface; import android.view.WindowManagerPolicy; public class InputDevice { static final boolean DEBUG_POINTERS = false; static final boolean DEBUG_HACKS = false; /** Amount that trackball needs to move in order to generate a key event. */ static final int TRACKBALL_MOVEMENT_THRESHOLD = 6; /** Maximum number of pointers we will track and report. */ static final int MAX_POINTERS = 10; final int id; final int classes; final String name; final AbsoluteInfo absX; final AbsoluteInfo absY; final AbsoluteInfo absPressure; final AbsoluteInfo absSize; long mKeyDownTime = 0; int mMetaKeysState = 0; // For use by KeyInputQueue for keeping track of the current touch // data in the old non-multi-touch protocol. final int[] curTouchVals = new int[MotionEvent.NUM_SAMPLE_DATA * 2]; final MotionState mAbs = new MotionState(0, 0); final MotionState mRel = new MotionState(TRACKBALL_MOVEMENT_THRESHOLD, TRACKBALL_MOVEMENT_THRESHOLD); static class MotionState { int xPrecision; int yPrecision; float xMoveScale; float yMoveScale; MotionEvent currentMove = null; boolean changed = false; long mDownTime = 0; // The currently assigned pointer IDs, corresponding to the last data. int[] mPointerIds = new int[MAX_POINTERS]; // This is the last generated pointer data, ordered to match // mPointerIds. boolean mSkipLastPointers; int mLastNumPointers = 0; final int[] mLastData = new int[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS]; // This is the next set of pointer data being generated. It is not // in any known order, and will be propagated in to mLastData // as part of mapping it to the appropriate pointer IDs. // Note that we have one extra sample of data here, to help clients // avoid doing bounds checking. int mNextNumPointers = 0; final int[] mNextData = new int[(MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS) + MotionEvent.NUM_SAMPLE_DATA]; // Used to determine whether we dropped bad data, to avoid doing // it repeatedly. final boolean[] mDroppedBadPoint = new boolean[MAX_POINTERS]; // Used to perform averaging of reported coordinates, to smooth // the data and filter out transients during a release. static final int HISTORY_SIZE = 5; int[] mHistoryDataStart = new int[MAX_POINTERS]; int[] mHistoryDataEnd = new int[MAX_POINTERS]; final int[] mHistoryData = new int[(MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS) * HISTORY_SIZE]; final int[] mAveragedData = new int[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS]; // Temporary data structures for doing the pointer ID mapping. final int[] mLast2Next = new int[MAX_POINTERS]; final int[] mNext2Last = new int[MAX_POINTERS]; final long[] mNext2LastDistance = new long[MAX_POINTERS]; // Temporary data structure for generating the final motion data. final float[] mReportData = new float[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS]; // This is not used here, but can be used by callers for state tracking. int mAddingPointerOffset = 0; final boolean[] mDown = new boolean[MAX_POINTERS]; MotionState(int mx, int my) { xPrecision = mx; yPrecision = my; xMoveScale = mx != 0 ? (1.0f/mx) : 1.0f; yMoveScale = my != 0 ? (1.0f/my) : 1.0f; for (int i=0; i<MAX_POINTERS; i++) { mPointerIds[i] = i; } } /** * Special hack for devices that have bad screen data: if one of the * points has moved more than a screen height from the last position, * then drop it. */ void dropBadPoint(InputDevice dev) { // We should always have absY, but let's be paranoid. if (dev.absY == null) { return; } // Don't do anything if a finger is going down or up. We run // here before assigning pointer IDs, so there isn't a good // way to do per-finger matching. if (mNextNumPointers != mLastNumPointers) { return; } // We consider a single movement across more than a 7/16 of // the long size of the screen to be bad. This was a magic value // determined by looking at the maximum distance it is feasible // to actually move in one sample. final int maxDy = ((dev.absY.maxValue-dev.absY.minValue)*7)/16; // Look through all new points and see if any are farther than // acceptable from all previous points. for (int i=mNextNumPointers-1; i>=0; i--) { final int ioff = i * MotionEvent.NUM_SAMPLE_DATA; //final int x = mNextData[ioff + MotionEvent.SAMPLE_X]; final int y = mNextData[ioff + MotionEvent.SAMPLE_Y]; if (DEBUG_HACKS) Log.v("InputDevice", "Looking at next point #" + i + ": y=" + y); boolean dropped = false; if (!mDroppedBadPoint[i] && mLastNumPointers > 0) { dropped = true; int closestDy = -1; int closestY = -1; // We will drop this new point if it is sufficiently // far away from -all- last points. for (int j=mLastNumPointers-1; j>=0; j--) { final int joff = j * MotionEvent.NUM_SAMPLE_DATA; //int dx = x - mLastData[joff + MotionEvent.SAMPLE_X]; int dy = y - mLastData[joff + MotionEvent.SAMPLE_Y]; //if (dx < 0) dx = -dx; if (dy < 0) dy = -dy; if (DEBUG_HACKS) Log.v("InputDevice", "Comparing with last point #" + j + ": y=" + mLastData[joff] + " dy=" + dy); if (dy < maxDy) { dropped = false; break; } else if (closestDy < 0 || dy < closestDy) { closestDy = dy; closestY = mLastData[joff + MotionEvent.SAMPLE_Y]; } } if (dropped) { dropped = true; Log.i("InputDevice", "Dropping bad point #" + i + ": newY=" + y + " closestDy=" + closestDy + " maxDy=" + maxDy); mNextData[ioff + MotionEvent.SAMPLE_Y] = closestY; break; } } mDroppedBadPoint[i] = dropped; } } /** * Special hack for devices that have bad screen data: aggregate and * compute averages of the coordinate data, to reduce the amount of * jitter seen by applications. */ int[] generateAveragedData(int upOrDownPointer, int lastNumPointers, int nextNumPointers) { final int numPointers = mLastNumPointers; final int[] rawData = mLastData; if (DEBUG_HACKS) Log.v("InputDevice", "lastNumPointers=" + lastNumPointers + " nextNumPointers=" + nextNumPointers + " numPointers=" + numPointers); for (int i=0; i<numPointers; i++) { final int ioff = i * MotionEvent.NUM_SAMPLE_DATA; // We keep the average data in offsets based on the pointer // ID, so we don't need to move it around as fingers are // pressed and released. final int p = mPointerIds[i]; final int poff = p * MotionEvent.NUM_SAMPLE_DATA * HISTORY_SIZE; if (i == upOrDownPointer && lastNumPointers != nextNumPointers) { if (lastNumPointers < nextNumPointers) { // This pointer is going down. Clear its history // and start fresh. if (DEBUG_HACKS) Log.v("InputDevice", "Pointer down @ index " + upOrDownPointer + " id " + mPointerIds[i]); mHistoryDataStart[i] = 0; mHistoryDataEnd[i] = 0; System.arraycopy(rawData, ioff, mHistoryData, poff, MotionEvent.NUM_SAMPLE_DATA); System.arraycopy(rawData, ioff, mAveragedData, ioff, MotionEvent.NUM_SAMPLE_DATA); continue; } else { // The pointer is going up. Just fall through to // recompute the last averaged point (and don't add // it as a new point to include in the average). if (DEBUG_HACKS) Log.v("InputDevice", "Pointer up @ index " + upOrDownPointer + " id " + mPointerIds[i]); } } else { int end = mHistoryDataEnd[i]; int eoff = poff + (end*MotionEvent.NUM_SAMPLE_DATA); int oldX = mHistoryData[eoff + MotionEvent.SAMPLE_X]; int oldY = mHistoryData[eoff + MotionEvent.SAMPLE_Y]; int newX = rawData[ioff + MotionEvent.SAMPLE_X]; int newY = rawData[ioff + MotionEvent.SAMPLE_Y]; int dx = newX-oldX; int dy = newY-oldY; int delta = dx*dx + dy*dy; if (DEBUG_HACKS) Log.v("InputDevice", "Delta from last: " + delta); if (delta >= (75*75)) { // Magic number, if moving farther than this, turn // off filtering to avoid lag in response. mHistoryDataStart[i] = 0; mHistoryDataEnd[i] = 0; System.arraycopy(rawData, ioff, mHistoryData, poff, MotionEvent.NUM_SAMPLE_DATA); System.arraycopy(rawData, ioff, mAveragedData, ioff, MotionEvent.NUM_SAMPLE_DATA); continue; } else { end++; if (end >= HISTORY_SIZE) { end -= HISTORY_SIZE; } mHistoryDataEnd[i] = end; int noff = poff + (end*MotionEvent.NUM_SAMPLE_DATA); mHistoryData[noff + MotionEvent.SAMPLE_X] = newX; mHistoryData[noff + MotionEvent.SAMPLE_Y] = newY; mHistoryData[noff + MotionEvent.SAMPLE_PRESSURE] = rawData[ioff + MotionEvent.SAMPLE_PRESSURE]; int start = mHistoryDataStart[i]; if (end == start) { start++; if (start >= HISTORY_SIZE) { start -= HISTORY_SIZE; } mHistoryDataStart[i] = start; } } } // Now compute the average. int start = mHistoryDataStart[i]; int end = mHistoryDataEnd[i]; int x=0, y=0; int totalPressure = 0; while (start != end) { int soff = poff + (start*MotionEvent.NUM_SAMPLE_DATA); int pressure = mHistoryData[soff + MotionEvent.SAMPLE_PRESSURE]; if (pressure <= 0) pressure = 1; x += mHistoryData[soff + MotionEvent.SAMPLE_X] * pressure; y += mHistoryData[soff + MotionEvent.SAMPLE_Y] * pressure; totalPressure += pressure; start++; if (start >= HISTORY_SIZE) start = 0; } int eoff = poff + (end*MotionEvent.NUM_SAMPLE_DATA); int pressure = mHistoryData[eoff + MotionEvent.SAMPLE_PRESSURE]; if (pressure <= 0) pressure = 1; x += mHistoryData[eoff + MotionEvent.SAMPLE_X] * pressure; y += mHistoryData[eoff + MotionEvent.SAMPLE_Y] * pressure; totalPressure += pressure; x /= totalPressure; y /= totalPressure; if (DEBUG_HACKS) Log.v("InputDevice", "Averaging " + totalPressure + " weight: (" + x + "," + y + ")"); mAveragedData[ioff + MotionEvent.SAMPLE_X] = x; mAveragedData[ioff + MotionEvent.SAMPLE_Y] = y; } return mAveragedData; } private boolean assignPointer(int nextIndex, boolean allowOverlap) { final int lastNumPointers = mLastNumPointers; final int[] next2Last = mNext2Last; final long[] next2LastDistance = mNext2LastDistance; final int[] last2Next = mLast2Next; final int[] lastData = mLastData; final int[] nextData = mNextData; final int id = nextIndex * MotionEvent.NUM_SAMPLE_DATA; if (DEBUG_POINTERS) Log.v("InputDevice", "assignPointer: nextIndex=" + nextIndex + " dataOff=" + id); final int x1 = nextData[id + MotionEvent.SAMPLE_X]; final int y1 = nextData[id + MotionEvent.SAMPLE_Y]; long bestDistance = -1; int bestIndex = -1; for (int j=0; j<lastNumPointers; j++) { if (!allowOverlap && last2Next[j] < 0) { continue; } final int jd = j * MotionEvent.NUM_SAMPLE_DATA; final int xd = lastData[jd + MotionEvent.SAMPLE_X] - x1; final int yd = lastData[jd + MotionEvent.SAMPLE_Y] - y1; final long distance = xd*(long)xd + yd*(long)yd; if (j == 0 || distance < bestDistance) { bestDistance = distance; bestIndex = j; } } if (DEBUG_POINTERS) Log.v("InputDevice", "New index " + nextIndex + " best old index=" + bestIndex + " (distance=" + bestDistance + ")"); next2Last[nextIndex] = bestIndex; next2LastDistance[nextIndex] = bestDistance; if (bestIndex < 0) { return true; } if (last2Next[bestIndex] == -1) { last2Next[bestIndex] = nextIndex; return false; } if (DEBUG_POINTERS) Log.v("InputDevice", "Old index " + bestIndex + " has multiple best new pointers!"); last2Next[bestIndex] = -2; return true; } private int updatePointerIdentifiers() { final int[] lastData = mLastData; final int[] nextData = mNextData; final int nextNumPointers = mNextNumPointers; final int lastNumPointers = mLastNumPointers; if (nextNumPointers == 1 && lastNumPointers == 1) { System.arraycopy(nextData, 0, lastData, 0, MotionEvent.NUM_SAMPLE_DATA); return -1; } // Clear our old state. final int[] last2Next = mLast2Next; for (int i=0; i<lastNumPointers; i++) { last2Next[i] = -1; } if (DEBUG_POINTERS) Log.v("InputDevice", "Update pointers: lastNumPointers=" + lastNumPointers + " nextNumPointers=" + nextNumPointers); // Figure out the closes new points to the previous points. final int[] next2Last = mNext2Last; final long[] next2LastDistance = mNext2LastDistance; boolean conflicts = false; for (int i=0; i<nextNumPointers; i++) { conflicts |= assignPointer(i, true); } // Resolve ambiguities in pointer mappings, when two or more // new pointer locations find their best previous location is // the same. if (conflicts) { if (DEBUG_POINTERS) Log.v("InputDevice", "Resolving conflicts"); for (int i=0; i<lastNumPointers; i++) { if (last2Next[i] != -2) { continue; } // Note that this algorithm is far from perfect. Ideally // we should do something like the one described at // http://portal.acm.org/citation.cfm?id=997856 if (DEBUG_POINTERS) Log.v("InputDevice", "Resolving last index #" + i); int numFound; do { numFound = 0; long worstDistance = 0; int worstJ = -1; for (int j=0; j<nextNumPointers; j++) { if (next2Last[j] != i) { continue; } numFound++; if (worstDistance < next2LastDistance[j]) { worstDistance = next2LastDistance[j]; worstJ = j; } } if (worstJ >= 0) { if (DEBUG_POINTERS) Log.v("InputDevice", "Worst new pointer: " + worstJ + " (distance=" + worstDistance + ")"); if (assignPointer(worstJ, false)) { // In this case there is no last pointer // remaining for this new one! next2Last[worstJ] = -1; } } } while (numFound > 2); } } int retIndex = -1; if (lastNumPointers < nextNumPointers) { // We have one or more new pointers that are down. Create a // new pointer identifier for one of them. if (DEBUG_POINTERS) Log.v("InputDevice", "Adding new pointer"); int nextId = 0; int i=0; while (i < lastNumPointers) { if (mPointerIds[i] > nextId) { // Found a hole, insert the pointer here. if (DEBUG_POINTERS) Log.v("InputDevice", "Inserting new pointer at hole " + i); System.arraycopy(mPointerIds, i, mPointerIds, i+1, lastNumPointers-i); System.arraycopy(lastData, i*MotionEvent.NUM_SAMPLE_DATA, lastData, (i+1)*MotionEvent.NUM_SAMPLE_DATA, (lastNumPointers-i)*MotionEvent.NUM_SAMPLE_DATA); break; } i++; nextId++; } if (DEBUG_POINTERS) Log.v("InputDevice", "New pointer id " + nextId + " at index " + i); mLastNumPointers++; retIndex = i; mPointerIds[i] = nextId; // And assign this identifier to the first new pointer. for (int j=0; j<nextNumPointers; j++) { if (next2Last[j] < 0) { if (DEBUG_POINTERS) Log.v("InputDevice", "Assigning new id to new pointer index " + j); next2Last[j] = i; break; } } } // Propagate all of the current data into the appropriate // location in the old data to match the pointer ID that was // assigned to it. for (int i=0; i<nextNumPointers; i++) { int lastIndex = next2Last[i]; if (lastIndex >= 0) { if (DEBUG_POINTERS) Log.v("InputDevice", "Copying next pointer index " + i + " to last index " + lastIndex); System.arraycopy(nextData, i*MotionEvent.NUM_SAMPLE_DATA, lastData, lastIndex*MotionEvent.NUM_SAMPLE_DATA, MotionEvent.NUM_SAMPLE_DATA); } } if (lastNumPointers > nextNumPointers) { // One or more pointers has gone up. Find the first one, // and adjust accordingly. if (DEBUG_POINTERS) Log.v("InputDevice", "Removing old pointer"); for (int i=0; i<lastNumPointers; i++) { if (last2Next[i] == -1) { if (DEBUG_POINTERS) Log.v("InputDevice", "Removing old pointer at index " + i); retIndex = i; break; } } } return retIndex; } void removeOldPointer(int index) { final int lastNumPointers = mLastNumPointers; if (index >= 0 && index < lastNumPointers) { System.arraycopy(mPointerIds, index+1, mPointerIds, index, lastNumPointers-index-1); System.arraycopy(mLastData, (index+1)*MotionEvent.NUM_SAMPLE_DATA, mLastData, (index)*MotionEvent.NUM_SAMPLE_DATA, (lastNumPointers-index-1)*MotionEvent.NUM_SAMPLE_DATA); mLastNumPointers--; } } MotionEvent generateAbsMotion(InputDevice device, long curTime, long curTimeNano, Display display, int orientation, int metaState) { if (mSkipLastPointers) { mSkipLastPointers = false; mLastNumPointers = 0; } if (mNextNumPointers <= 0 && mLastNumPointers <= 0) { return null; } final int lastNumPointers = mLastNumPointers; final int nextNumPointers = mNextNumPointers; if (mNextNumPointers > MAX_POINTERS) { Log.w("InputDevice", "Number of pointers " + mNextNumPointers + " exceeded maximum of " + MAX_POINTERS); mNextNumPointers = MAX_POINTERS; } int upOrDownPointer = updatePointerIdentifiers(); final float[] reportData = mReportData; final int[] rawData; if (KeyInputQueue.BAD_TOUCH_HACK) { rawData = generateAveragedData(upOrDownPointer, lastNumPointers, nextNumPointers); } else { rawData = mLastData; } final int numPointers = mLastNumPointers; if (DEBUG_POINTERS) Log.v("InputDevice", "Processing " + numPointers + " pointers (going from " + lastNumPointers + " to " + nextNumPointers + ")"); for (int i=0; i<numPointers; i++) { final int pos = i * MotionEvent.NUM_SAMPLE_DATA; reportData[pos + MotionEvent.SAMPLE_X] = rawData[pos + MotionEvent.SAMPLE_X]; reportData[pos + MotionEvent.SAMPLE_Y] = rawData[pos + MotionEvent.SAMPLE_Y]; reportData[pos + MotionEvent.SAMPLE_PRESSURE] = rawData[pos + MotionEvent.SAMPLE_PRESSURE]; reportData[pos + MotionEvent.SAMPLE_SIZE] = rawData[pos + MotionEvent.SAMPLE_SIZE]; } int action; int edgeFlags = 0; if (nextNumPointers != lastNumPointers) { if (nextNumPointers > lastNumPointers) { if (lastNumPointers == 0) { action = MotionEvent.ACTION_DOWN; mDownTime = curTime; } else { action = MotionEvent.ACTION_POINTER_DOWN | (upOrDownPointer << MotionEvent.ACTION_POINTER_ID_SHIFT); } } else { if (numPointers == 1) { action = MotionEvent.ACTION_UP; } else { action = MotionEvent.ACTION_POINTER_UP | (upOrDownPointer << MotionEvent.ACTION_POINTER_ID_SHIFT); } } currentMove = null; } else { action = MotionEvent.ACTION_MOVE; } final int dispW = display.getWidth()-1; final int dispH = display.getHeight()-1; int w = dispW; int h = dispH; if (orientation == Surface.ROTATION_90 || orientation == Surface.ROTATION_270) { int tmp = w; w = h; h = tmp; } final AbsoluteInfo absX = device.absX; final AbsoluteInfo absY = device.absY; final AbsoluteInfo absPressure = device.absPressure; final AbsoluteInfo absSize = device.absSize; for (int i=0; i<numPointers; i++) { final int j = i * MotionEvent.NUM_SAMPLE_DATA; if (absX != null) { reportData[j + MotionEvent.SAMPLE_X] = ((reportData[j + MotionEvent.SAMPLE_X]-absX.minValue) / absX.range) * w; } if (absY != null) { reportData[j + MotionEvent.SAMPLE_Y] = ((reportData[j + MotionEvent.SAMPLE_Y]-absY.minValue) / absY.range) * h; } if (absPressure != null) { reportData[j + MotionEvent.SAMPLE_PRESSURE] = ((reportData[j + MotionEvent.SAMPLE_PRESSURE]-absPressure.minValue) / (float)absPressure.range); } if (absSize != null) { reportData[j + MotionEvent.SAMPLE_SIZE] = ((reportData[j + MotionEvent.SAMPLE_SIZE]-absSize.minValue) / (float)absSize.range); } switch (orientation) { case Surface.ROTATION_90: { final float temp = reportData[j + MotionEvent.SAMPLE_X]; reportData[j + MotionEvent.SAMPLE_X] = reportData[j + MotionEvent.SAMPLE_Y]; reportData[j + MotionEvent.SAMPLE_Y] = w-temp; break; } case Surface.ROTATION_180: { reportData[j + MotionEvent.SAMPLE_X] = w-reportData[j + MotionEvent.SAMPLE_X]; reportData[j + MotionEvent.SAMPLE_Y] = h-reportData[j + MotionEvent.SAMPLE_Y]; break; } case Surface.ROTATION_270: { final float temp = reportData[j + MotionEvent.SAMPLE_X]; reportData[j + MotionEvent.SAMPLE_X] = h-reportData[j + MotionEvent.SAMPLE_Y]; reportData[j + MotionEvent.SAMPLE_Y] = temp; break; } } } // We only consider the first pointer when computing the edge // flags, since they are global to the event. if (action == MotionEvent.ACTION_DOWN) { if (reportData[MotionEvent.SAMPLE_X] <= 0) { edgeFlags |= MotionEvent.EDGE_LEFT; } else if (reportData[MotionEvent.SAMPLE_X] >= dispW) { edgeFlags |= MotionEvent.EDGE_RIGHT; } if (reportData[MotionEvent.SAMPLE_Y] <= 0) { edgeFlags |= MotionEvent.EDGE_TOP; } else if (reportData[MotionEvent.SAMPLE_Y] >= dispH) { edgeFlags |= MotionEvent.EDGE_BOTTOM; } } if (currentMove != null) { if (false) Log.i("InputDevice", "Adding batch x=" + reportData[MotionEvent.SAMPLE_X] + " y=" + reportData[MotionEvent.SAMPLE_Y] + " to " + currentMove); currentMove.addBatch(curTime, reportData, metaState); if (WindowManagerPolicy.WATCH_POINTER) { Log.i("KeyInputQueue", "Updating: " + currentMove); } return null; } MotionEvent me = MotionEvent.obtainNano(mDownTime, curTime, curTimeNano, action, numPointers, mPointerIds, reportData, metaState, xPrecision, yPrecision, device.id, edgeFlags); if (action == MotionEvent.ACTION_MOVE) { currentMove = me; } if (nextNumPointers < lastNumPointers) { removeOldPointer(upOrDownPointer); } return me; } boolean hasMore() { return mLastNumPointers != mNextNumPointers; } void finish() { mNextNumPointers = mAddingPointerOffset = 0; mNextData[MotionEvent.SAMPLE_PRESSURE] = 0; } MotionEvent generateRelMotion(InputDevice device, long curTime, long curTimeNano, int orientation, int metaState) { final float[] scaled = mReportData; // For now we only support 1 pointer with relative motions. scaled[MotionEvent.SAMPLE_X] = mNextData[MotionEvent.SAMPLE_X]; scaled[MotionEvent.SAMPLE_Y] = mNextData[MotionEvent.SAMPLE_Y]; scaled[MotionEvent.SAMPLE_PRESSURE] = 1.0f; scaled[MotionEvent.SAMPLE_SIZE] = 0; int edgeFlags = 0; int action; if (mNextNumPointers != mLastNumPointers) { mNextData[MotionEvent.SAMPLE_X] = mNextData[MotionEvent.SAMPLE_Y] = 0; if (mNextNumPointers > 0 && mLastNumPointers == 0) { action = MotionEvent.ACTION_DOWN; mDownTime = curTime; } else if (mNextNumPointers == 0) { action = MotionEvent.ACTION_UP; } else { action = MotionEvent.ACTION_MOVE; } mLastNumPointers = mNextNumPointers; currentMove = null; } else { action = MotionEvent.ACTION_MOVE; } scaled[MotionEvent.SAMPLE_X] *= xMoveScale; scaled[MotionEvent.SAMPLE_Y] *= yMoveScale; switch (orientation) { case Surface.ROTATION_90: { final float temp = scaled[MotionEvent.SAMPLE_X]; scaled[MotionEvent.SAMPLE_X] = scaled[MotionEvent.SAMPLE_Y]; scaled[MotionEvent.SAMPLE_Y] = -temp; break; } case Surface.ROTATION_180: { scaled[MotionEvent.SAMPLE_X] = -scaled[MotionEvent.SAMPLE_X]; scaled[MotionEvent.SAMPLE_Y] = -scaled[MotionEvent.SAMPLE_Y]; break; } case Surface.ROTATION_270: { final float temp = scaled[MotionEvent.SAMPLE_X]; scaled[MotionEvent.SAMPLE_X] = -scaled[MotionEvent.SAMPLE_Y]; scaled[MotionEvent.SAMPLE_Y] = temp; break; } } if (currentMove != null) { if (false) Log.i("InputDevice", "Adding batch x=" + scaled[MotionEvent.SAMPLE_X] + " y=" + scaled[MotionEvent.SAMPLE_Y] + " to " + currentMove); currentMove.addBatch(curTime, scaled, metaState); if (WindowManagerPolicy.WATCH_POINTER) { Log.i("KeyInputQueue", "Updating: " + currentMove); } return null; } MotionEvent me = MotionEvent.obtainNano(mDownTime, curTime, curTimeNano, action, 1, mPointerIds, scaled, metaState, xPrecision, yPrecision, device.id, edgeFlags); if (action == MotionEvent.ACTION_MOVE) { currentMove = me; } return me; } } static class AbsoluteInfo { int minValue; int maxValue; int range; int flat; int fuzz; }; InputDevice(int _id, int _classes, String _name, AbsoluteInfo _absX, AbsoluteInfo _absY, AbsoluteInfo _absPressure, AbsoluteInfo _absSize) { id = _id; classes = _classes; name = _name; absX = _absX; absY = _absY; absPressure = _absPressure; absSize = _absSize; } };