| /* |
| * 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.Slog; |
| import android.view.Display; |
| import android.view.MotionEvent; |
| import android.view.Surface; |
| import android.view.WindowManagerPolicy; |
| |
| import java.io.PrintWriter; |
| |
| 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; |
| |
| /** |
| * Slop distance for jumpy pointer detection. |
| * The vertical range of the screen divided by this is our epsilon value. |
| */ |
| private static final int JUMPY_EPSILON_DIVISOR = 212; |
| |
| /** Number of jumpy points to drop for touchscreens that need it. */ |
| private static final int JUMPY_TRANSITION_DROPS = 3; |
| private static final int JUMPY_DROP_LIMIT = 3; |
| |
| 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; |
| boolean everChanged = 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 count the number of jumpy points dropped. |
| private int mJumpyPointsDropped = 0; |
| |
| // 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]; |
| |
| void dumpIntArray(PrintWriter pw, int[] array) { |
| pw.print("["); |
| for (int i=0; i<array.length; i++) { |
| if (i > 0) pw.print(", "); |
| pw.print(array[i]); |
| } |
| pw.print("]"); |
| } |
| |
| void dumpBooleanArray(PrintWriter pw, boolean[] array) { |
| pw.print("["); |
| for (int i=0; i<array.length; i++) { |
| if (i > 0) pw.print(", "); |
| pw.print(array[i] ? "true" : "false"); |
| } |
| pw.print("]"); |
| } |
| |
| void dump(PrintWriter pw, String prefix) { |
| pw.print(prefix); pw.print("xPrecision="); pw.print(xPrecision); |
| pw.print(" yPrecision="); pw.println(yPrecision); |
| pw.print(prefix); pw.print("xMoveScale="); pw.print(xMoveScale); |
| pw.print(" yMoveScale="); pw.println(yMoveScale); |
| if (currentMove != null) { |
| pw.print(prefix); pw.print("currentMove="); pw.println(currentMove); |
| } |
| if (changed || mDownTime != 0) { |
| pw.print(prefix); pw.print("changed="); pw.print(changed); |
| pw.print(" mDownTime="); pw.println(mDownTime); |
| } |
| pw.print(prefix); pw.print("mPointerIds="); dumpIntArray(pw, mPointerIds); |
| pw.println(""); |
| if (mSkipLastPointers || mLastNumPointers != 0) { |
| pw.print(prefix); pw.print("mSkipLastPointers="); pw.print(mSkipLastPointers); |
| pw.print(" mLastNumPointers="); pw.println(mLastNumPointers); |
| pw.print(prefix); pw.print("mLastData="); dumpIntArray(pw, mLastData); |
| pw.println(""); |
| } |
| if (mNextNumPointers != 0) { |
| pw.print(prefix); pw.print("mNextNumPointers="); pw.println(mNextNumPointers); |
| pw.print(prefix); pw.print("mNextData="); dumpIntArray(pw, mNextData); |
| pw.println(""); |
| } |
| pw.print(prefix); pw.print("mDroppedBadPoint="); |
| dumpBooleanArray(pw, mDroppedBadPoint); pw.println(""); |
| pw.print(prefix); pw.print("mAddingPointerOffset="); pw.println(mAddingPointerOffset); |
| pw.print(prefix); pw.print("mDown="); |
| dumpBooleanArray(pw, mDown); pw.println(""); |
| } |
| |
| 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) Slog.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) Slog.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; |
| Slog.i("InputDevice", "Dropping bad point #" + i |
| + ": newY=" + y + " closestDy=" + closestDy |
| + " maxDy=" + maxDy); |
| mNextData[ioff + MotionEvent.SAMPLE_Y] = closestY; |
| break; |
| } |
| } |
| mDroppedBadPoint[i] = dropped; |
| } |
| } |
| |
| void dropJumpyPoint(InputDevice dev) { |
| // We should always have absY, but let's be paranoid. |
| if (dev.absY == null) { |
| return; |
| } |
| final int jumpyEpsilon = dev.absY.range / JUMPY_EPSILON_DIVISOR; |
| |
| final int nextNumPointers = mNextNumPointers; |
| final int lastNumPointers = mLastNumPointers; |
| final int[] nextData = mNextData; |
| final int[] lastData = mLastData; |
| |
| if (nextNumPointers != mLastNumPointers) { |
| if (DEBUG_HACKS) { |
| Slog.d("InputDevice", "Different pointer count " + lastNumPointers + |
| " -> " + nextNumPointers); |
| for (int i = 0; i < nextNumPointers; i++) { |
| int ioff = i * MotionEvent.NUM_SAMPLE_DATA; |
| Slog.d("InputDevice", "Pointer " + i + " (" + |
| mNextData[ioff + MotionEvent.SAMPLE_X] + ", " + |
| mNextData[ioff + MotionEvent.SAMPLE_Y] + ")"); |
| } |
| } |
| |
| // Just drop the first few events going from 1 to 2 pointers. |
| // They're bad often enough that they're not worth considering. |
| if (lastNumPointers == 1 && nextNumPointers == 2 |
| && mJumpyPointsDropped < JUMPY_TRANSITION_DROPS) { |
| mNextNumPointers = 1; |
| mJumpyPointsDropped++; |
| } else if (lastNumPointers == 2 && nextNumPointers == 1 |
| && mJumpyPointsDropped < JUMPY_TRANSITION_DROPS) { |
| // The event when we go from 2 -> 1 tends to be messed up too |
| System.arraycopy(lastData, 0, nextData, 0, |
| lastNumPointers * MotionEvent.NUM_SAMPLE_DATA); |
| mNextNumPointers = lastNumPointers; |
| mJumpyPointsDropped++; |
| |
| if (DEBUG_HACKS) { |
| for (int i = 0; i < mNextNumPointers; i++) { |
| int ioff = i * MotionEvent.NUM_SAMPLE_DATA; |
| Slog.d("InputDevice", "Pointer " + i + " replaced (" + |
| mNextData[ioff + MotionEvent.SAMPLE_X] + ", " + |
| mNextData[ioff + MotionEvent.SAMPLE_Y] + ")"); |
| } |
| } |
| } else { |
| mJumpyPointsDropped = 0; |
| |
| if (DEBUG_HACKS) { |
| Slog.d("InputDevice", "Transition - drop limit reset"); |
| } |
| } |
| return; |
| } |
| |
| // A 'jumpy' point is one where the coordinate value for one axis |
| // has jumped to the other pointer's location. No need to do anything |
| // else if we only have one pointer. |
| if (nextNumPointers < 2) { |
| return; |
| } |
| |
| int badPointerIndex = -1; |
| int badPointerReplaceXWith = 0; |
| int badPointerReplaceYWith = 0; |
| int badPointerDistance = Integer.MIN_VALUE; |
| for (int i = nextNumPointers - 1; i >= 0; i--) { |
| boolean dropx = false; |
| boolean dropy = false; |
| |
| // Limit how many times a jumpy point can get dropped. |
| if (mJumpyPointsDropped < JUMPY_DROP_LIMIT) { |
| final int ioff = i * MotionEvent.NUM_SAMPLE_DATA; |
| final int x = nextData[ioff + MotionEvent.SAMPLE_X]; |
| final int y = nextData[ioff + MotionEvent.SAMPLE_Y]; |
| |
| if (DEBUG_HACKS) { |
| Slog.d("InputDevice", "Point " + i + " (" + x + ", " + y + ")"); |
| } |
| |
| // Check if a touch point is too close to another's coordinates |
| for (int j = 0; j < nextNumPointers && !dropx && !dropy; j++) { |
| if (j == i) { |
| continue; |
| } |
| |
| final int joff = j * MotionEvent.NUM_SAMPLE_DATA; |
| final int xOther = nextData[joff + MotionEvent.SAMPLE_X]; |
| final int yOther = nextData[joff + MotionEvent.SAMPLE_Y]; |
| |
| dropx = Math.abs(x - xOther) <= jumpyEpsilon; |
| dropy = Math.abs(y - yOther) <= jumpyEpsilon; |
| } |
| |
| if (dropx) { |
| int xreplace = lastData[MotionEvent.SAMPLE_X]; |
| int yreplace = lastData[MotionEvent.SAMPLE_Y]; |
| int distance = Math.abs(yreplace - y); |
| for (int j = 1; j < lastNumPointers; j++) { |
| final int joff = j * MotionEvent.NUM_SAMPLE_DATA; |
| int lasty = lastData[joff + MotionEvent.SAMPLE_Y]; |
| int currDist = Math.abs(lasty - y); |
| if (currDist < distance) { |
| xreplace = lastData[joff + MotionEvent.SAMPLE_X]; |
| yreplace = lasty; |
| distance = currDist; |
| } |
| } |
| |
| int badXDelta = Math.abs(xreplace - x); |
| if (badXDelta > badPointerDistance) { |
| badPointerDistance = badXDelta; |
| badPointerIndex = i; |
| badPointerReplaceXWith = xreplace; |
| badPointerReplaceYWith = yreplace; |
| } |
| } else if (dropy) { |
| int xreplace = lastData[MotionEvent.SAMPLE_X]; |
| int yreplace = lastData[MotionEvent.SAMPLE_Y]; |
| int distance = Math.abs(xreplace - x); |
| for (int j = 1; j < lastNumPointers; j++) { |
| final int joff = j * MotionEvent.NUM_SAMPLE_DATA; |
| int lastx = lastData[joff + MotionEvent.SAMPLE_X]; |
| int currDist = Math.abs(lastx - x); |
| if (currDist < distance) { |
| xreplace = lastx; |
| yreplace = lastData[joff + MotionEvent.SAMPLE_Y]; |
| distance = currDist; |
| } |
| } |
| |
| int badYDelta = Math.abs(yreplace - y); |
| if (badYDelta > badPointerDistance) { |
| badPointerDistance = badYDelta; |
| badPointerIndex = i; |
| badPointerReplaceXWith = xreplace; |
| badPointerReplaceYWith = yreplace; |
| } |
| } |
| } |
| } |
| if (badPointerIndex >= 0) { |
| if (DEBUG_HACKS) { |
| Slog.d("InputDevice", "Replacing bad pointer " + badPointerIndex + |
| " with (" + badPointerReplaceXWith + ", " + badPointerReplaceYWith + |
| ")"); |
| } |
| |
| final int offset = badPointerIndex * MotionEvent.NUM_SAMPLE_DATA; |
| nextData[offset + MotionEvent.SAMPLE_X] = badPointerReplaceXWith; |
| nextData[offset + MotionEvent.SAMPLE_Y] = badPointerReplaceYWith; |
| mJumpyPointsDropped++; |
| } else { |
| mJumpyPointsDropped = 0; |
| } |
| } |
| |
| /** |
| * 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) Slog.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) Slog.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) Slog.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) Slog.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) Slog.v("InputDevice", "Averaging " + totalPressure |
| + " weight: (" + x + "," + y + ")"); |
| mAveragedData[ioff + MotionEvent.SAMPLE_X] = x; |
| mAveragedData[ioff + MotionEvent.SAMPLE_Y] = y; |
| mAveragedData[ioff + MotionEvent.SAMPLE_PRESSURE] = |
| rawData[ioff + MotionEvent.SAMPLE_PRESSURE]; |
| mAveragedData[ioff + MotionEvent.SAMPLE_SIZE] = |
| rawData[ioff + MotionEvent.SAMPLE_SIZE]; |
| } |
| 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) Slog.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 we are not allowing multiple new points to be assigned |
| // to the same old pointer, then skip this one if it is already |
| // detected as a conflict (-2). |
| if (!allowOverlap && last2Next[j] < -1) { |
| 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 (bestDistance == -1 || distance < bestDistance) { |
| bestDistance = distance; |
| bestIndex = j; |
| } |
| } |
| |
| if (DEBUG_POINTERS) Slog.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) Slog.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) Slog.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) Slog.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) Slog.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) Slog.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) Slog.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) Slog.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); |
| System.arraycopy(next2Last, i, next2Last, |
| i+1, lastNumPointers-i); |
| break; |
| } |
| i++; |
| nextId++; |
| } |
| |
| if (DEBUG_POINTERS) Slog.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) Slog.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) Slog.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) Slog.v("InputDevice", "Removing old pointer"); |
| for (int i=0; i<lastNumPointers; i++) { |
| if (last2Next[i] == -1) { |
| if (DEBUG_POINTERS) Slog.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) { |
| Slog.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) Slog.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_INDEX_SHIFT); |
| } |
| } else { |
| if (numPointers == 1) { |
| action = MotionEvent.ACTION_UP; |
| } else { |
| action = MotionEvent.ACTION_POINTER_UP |
| | (upOrDownPointer << MotionEvent.ACTION_POINTER_INDEX_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) Slog.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) { |
| Slog.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) Slog.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) { |
| Slog.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; |
| |
| final void dump(PrintWriter pw) { |
| pw.print("minValue="); pw.print(minValue); |
| pw.print(" maxValue="); pw.print(maxValue); |
| pw.print(" range="); pw.print(range); |
| pw.print(" flat="); pw.print(flat); |
| pw.print(" fuzz="); pw.print(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; |
| } |
| }; |