blob: 882a07903741fa4c278f151b0d99c8e7aef09a98 [file] [log] [blame]
/*
* 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 android.view;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.Config;
/**
* Object used to report movement (mouse, pen, finger, trackball) events. This
* class may hold either absolute or relative movements, depending on what
* it is being used for.
*/
public final class MotionEvent implements Parcelable {
/**
* Constant for {@link #getAction}: A pressed gesture has started, the
* motion contains the initial starting location.
*/
public static final int ACTION_DOWN = 0;
/**
* Constant for {@link #getAction}: A pressed gesture has finished, the
* motion contains the final release location as well as any intermediate
* points since the last down or move event.
*/
public static final int ACTION_UP = 1;
/**
* Constant for {@link #getAction}: A change has happened during a
* press gesture (between {@link #ACTION_DOWN} and {@link #ACTION_UP}).
* The motion contains the most recent point, as well as any intermediate
* points since the last down or move event.
*/
public static final int ACTION_MOVE = 2;
/**
* Constant for {@link #getAction}: The current gesture has been aborted.
* You will not receive any more points in it. You should treat this as
* an up event, but not perform any action that you normally would.
*/
public static final int ACTION_CANCEL = 3;
/**
* Constant for {@link #getAction}: A movement has happened outside of the
* normal bounds of the UI element. This does not provide a full gesture,
* but only the initial location of the movement/touch.
*/
public static final int ACTION_OUTSIDE = 4;
private static final boolean TRACK_RECYCLED_LOCATION = false;
/**
* Flag indicating the motion event intersected the top edge of the screen.
*/
public static final int EDGE_TOP = 0x00000001;
/**
* Flag indicating the motion event intersected the bottom edge of the screen.
*/
public static final int EDGE_BOTTOM = 0x00000002;
/**
* Flag indicating the motion event intersected the left edge of the screen.
*/
public static final int EDGE_LEFT = 0x00000004;
/**
* Flag indicating the motion event intersected the right edge of the screen.
*/
public static final int EDGE_RIGHT = 0x00000008;
static private final int MAX_RECYCLED = 10;
static private Object gRecyclerLock = new Object();
static private int gRecyclerUsed = 0;
static private MotionEvent gRecyclerTop = null;
private long mDownTime;
private long mEventTime;
private int mAction;
private float mX;
private float mY;
private float mRawX;
private float mRawY;
private float mPressure;
private float mSize;
private int mMetaState;
private int mNumHistory;
private float[] mHistory;
private long[] mHistoryTimes;
private float mXPrecision;
private float mYPrecision;
private int mDeviceId;
private int mEdgeFlags;
private MotionEvent mNext;
private RuntimeException mRecycledLocation;
private boolean mRecycled;
private MotionEvent() {
}
static private MotionEvent obtain() {
synchronized (gRecyclerLock) {
if (gRecyclerTop == null) {
return new MotionEvent();
}
MotionEvent ev = gRecyclerTop;
gRecyclerTop = ev.mNext;
gRecyclerUsed--;
ev.mRecycledLocation = null;
ev.mRecycled = false;
return ev;
}
}
/**
* Create a new MotionEvent, filling in all of the basic values that
* define the motion.
*
* @param downTime The time (in ms) when the user originally pressed down to start
* a stream of position events. This must be obtained from {@link SystemClock#uptimeMillis()}.
* @param eventTime The the time (in ms) when this specific event was generated. This
* must be obtained from {@link SystemClock#uptimeMillis()}.
* @param action The kind of action being performed -- one of either
* {@link #ACTION_DOWN}, {@link #ACTION_MOVE}, {@link #ACTION_UP}, or
* {@link #ACTION_CANCEL}.
* @param x The X coordinate of this event.
* @param y The Y coordinate of this event.
* @param pressure The current pressure of this event. The pressure generally
* ranges from 0 (no pressure at all) to 1 (normal pressure), however
* values higher than 1 may be generated depending on the calibration of
* the input device.
* @param size A scaled value of the approximate size of the area being pressed when
* touched with the finger. The actual value in pixels corresponding to the finger
* touch is normalized with a device specific range of values
* and scaled to a value between 0 and 1.
* @param metaState The state of any meta / modifier keys that were in effect when
* the event was generated.
* @param xPrecision The precision of the X coordinate being reported.
* @param yPrecision The precision of the Y coordinate being reported.
* @param deviceId The id for the device that this event came from. An id of
* zero indicates that the event didn't come from a physical device; other
* numbers are arbitrary and you shouldn't depend on the values.
* @param edgeFlags A bitfield indicating which edges, if any, where touched by this
* MotionEvent.
*/
static public MotionEvent obtain(long downTime, long eventTime, int action,
float x, float y, float pressure, float size, int metaState,
float xPrecision, float yPrecision, int deviceId, int edgeFlags) {
MotionEvent ev = obtain();
ev.mDeviceId = deviceId;
ev.mEdgeFlags = edgeFlags;
ev.mDownTime = downTime;
ev.mEventTime = eventTime;
ev.mAction = action;
ev.mX = ev.mRawX = x;
ev.mY = ev.mRawY = y;
ev.mPressure = pressure;
ev.mSize = size;
ev.mMetaState = metaState;
ev.mXPrecision = xPrecision;
ev.mYPrecision = yPrecision;
return ev;
}
/**
* Create a new MotionEvent, filling in a subset of the basic motion
* values. Those not specified here are: device id (always 0), pressure
* and size (always 1), x and y precision (always 1), and edgeFlags (always 0).
*
* @param downTime The time (in ms) when the user originally pressed down to start
* a stream of position events. This must be obtained from {@link SystemClock#uptimeMillis()}.
* @param eventTime The the time (in ms) when this specific event was generated. This
* must be obtained from {@link SystemClock#uptimeMillis()}.
* @param action The kind of action being performed -- one of either
* {@link #ACTION_DOWN}, {@link #ACTION_MOVE}, {@link #ACTION_UP}, or
* {@link #ACTION_CANCEL}.
* @param x The X coordinate of this event.
* @param y The Y coordinate of this event.
* @param metaState The state of any meta / modifier keys that were in effect when
* the event was generated.
*/
static public MotionEvent obtain(long downTime, long eventTime, int action,
float x, float y, int metaState) {
MotionEvent ev = obtain();
ev.mDeviceId = 0;
ev.mEdgeFlags = 0;
ev.mDownTime = downTime;
ev.mEventTime = eventTime;
ev.mAction = action;
ev.mX = ev.mRawX = x;
ev.mY = ev.mRawY = y;
ev.mPressure = 1.0f;
ev.mSize = 1.0f;
ev.mMetaState = metaState;
ev.mXPrecision = 1.0f;
ev.mYPrecision = 1.0f;
return ev;
}
/**
* Create a new MotionEvent, copying from an existing one.
*/
static public MotionEvent obtain(MotionEvent o) {
MotionEvent ev = obtain();
ev.mDeviceId = o.mDeviceId;
ev.mEdgeFlags = o.mEdgeFlags;
ev.mDownTime = o.mDownTime;
ev.mEventTime = o.mEventTime;
ev.mAction = o.mAction;
ev.mX = o.mX;
ev.mRawX = o.mRawX;
ev.mY = o.mY;
ev.mRawY = o.mRawY;
ev.mPressure = o.mPressure;
ev.mSize = o.mSize;
ev.mMetaState = o.mMetaState;
ev.mXPrecision = o.mXPrecision;
ev.mYPrecision = o.mYPrecision;
final int N = o.mNumHistory;
ev.mNumHistory = N;
if (N > 0) {
// could be more efficient about this...
ev.mHistory = (float[])o.mHistory.clone();
ev.mHistoryTimes = (long[])o.mHistoryTimes.clone();
}
return ev;
}
/**
* Recycle the MotionEvent, to be re-used by a later caller. After calling
* this function you must not ever touch the event again.
*/
public void recycle() {
// Ensure recycle is only called once!
if (TRACK_RECYCLED_LOCATION) {
if (mRecycledLocation != null) {
throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation);
}
mRecycledLocation = new RuntimeException("Last recycled here");
} else if (mRecycled) {
throw new RuntimeException(toString() + " recycled twice!");
}
//Log.w("MotionEvent", "Recycling event " + this, mRecycledLocation);
synchronized (gRecyclerLock) {
if (gRecyclerUsed < MAX_RECYCLED) {
gRecyclerUsed++;
mNumHistory = 0;
mNext = gRecyclerTop;
gRecyclerTop = this;
}
}
}
/**
* Return the kind of action being performed -- one of either
* {@link #ACTION_DOWN}, {@link #ACTION_MOVE}, {@link #ACTION_UP}, or
* {@link #ACTION_CANCEL}.
*/
public final int getAction() {
return mAction;
}
/**
* Returns the time (in ms) when the user originally pressed down to start
* a stream of position events.
*/
public final long getDownTime() {
return mDownTime;
}
/**
* Returns the time (in ms) when this specific event was generated.
*/
public final long getEventTime() {
return mEventTime;
}
/**
* Returns the X coordinate of this event. Whole numbers are pixels; the
* value may have a fraction for input devices that are sub-pixel precise.
*/
public final float getX() {
return mX;
}
/**
* Returns the Y coordinate of this event. Whole numbers are pixels; the
* value may have a fraction for input devices that are sub-pixel precise.
*/
public final float getY() {
return mY;
}
/**
* Returns the current pressure of this event. The pressure generally
* ranges from 0 (no pressure at all) to 1 (normal pressure), however
* values higher than 1 may be generated depending on the calibration of
* the input device.
*/
public final float getPressure() {
return mPressure;
}
/**
* Returns a scaled value of the approximate size, of the area being pressed when
* touched with the finger. The actual value in pixels corresponding to the finger
* touch is normalized with the device specific range of values
* and scaled to a value between 0 and 1. The value of size can be used to
* determine fat touch events.
*/
public final float getSize() {
return mSize;
}
/**
* Returns the state of any meta / modifier keys that were in effect when
* the event was generated. This is the same values as those
* returned by {@link KeyEvent#getMetaState() KeyEvent.getMetaState}.
*
* @return an integer in which each bit set to 1 represents a pressed
* meta key
*
* @see KeyEvent#getMetaState()
*/
public final int getMetaState() {
return mMetaState;
}
/**
* Returns the original raw X coordinate of this event. For touch
* events on the screen, this is the original location of the event
* on the screen, before it had been adjusted for the containing window
* and views.
*/
public final float getRawX() {
return mRawX;
}
/**
* Returns the original raw Y coordinate of this event. For touch
* events on the screen, this is the original location of the event
* on the screen, before it had been adjusted for the containing window
* and views.
*/
public final float getRawY() {
return mRawY;
}
/**
* Return the precision of the X coordinates being reported. You can
* multiple this number with {@link #getX} to find the actual hardware
* value of the X coordinate.
* @return Returns the precision of X coordinates being reported.
*/
public final float getXPrecision() {
return mXPrecision;
}
/**
* Return the precision of the Y coordinates being reported. You can
* multiple this number with {@link #getY} to find the actual hardware
* value of the Y coordinate.
* @return Returns the precision of Y coordinates being reported.
*/
public final float getYPrecision() {
return mYPrecision;
}
/**
* Returns the number of historical points in this event. These are
* movements that have occurred between this event and the previous event.
* This only applies to ACTION_MOVE events -- all other actions will have
* a size of 0.
*
* @return Returns the number of historical points in the event.
*/
public final int getHistorySize() {
return mNumHistory;
}
/**
* Returns the time that a historical movement occurred between this event
* and the previous event. Only applies to ACTION_MOVE events.
*
* @param pos Which historical value to return; must be less than
* {@link #getHistorySize}
*
* @see #getHistorySize
* @see #getEventTime
*/
public final long getHistoricalEventTime(int pos) {
return mHistoryTimes[pos];
}
/**
* Returns a historical X coordinate that occurred between this event
* and the previous event. Only applies to ACTION_MOVE events.
*
* @param pos Which historical value to return; must be less than
* {@link #getHistorySize}
*
* @see #getHistorySize
* @see #getX
*/
public final float getHistoricalX(int pos) {
return mHistory[pos*4];
}
/**
* Returns a historical Y coordinate that occurred between this event
* and the previous event. Only applies to ACTION_MOVE events.
*
* @param pos Which historical value to return; must be less than
* {@link #getHistorySize}
*
* @see #getHistorySize
* @see #getY
*/
public final float getHistoricalY(int pos) {
return mHistory[pos*4 + 1];
}
/**
* Returns a historical pressure coordinate that occurred between this event
* and the previous event. Only applies to ACTION_MOVE events.
*
* @param pos Which historical value to return; must be less than
* {@link #getHistorySize}
*
* @see #getHistorySize
* @see #getPressure
*/
public final float getHistoricalPressure(int pos) {
return mHistory[pos*4 + 2];
}
/**
* Returns a historical size coordinate that occurred between this event
* and the previous event. Only applies to ACTION_MOVE events.
*
* @param pos Which historical value to return; must be less than
* {@link #getHistorySize}
*
* @see #getHistorySize
* @see #getSize
*/
public final float getHistoricalSize(int pos) {
return mHistory[pos*4 + 3];
}
/**
* Return the id for the device that this event came from. An id of
* zero indicates that the event didn't come from a physical device; other
* numbers are arbitrary and you shouldn't depend on the values.
*/
public final int getDeviceId() {
return mDeviceId;
}
/**
* Returns a bitfield indicating which edges, if any, where touched by this
* MotionEvent. For touch events, clients can use this to determine if the
* user's finger was touching the edge of the display.
*
* @see #EDGE_LEFT
* @see #EDGE_TOP
* @see #EDGE_RIGHT
* @see #EDGE_BOTTOM
*/
public final int getEdgeFlags() {
return mEdgeFlags;
}
/**
* Sets the bitfield indicating which edges, if any, where touched by this
* MotionEvent.
*
* @see #getEdgeFlags()
*/
public final void setEdgeFlags(int flags) {
mEdgeFlags = flags;
}
/**
* Sets this event's action.
*/
public final void setAction(int action) {
mAction = action;
}
/**
* Adjust this event's location.
* @param deltaX Amount to add to the current X coordinate of the event.
* @param deltaY Amount to add to the current Y coordinate of the event.
*/
public final void offsetLocation(float deltaX, float deltaY) {
mX += deltaX;
mY += deltaY;
final int N = mNumHistory*4;
if (N <= 0) {
return;
}
final float[] pos = mHistory;
for (int i=0; i<N; i+=4) {
pos[i] += deltaX;
pos[i+1] += deltaY;
}
}
/**
* Set this event's location. Applies {@link #offsetLocation} with a
* delta from the current location to the given new location.
*
* @param x New absolute X location.
* @param y New absolute Y location.
*/
public final void setLocation(float x, float y) {
float deltaX = x-mX;
float deltaY = y-mY;
if (deltaX != 0 || deltaY != 0) {
offsetLocation(deltaX, deltaY);
}
}
/**
* Add a new movement to the batch of movements in this event. The event's
* current location, position and size is updated to the new values. In
* the future, the current values in the event will be added to a list of
* historic values.
*
* @param x The new X position.
* @param y The new Y position.
* @param pressure The new pressure.
* @param size The new size.
*/
public final void addBatch(long eventTime, float x, float y,
float pressure, float size, int metaState) {
float[] history = mHistory;
long[] historyTimes = mHistoryTimes;
int N;
int avail;
if (history == null) {
mHistory = history = new float[8*4];
mHistoryTimes = historyTimes = new long[8];
mNumHistory = N = 0;
avail = 8;
} else {
N = mNumHistory;
avail = history.length/4;
if (N == avail) {
avail += 8;
float[] newHistory = new float[avail*4];
System.arraycopy(history, 0, newHistory, 0, N*4);
mHistory = history = newHistory;
long[] newHistoryTimes = new long[avail];
System.arraycopy(historyTimes, 0, newHistoryTimes, 0, N);
mHistoryTimes = historyTimes = newHistoryTimes;
}
}
historyTimes[N] = mEventTime;
final int pos = N*4;
history[pos] = mX;
history[pos+1] = mY;
history[pos+2] = mPressure;
history[pos+3] = mSize;
mNumHistory = N+1;
mEventTime = eventTime;
mX = mRawX = x;
mY = mRawY = y;
mPressure = pressure;
mSize = size;
mMetaState |= metaState;
}
@Override
public String toString() {
return "MotionEvent{" + Integer.toHexString(System.identityHashCode(this))
+ " action=" + mAction + " x=" + mX
+ " y=" + mY + " pressure=" + mPressure + " size=" + mSize + "}";
}
public static final Parcelable.Creator<MotionEvent> CREATOR
= new Parcelable.Creator<MotionEvent>() {
public MotionEvent createFromParcel(Parcel in) {
MotionEvent ev = obtain();
ev.readFromParcel(in);
return ev;
}
public MotionEvent[] newArray(int size) {
return new MotionEvent[size];
}
};
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
out.writeLong(mDownTime);
out.writeLong(mEventTime);
out.writeInt(mAction);
out.writeFloat(mX);
out.writeFloat(mY);
out.writeFloat(mPressure);
out.writeFloat(mSize);
out.writeInt(mMetaState);
out.writeFloat(mRawX);
out.writeFloat(mRawY);
final int N = mNumHistory;
out.writeInt(N);
if (N > 0) {
final int N4 = N*4;
int i;
float[] history = mHistory;
for (i=0; i<N4; i++) {
out.writeFloat(history[i]);
}
long[] times = mHistoryTimes;
for (i=0; i<N; i++) {
out.writeLong(times[i]);
}
}
out.writeFloat(mXPrecision);
out.writeFloat(mYPrecision);
out.writeInt(mDeviceId);
out.writeInt(mEdgeFlags);
}
private void readFromParcel(Parcel in) {
mDownTime = in.readLong();
mEventTime = in.readLong();
mAction = in.readInt();
mX = in.readFloat();
mY = in.readFloat();
mPressure = in.readFloat();
mSize = in.readFloat();
mMetaState = in.readInt();
mRawX = in.readFloat();
mRawY = in.readFloat();
final int N = in.readInt();
if ((mNumHistory=N) > 0) {
final int N4 = N*4;
float[] history = mHistory;
if (history == null || history.length < N4) {
mHistory = history = new float[N4 + (4*4)];
}
for (int i=0; i<N4; i++) {
history[i] = in.readFloat();
}
long[] times = mHistoryTimes;
if (times == null || times.length < N) {
mHistoryTimes = times = new long[N + 4];
}
for (int i=0; i<N; i++) {
times[i] = in.readLong();
}
}
mXPrecision = in.readFloat();
mYPrecision = in.readFloat();
mDeviceId = in.readInt();
mEdgeFlags = in.readInt();
}
}