/* * 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; 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 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 0) pw.print(", "); pw.print(array[i]); } pw.print("]"); } void dumpBooleanArray(PrintWriter pw, boolean[] array) { pw.print("["); for (int i=0; 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=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; } } /** * 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= (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= 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); 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= 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= 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 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= 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; } };